mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
Add advisor model to openrouter
This commit is contained in:
parent
1b0863d336
commit
f4ae4c66df
9 changed files with 252 additions and 31 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiConfiguration, ModelInfo } from "../shared/api"
|
||||
import { ApiConfiguration, ModelInfo, ModelType } from "../shared/api"
|
||||
import { AnthropicHandler } from "./providers/anthropic"
|
||||
import { AwsBedrockHandler } from "./providers/bedrock"
|
||||
import { OpenRouterHandler } from "./providers/openrouter"
|
||||
|
|
@ -14,8 +14,9 @@ import { DeepSeekHandler } from "./providers/deepseek"
|
|||
import { MistralHandler } from "./providers/mistral"
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream
|
||||
getModel(): { id: string; info: ModelInfo }
|
||||
getAdvisorModel?(): { id: string; info: ModelInfo }
|
||||
}
|
||||
|
||||
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,15 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
|||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../shared/api"
|
||||
import {
|
||||
ApiHandlerOptions,
|
||||
ModelInfo,
|
||||
ModelType,
|
||||
openRouterDefaultAdvisorModelId,
|
||||
openRouterDefaultAdvisorModelInfo,
|
||||
openRouterDefaultModelId,
|
||||
openRouterDefaultModelInfo,
|
||||
} from "../../shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import delay from "delay"
|
||||
|
|
@ -23,7 +31,9 @@ export class OpenRouterHandler implements ApiHandler {
|
|||
})
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream {
|
||||
const model = modelType === "advisor" ? this.getAdvisorModel() : this.getModel()
|
||||
|
||||
// Convert Anthropic messages to OpenAI format
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
|
|
@ -32,7 +42,7 @@ export class OpenRouterHandler implements ApiHandler {
|
|||
|
||||
// prompt caching: https://openrouter.ai/docs/prompt-caching
|
||||
// this is specifically for claude models (some models may 'support prompt caching' automatically without this)
|
||||
switch (this.getModel().id) {
|
||||
switch (model.id) {
|
||||
case "anthropic/claude-3.5-sonnet":
|
||||
case "anthropic/claude-3.5-sonnet:beta":
|
||||
case "anthropic/claude-3.5-sonnet-20240620":
|
||||
|
|
@ -83,7 +93,7 @@ export class OpenRouterHandler implements ApiHandler {
|
|||
// Not sure how openrouter defaults max tokens when no value is provided, but the anthropic api requires this value and since they offer both 4096 and 8192 variants, we should ensure 8192.
|
||||
// (models usually default to max tokens allowed)
|
||||
let maxTokens: number | undefined
|
||||
switch (this.getModel().id) {
|
||||
switch (model.id) {
|
||||
case "anthropic/claude-3.5-sonnet":
|
||||
case "anthropic/claude-3.5-sonnet:beta":
|
||||
case "anthropic/claude-3.5-sonnet-20240620":
|
||||
|
|
@ -97,15 +107,15 @@ export class OpenRouterHandler implements ApiHandler {
|
|||
}
|
||||
|
||||
// Removes messages in the middle when close to context window limit. Should not be applied to models that support prompt caching since it would continuously break the cache.
|
||||
let shouldApplyMiddleOutTransform = !this.getModel().info.supportsPromptCache
|
||||
let shouldApplyMiddleOutTransform = !model.info.supportsPromptCache
|
||||
// except for deepseek (which we set supportsPromptCache to true for), where because the context window is so small our truncation algo might miss and we should use openrouter's middle-out transform as a fallback to ensure we don't exceed the context window (FIXME: once we have a more robust token estimator we should not rely on this)
|
||||
if (this.getModel().id === "deepseek/deepseek-chat") {
|
||||
if (model.id === "deepseek/deepseek-chat") {
|
||||
shouldApplyMiddleOutTransform = true
|
||||
}
|
||||
|
||||
// @ts-ignore-next-line
|
||||
const stream = await this.client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
model: model.id,
|
||||
max_tokens: maxTokens,
|
||||
temperature: 0,
|
||||
messages: openAiMessages,
|
||||
|
|
@ -181,4 +191,16 @@ export class OpenRouterHandler implements ApiHandler {
|
|||
info: openRouterDefaultModelInfo,
|
||||
}
|
||||
}
|
||||
|
||||
getAdvisorModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.openRouterAdvisorModelId
|
||||
const modelInfo = this.options.openRouterAdvisorModelInfo
|
||||
if (modelId && modelInfo) {
|
||||
return { id: modelId, info: modelInfo }
|
||||
}
|
||||
return {
|
||||
id: openRouterDefaultAdvisorModelId,
|
||||
info: openRouterDefaultAdvisorModelInfo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,9 @@ type GlobalStateKey =
|
|||
| "anthropicBaseUrl"
|
||||
| "azureApiVersion"
|
||||
| "openRouterModelId"
|
||||
| "openRouterAdvisorModelId"
|
||||
| "openRouterModelInfo"
|
||||
| "openRouterAdvisorModelInfo"
|
||||
| "autoApprovalSettings"
|
||||
| "browserSettings"
|
||||
|
||||
|
|
@ -354,6 +356,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
)
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
if (apiConfiguration.openRouterAdvisorModelId) {
|
||||
await this.updateGlobalState(
|
||||
"openRouterAdvisorModelInfo",
|
||||
openRouterModels[apiConfiguration.openRouterAdvisorModelId],
|
||||
)
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
|
|
@ -397,6 +406,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openRouterAdvisorModelId,
|
||||
openRouterAdvisorModelInfo,
|
||||
} = message.apiConfiguration
|
||||
await this.updateGlobalState("apiProvider", apiProvider)
|
||||
await this.updateGlobalState("apiModelId", apiModelId)
|
||||
|
|
@ -424,6 +435,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.updateGlobalState("azureApiVersion", azureApiVersion)
|
||||
await this.updateGlobalState("openRouterModelId", openRouterModelId)
|
||||
await this.updateGlobalState("openRouterModelInfo", openRouterModelInfo)
|
||||
await this.updateGlobalState("openRouterAdvisorModelId", openRouterAdvisorModelId)
|
||||
await this.updateGlobalState("openRouterAdvisorModelInfo", openRouterAdvisorModelInfo)
|
||||
if (this.cline) {
|
||||
this.cline.api = buildApiHandler(message.apiConfiguration)
|
||||
}
|
||||
|
|
@ -1030,6 +1043,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openRouterAdvisorModelId,
|
||||
openRouterAdvisorModelInfo,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
taskHistory,
|
||||
|
|
@ -1062,6 +1077,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.getGlobalState("azureApiVersion") as Promise<string | undefined>,
|
||||
this.getGlobalState("openRouterModelId") as Promise<string | undefined>,
|
||||
this.getGlobalState("openRouterModelInfo") as Promise<ModelInfo | undefined>,
|
||||
this.getGlobalState("openRouterAdvisorModelId") as Promise<string | undefined>,
|
||||
this.getGlobalState("openRouterAdvisorModelInfo") as Promise<ModelInfo | undefined>,
|
||||
this.getGlobalState("lastShownAnnouncementId") as Promise<string | undefined>,
|
||||
this.getGlobalState("customInstructions") as Promise<string | undefined>,
|
||||
this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
|
||||
|
|
@ -1111,6 +1128,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openRouterAdvisorModelId,
|
||||
openRouterAdvisorModelInfo,
|
||||
},
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ export interface ApiHandlerOptions {
|
|||
anthropicBaseUrl?: string
|
||||
openRouterApiKey?: string
|
||||
openRouterModelId?: string
|
||||
openRouterAdvisorModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
openRouterAdvisorModelInfo?: ModelInfo
|
||||
awsAccessKey?: string
|
||||
awsSecretKey?: string
|
||||
awsSessionToken?: string
|
||||
|
|
@ -178,6 +180,19 @@ export const openRouterDefaultModelInfo: ModelInfo = {
|
|||
description:
|
||||
"The new Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: New Sonnet scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal\n\n_This is a faster endpoint, made available in collaboration with Anthropic, that is self-moderated: response moderation happens on the provider's side instead of OpenRouter's. For requests that pass moderation, it's identical to the [Standard](/anthropic/claude-3.5-sonnet) variant._",
|
||||
}
|
||||
export const openRouterDefaultAdvisorModelId = "openai/o1-preview" // will always exist in openRouterModels
|
||||
export const openRouterDefaultAdvisorModelInfo: ModelInfo = {
|
||||
maxTokens: 33_000,
|
||||
contextWindow: 128_000,
|
||||
supportsImages: true,
|
||||
supportsComputerUse: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 15,
|
||||
outputPrice: 60,
|
||||
description:
|
||||
"The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.",
|
||||
}
|
||||
export type ModelType = "base" | "advisor"
|
||||
|
||||
// Vertex AI
|
||||
// https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react
|
|||
import { useEvent, useInterval } from "react-use"
|
||||
import {
|
||||
ApiConfiguration,
|
||||
ApiProvider,
|
||||
ModelInfo,
|
||||
anthropicDefaultModelId,
|
||||
anthropicModels,
|
||||
|
|
@ -26,6 +27,8 @@ import {
|
|||
openAiModelInfoSaneDefaults,
|
||||
openAiNativeDefaultModelId,
|
||||
openAiNativeModels,
|
||||
openRouterDefaultAdvisorModelId,
|
||||
openRouterDefaultAdvisorModelInfo,
|
||||
openRouterDefaultModelId,
|
||||
openRouterDefaultModelInfo,
|
||||
vertexDefaultModelId,
|
||||
|
|
@ -41,15 +44,49 @@ interface ApiOptionsProps {
|
|||
showModelOptions: boolean
|
||||
apiErrorMessage?: string
|
||||
modelIdErrorMessage?: string
|
||||
advisorModelIdErrorMessage?: string
|
||||
}
|
||||
|
||||
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) => {
|
||||
const TabPanel = ({ children, isSelected }: { children: React.ReactNode; isSelected: boolean }) => {
|
||||
if (!isSelected) return null
|
||||
return <div style={{ marginTop: 10 }}>{children}</div>
|
||||
}
|
||||
|
||||
const TabButton = ({
|
||||
isSelected,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
isSelected: boolean
|
||||
onClick: () => void
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
style={{
|
||||
background: "var(--vscode-tab-inactiveBackground)",
|
||||
border: "none",
|
||||
padding: "8px 16px",
|
||||
color: isSelected ? "var(--vscode-tab-activeForeground)" : "var(--vscode-tab-inactiveForeground)",
|
||||
cursor: "pointer",
|
||||
borderBottom: `2px solid ${isSelected ? "var(--vscode-foreground)" : "transparent"}`,
|
||||
fontSize: "12px",
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, advisorModelIdErrorMessage }: ApiOptionsProps) => {
|
||||
const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState()
|
||||
const [ollamaModels, setOllamaModels] = useState<string[]>([])
|
||||
const [lmStudioModels, setLmStudioModels] = useState<string[]>([])
|
||||
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
|
||||
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
|
||||
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
|
||||
const [selectedTab, setSelectedTab] = useState("base")
|
||||
|
||||
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
||||
setApiConfiguration({
|
||||
|
|
@ -713,8 +750,6 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
|
|||
</p>
|
||||
)}
|
||||
|
||||
{selectedProvider === "openrouter" && showModelOptions && <OpenRouterModelPicker />}
|
||||
|
||||
{selectedProvider !== "openrouter" &&
|
||||
selectedProvider !== "openai" &&
|
||||
selectedProvider !== "ollama" &&
|
||||
|
|
@ -743,7 +778,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
|
|||
</>
|
||||
)}
|
||||
|
||||
{modelIdErrorMessage && (
|
||||
{selectedProvider !== "openrouter" && modelIdErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
margin: "-10px 0 4px 0",
|
||||
|
|
@ -753,6 +788,65 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
|
|||
{modelIdErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{selectedProvider === "openrouter" && showModelOptions && (
|
||||
<div style={{ marginTop: -5 }}>
|
||||
<div style={{ display: "flex", borderBottom: "1px solid var(--vscode-panel-border)" }}>
|
||||
<TabButton isSelected={selectedTab === "base"} onClick={() => setSelectedTab("base")}>
|
||||
Cline Model
|
||||
</TabButton>
|
||||
<TabButton isSelected={selectedTab === "advisor"} onClick={() => setSelectedTab("advisor")}>
|
||||
Advisor Model
|
||||
</TabButton>
|
||||
</div>
|
||||
|
||||
<TabPanel isSelected={selectedTab === "base"}>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginBottom: "10px",
|
||||
color: "var(--vscode-foreground)",
|
||||
}}>
|
||||
This is the default driver model for Cline. It will read and edit files, run commands, and more, with
|
||||
your permission at each step.
|
||||
</p>
|
||||
<OpenRouterModelPicker modelType="base" key="base-model-picker" />
|
||||
{modelIdErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
margin: "-10px 0 4px 0",
|
||||
fontSize: 12,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{modelIdErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel isSelected={selectedTab === "advisor"}>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginBottom: "10px",
|
||||
color: "var(--vscode-foreground)",
|
||||
}}>
|
||||
The Cline model can call this smarter, more powerful model to ask for help on planning out a task,
|
||||
fixing a hard bug, and other complex problems.
|
||||
</p>
|
||||
<OpenRouterModelPicker modelType="advisor" key="advisor-model-picker" />
|
||||
{advisorModelIdErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
margin: "-10px 0 4px 0",
|
||||
fontSize: 12,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{advisorModelIdErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
</TabPanel>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -895,7 +989,13 @@ const ModelInfoSupportsItem = ({
|
|||
</span>
|
||||
)
|
||||
|
||||
export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
|
||||
export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration): {
|
||||
selectedProvider: ApiProvider
|
||||
selectedModelId: string
|
||||
selectedModelInfo: ModelInfo
|
||||
selectedAdvisorModelId?: string
|
||||
selectedAdvisorModelInfo?: ModelInfo
|
||||
} {
|
||||
const provider = apiConfiguration?.apiProvider || "anthropic"
|
||||
const modelId = apiConfiguration?.apiModelId
|
||||
|
||||
|
|
@ -935,6 +1035,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
|
|||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
|
||||
selectedModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo,
|
||||
selectedAdvisorModelId: apiConfiguration?.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId,
|
||||
selectedAdvisorModelInfo: apiConfiguration?.openRouterAdvisorModelInfo || openRouterDefaultAdvisorModelInfo,
|
||||
}
|
||||
case "openai":
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,15 +4,28 @@ import React, { KeyboardEvent, memo, useEffect, useMemo, useRef, useState } from
|
|||
import { useRemark } from "react-remark"
|
||||
import { useMount } from "react-use"
|
||||
import styled from "styled-components"
|
||||
import { openRouterDefaultModelId } from "../../../../src/shared/api"
|
||||
import {
|
||||
ModelType,
|
||||
openRouterDefaultAdvisorModelId,
|
||||
openRouterDefaultAdvisorModelInfo,
|
||||
openRouterDefaultModelId,
|
||||
} from "../../../../src/shared/api"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import { highlight } from "../history/HistoryView"
|
||||
import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions"
|
||||
|
||||
const OpenRouterModelPicker: React.FC = () => {
|
||||
export interface OpenRouterModelPickerProps {
|
||||
modelType: ModelType
|
||||
}
|
||||
|
||||
const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ modelType }) => {
|
||||
const { apiConfiguration, setApiConfiguration, openRouterModels } = useExtensionState()
|
||||
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.openRouterModelId || openRouterDefaultModelId)
|
||||
const [searchTerm, setSearchTerm] = useState(
|
||||
modelType === "advisor"
|
||||
? apiConfiguration?.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId
|
||||
: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
|
||||
)
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
|
@ -24,13 +37,20 @@ const OpenRouterModelPicker: React.FC = () => {
|
|||
// could be setting invalid model id/undefined info but validation will catch it
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openRouterModelId: newModelId,
|
||||
openRouterModelInfo: openRouterModels[newModelId],
|
||||
...(modelType === "advisor"
|
||||
? {
|
||||
openRouterAdvisorModelId: newModelId,
|
||||
openRouterAdvisorModelInfo: openRouterModels[newModelId],
|
||||
}
|
||||
: {
|
||||
openRouterModelId: newModelId,
|
||||
openRouterModelInfo: openRouterModels[newModelId],
|
||||
}),
|
||||
})
|
||||
setSearchTerm(newModelId)
|
||||
}
|
||||
|
||||
const { selectedModelId, selectedModelInfo } = useMemo(() => {
|
||||
const { selectedModelId, selectedModelInfo, selectedAdvisorModelId, selectedAdvisorModelInfo } = useMemo(() => {
|
||||
return normalizeApiConfiguration(apiConfiguration)
|
||||
}, [apiConfiguration])
|
||||
|
||||
|
|
@ -129,7 +149,7 @@ const OpenRouterModelPicker: React.FC = () => {
|
|||
}, [selectedIndex])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ width: "100%" }}>
|
||||
<style>
|
||||
{`
|
||||
.model-item-highlight {
|
||||
|
|
@ -138,10 +158,10 @@ const OpenRouterModelPicker: React.FC = () => {
|
|||
}
|
||||
`}
|
||||
</style>
|
||||
<div>
|
||||
<label htmlFor="model-search">
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
{/* <label htmlFor="model-search">
|
||||
<span style={{ fontWeight: 500 }}>Model</span>
|
||||
</label>
|
||||
</label> */}
|
||||
<DropdownWrapper ref={dropdownRef}>
|
||||
<VSCodeTextField
|
||||
id="model-search"
|
||||
|
|
@ -200,8 +220,14 @@ const OpenRouterModelPicker: React.FC = () => {
|
|||
|
||||
{hasInfo ? (
|
||||
<ModelInfoView
|
||||
selectedModelId={selectedModelId}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={
|
||||
modelType === "advisor" ? selectedAdvisorModelId || openRouterDefaultAdvisorModelId : selectedModelId
|
||||
}
|
||||
modelInfo={
|
||||
modelType === "advisor"
|
||||
? selectedAdvisorModelInfo || openRouterDefaultAdvisorModelInfo
|
||||
: selectedModelInfo
|
||||
}
|
||||
isDescriptionExpanded={isDescriptionExpanded}
|
||||
setIsDescriptionExpanded={setIsDescriptionExpanded}
|
||||
/>
|
||||
|
|
@ -225,7 +251,7 @@ const OpenRouterModelPicker: React.FC = () => {
|
|||
You can also try searching "free" for no-cost options currently available.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { VSCodeButton, VSCodeLink, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useEffect, useState } from "react"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { validateApiConfiguration, validateModelId } from "../../utils/validate"
|
||||
import { validateAdvisorModelId, validateApiConfiguration, validateModelId } from "../../utils/validate"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import ApiOptions from "./ApiOptions"
|
||||
|
||||
|
|
@ -15,13 +15,18 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } = useExtensionState()
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [advisorModelIdErrorMessage, setAdvisorModelIdErrorMessage] = useState<string | undefined>(undefined)
|
||||
|
||||
const handleSubmit = () => {
|
||||
const apiValidationResult = validateApiConfiguration(apiConfiguration)
|
||||
const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels)
|
||||
const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels)
|
||||
|
||||
setApiErrorMessage(apiValidationResult)
|
||||
setModelIdErrorMessage(modelIdValidationResult)
|
||||
if (!apiValidationResult && !modelIdValidationResult) {
|
||||
setAdvisorModelIdErrorMessage(advisorModelIdValidationResult)
|
||||
|
||||
if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) {
|
||||
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||
vscode.postMessage({
|
||||
type: "customInstructions",
|
||||
|
|
@ -34,6 +39,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
useEffect(() => {
|
||||
setApiErrorMessage(undefined)
|
||||
setModelIdErrorMessage(undefined)
|
||||
setAdvisorModelIdErrorMessage(undefined)
|
||||
}, [apiConfiguration])
|
||||
|
||||
// validate as soon as the component is mounted
|
||||
|
|
@ -89,6 +95,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
showModelOptions={true}
|
||||
apiErrorMessage={apiErrorMessage}
|
||||
modelIdErrorMessage={modelIdErrorMessage}
|
||||
advisorModelIdErrorMessage={advisorModelIdErrorMessage}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@ import React, { createContext, useCallback, useContext, useEffect, useState } fr
|
|||
import { useEvent } from "react-use"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../../src/shared/AutoApprovalSettings"
|
||||
import { ExtensionMessage, ExtensionState } from "../../../src/shared/ExtensionMessage"
|
||||
import { ApiConfiguration, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../../src/shared/api"
|
||||
import {
|
||||
ApiConfiguration,
|
||||
ModelInfo,
|
||||
openRouterDefaultAdvisorModelId,
|
||||
openRouterDefaultAdvisorModelInfo,
|
||||
openRouterDefaultModelId,
|
||||
openRouterDefaultModelInfo,
|
||||
} from "../../../src/shared/api"
|
||||
import { findLastIndex } from "../../../src/shared/array"
|
||||
import { McpServer } from "../../../src/shared/mcp"
|
||||
import { convertTextMateToHljs } from "../utils/textMateToHljs"
|
||||
|
|
@ -40,6 +47,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
const [filePaths, setFilePaths] = useState<string[]>([])
|
||||
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
|
||||
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
|
||||
[openRouterDefaultAdvisorModelId]: openRouterDefaultAdvisorModelInfo,
|
||||
})
|
||||
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
|
||||
|
||||
|
|
@ -96,6 +104,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
const updatedModels = message.openRouterModels ?? {}
|
||||
setOpenRouterModels({
|
||||
[openRouterDefaultModelId]: openRouterDefaultModelInfo, // in case the extension sent a model list without the default model
|
||||
[openRouterDefaultAdvisorModelId]: openRouterDefaultAdvisorModelInfo,
|
||||
...updatedModels,
|
||||
})
|
||||
break
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ApiConfiguration, openRouterDefaultModelId } from "../../../src/shared/api"
|
||||
import { ApiConfiguration, openRouterDefaultAdvisorModelId, openRouterDefaultModelId } from "../../../src/shared/api"
|
||||
import { ModelInfo } from "../../../src/shared/api"
|
||||
export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): string | undefined {
|
||||
if (apiConfiguration) {
|
||||
|
|
@ -83,3 +83,23 @@ export function validateModelId(
|
|||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function validateAdvisorModelId(
|
||||
apiConfiguration?: ApiConfiguration,
|
||||
openRouterModels?: Record<string, ModelInfo>,
|
||||
): string | undefined {
|
||||
if (apiConfiguration) {
|
||||
switch (apiConfiguration.apiProvider) {
|
||||
case "openrouter":
|
||||
const advisorModelId = apiConfiguration.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId // in case the user hasn't changed the model id, it will be undefined by default
|
||||
if (!advisorModelId) {
|
||||
return "You must provide a model ID."
|
||||
}
|
||||
if (openRouterModels && !Object.keys(openRouterModels).includes(advisorModelId)) {
|
||||
return "The model ID you provided is not available. Please choose a different model."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue