feat: add Azure AI models support in context and API handling

This commit is contained in:
pacnpal
2025-02-03 13:08:13 -05:00
parent 412eb9ef92
commit 8f0548ec97
5 changed files with 574 additions and 293 deletions

View File

@@ -754,6 +754,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.postMessageToWebview({ type: "openAiModels", openAiModels }) this.postMessageToWebview({ type: "openAiModels", openAiModels })
} }
break break
case "refreshAzureAiModels":
if (message?.values?.endpoint && message?.values?.key) {
const azureAiModels = await this.getAzureAiModels(
message?.values?.endpoint,
message?.values?.key,
)
this.postMessageToWebview({ type: "azureAiModels", azureAiModels })
}
break
case "openImage": case "openImage":
openImage(message.text!) openImage(message.text!)
break break
@@ -1645,6 +1654,28 @@ export class ClineProvider implements vscode.WebviewViewProvider {
} }
} }
// Azure AI
async getAzureAiModels(endpoint?: string, key?: string) {
try {
if (!endpoint || !key) {
return []
}
if (!URL.canParse(endpoint)) {
return []
}
const response = await axios.get(`${endpoint}/deployments`, {
headers: { "api-key": key },
})
const modelsArray = response.data?.value?.map((deployment: any) => deployment.name) || []
const models = [...new Set<string>(modelsArray)]
return models
} catch (error) {
return []
}
}
// OpenRouter // OpenRouter
async handleOpenRouterCallback(code: string) { async handleOpenRouterCallback(code: string) {

View File

@@ -7,220 +7,223 @@ import { CustomSupportPrompts } from "./support-prompt"
import { ExperimentId } from "./experiments" import { ExperimentId } from "./experiments"
export interface LanguageModelChatSelector { export interface LanguageModelChatSelector {
vendor?: string vendor?: string
family?: string family?: string
version?: string version?: string
id?: string id?: string
} }
export interface ExtensionMessage { export interface ExtensionMessage {
type: type:
| "action" | "action"
| "state" | "state"
| "selectedImages" | "selectedImages"
| "ollamaModels" | "ollamaModels"
| "lmStudioModels" | "lmStudioModels"
| "theme" | "theme"
| "workspaceUpdated" | "workspaceUpdated"
| "invoke" | "invoke"
| "partialMessage" | "partialMessage"
| "glamaModels" | "glamaModels"
| "openRouterModels" | "openRouterModels"
| "openAiModels" | "openAiModels"
| "mcpServers" | "refreshAzureAiModels"
| "enhancedPrompt" | "azureAiModels"
| "commitSearchResults" | "mcpServers"
| "listApiConfig" | "enhancedPrompt"
| "vsCodeLmModels" | "commitSearchResults"
| "vsCodeLmApiAvailable" | "listApiConfig"
| "requestVsCodeLmModels" | "vsCodeLmModels"
| "updatePrompt" | "vsCodeLmApiAvailable"
| "systemPrompt" | "requestVsCodeLmModels"
| "autoApprovalEnabled" | "updatePrompt"
| "updateCustomMode" | "systemPrompt"
| "deleteCustomMode" | "autoApprovalEnabled"
text?: string | "updateCustomMode"
action?: | "deleteCustomMode"
| "chatButtonClicked" text?: string
| "mcpButtonClicked" action?:
| "settingsButtonClicked" | "chatButtonClicked"
| "historyButtonClicked" | "mcpButtonClicked"
| "promptsButtonClicked" | "settingsButtonClicked"
| "didBecomeVisible" | "historyButtonClicked"
invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage" | "promptsButtonClicked"
state?: ExtensionState | "didBecomeVisible"
images?: string[] invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage"
ollamaModels?: string[] state?: ExtensionState
lmStudioModels?: string[] images?: string[]
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[] ollamaModels?: string[]
filePaths?: string[] lmStudioModels?: string[]
openedTabs?: Array<{ vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
label: string filePaths?: string[]
isActive: boolean openedTabs?: Array<{
path?: string label: string
}> isActive: boolean
partialMessage?: ClineMessage path?: string
glamaModels?: Record<string, ModelInfo> }>
openRouterModels?: Record<string, ModelInfo> partialMessage?: ClineMessage
openAiModels?: string[] glamaModels?: Record<string, ModelInfo>
mcpServers?: McpServer[] openRouterModels?: Record<string, ModelInfo>
commits?: GitCommit[] openAiModels?: string[]
listApiConfig?: ApiConfigMeta[] azureAiModels?: string[]
mode?: Mode mcpServers?: McpServer[]
customMode?: ModeConfig commits?: GitCommit[]
slug?: string listApiConfig?: ApiConfigMeta[]
mode?: Mode
customMode?: ModeConfig
slug?: string
} }
export interface ApiConfigMeta { export interface ApiConfigMeta {
id: string id: string
name: string name: string
apiProvider?: ApiProvider apiProvider?: ApiProvider
} }
export interface ExtensionState { export interface ExtensionState {
version: string version: string
clineMessages: ClineMessage[] clineMessages: ClineMessage[]
taskHistory: HistoryItem[] taskHistory: HistoryItem[]
shouldShowAnnouncement: boolean shouldShowAnnouncement: boolean
apiConfiguration?: ApiConfiguration apiConfiguration?: ApiConfiguration
currentApiConfigName?: string currentApiConfigName?: string
listApiConfigMeta?: ApiConfigMeta[] listApiConfigMeta?: ApiConfigMeta[]
customInstructions?: string customInstructions?: string
customModePrompts?: CustomModePrompts customModePrompts?: CustomModePrompts
customSupportPrompts?: CustomSupportPrompts customSupportPrompts?: CustomSupportPrompts
alwaysAllowReadOnly?: boolean alwaysAllowReadOnly?: boolean
alwaysAllowWrite?: boolean alwaysAllowWrite?: boolean
alwaysAllowExecute?: boolean alwaysAllowExecute?: boolean
alwaysAllowBrowser?: boolean alwaysAllowBrowser?: boolean
alwaysAllowMcp?: boolean alwaysAllowMcp?: boolean
alwaysApproveResubmit?: boolean alwaysApproveResubmit?: boolean
alwaysAllowModeSwitch?: boolean alwaysAllowModeSwitch?: boolean
requestDelaySeconds: number requestDelaySeconds: number
rateLimitSeconds: number rateLimitSeconds: number
uriScheme?: string uriScheme?: string
allowedCommands?: string[] allowedCommands?: string[]
soundEnabled?: boolean soundEnabled?: boolean
soundVolume?: number soundVolume?: number
diffEnabled?: boolean diffEnabled?: boolean
browserViewportSize?: string browserViewportSize?: string
screenshotQuality?: number screenshotQuality?: number
fuzzyMatchThreshold?: number fuzzyMatchThreshold?: number
preferredLanguage: string preferredLanguage: string
writeDelayMs: number writeDelayMs: number
terminalOutputLineLimit?: number terminalOutputLineLimit?: number
mcpEnabled: boolean mcpEnabled: boolean
enableMcpServerCreation: boolean enableMcpServerCreation: boolean
mode: Mode mode: Mode
modeApiConfigs?: Record<Mode, string> modeApiConfigs?: Record<Mode, string>
enhancementApiConfigId?: string enhancementApiConfigId?: string
experiments: Record<ExperimentId, boolean> experiments: Record<ExperimentId, boolean>
autoApprovalEnabled?: boolean autoApprovalEnabled?: boolean
customModes: ModeConfig[] customModes: ModeConfig[]
toolRequirements?: Record<string, boolean> toolRequirements?: Record<string, boolean>
azureAiDeployments?: Record<string, AzureDeploymentConfig> azureAiDeployments?: Record<string, AzureDeploymentConfig>
} }
export interface ClineMessage { export interface ClineMessage {
ts: number ts: number
type: "ask" | "say" type: "ask" | "say"
ask?: ClineAsk ask?: ClineAsk
say?: ClineSay say?: ClineSay
text?: string text?: string
images?: string[] images?: string[]
partial?: boolean partial?: boolean
reasoning?: string reasoning?: string
} }
export type ClineAsk = export type ClineAsk =
| "followup" | "followup"
| "command" | "command"
| "command_output" | "command_output"
| "completion_result" | "completion_result"
| "tool" | "tool"
| "api_req_failed" | "api_req_failed"
| "resume_task" | "resume_task"
| "resume_completed_task" | "resume_completed_task"
| "mistake_limit_reached" | "mistake_limit_reached"
| "browser_action_launch" | "browser_action_launch"
| "use_mcp_server" | "use_mcp_server"
export type ClineSay = export type ClineSay =
| "task" | "task"
| "error" | "error"
| "api_req_started" | "api_req_started"
| "api_req_finished" | "api_req_finished"
| "text" | "text"
| "reasoning" | "reasoning"
| "completion_result" | "completion_result"
| "user_feedback" | "user_feedback"
| "user_feedback_diff" | "user_feedback_diff"
| "api_req_retried" | "api_req_retried"
| "api_req_retry_delayed" | "api_req_retry_delayed"
| "command_output" | "command_output"
| "tool" | "tool"
| "shell_integration_warning" | "shell_integration_warning"
| "browser_action" | "browser_action"
| "browser_action_result" | "browser_action_result"
| "command" | "command"
| "mcp_server_request_started" | "mcp_server_request_started"
| "mcp_server_response" | "mcp_server_response"
| "new_task_started" | "new_task_started"
| "new_task" | "new_task"
export interface ClineSayTool { export interface ClineSayTool {
tool: tool:
| "editedExistingFile" | "editedExistingFile"
| "appliedDiff" | "appliedDiff"
| "newFileCreated" | "newFileCreated"
| "readFile" | "readFile"
| "listFilesTopLevel" | "listFilesTopLevel"
| "listFilesRecursive" | "listFilesRecursive"
| "listCodeDefinitionNames" | "listCodeDefinitionNames"
| "searchFiles" | "searchFiles"
| "switchMode" | "switchMode"
| "newTask" | "newTask"
path?: string path?: string
diff?: string diff?: string
content?: string content?: string
regex?: string regex?: string
filePattern?: string filePattern?: string
mode?: string mode?: string
reason?: string reason?: string
} }
export const browserActions = ["launch", "click", "type", "scroll_down", "scroll_up", "close"] as const export const browserActions = ["launch", "click", "type", "scroll_down", "scroll_up", "close"] as const
export type BrowserAction = (typeof browserActions)[number] export type BrowserAction = (typeof browserActions)[number]
export interface ClineSayBrowserAction { export interface ClineSayBrowserAction {
action: BrowserAction action: BrowserAction
coordinate?: string coordinate?: string
text?: string text?: string
} }
export type BrowserActionResult = { export type BrowserActionResult = {
screenshot?: string screenshot?: string
logs?: string logs?: string
currentUrl?: string currentUrl?: string
currentMousePosition?: string currentMousePosition?: string
} }
export interface ClineAskUseMcpServer { export interface ClineAskUseMcpServer {
serverName: string serverName: string
type: "use_mcp_tool" | "access_mcp_resource" type: "use_mcp_tool" | "access_mcp_resource"
toolName?: string toolName?: string
arguments?: string arguments?: string
uri?: string uri?: string
} }
export interface ClineApiReqInfo { export interface ClineApiReqInfo {
request?: string request?: string
tokensIn?: number tokensIn?: number
tokensOut?: number tokensOut?: number
cacheWrites?: number cacheWrites?: number
cacheReads?: number cacheReads?: number
cost?: number cost?: number
cancelReason?: ClineApiReqCancelReason cancelReason?: ClineApiReqCancelReason
streamingFailedMessage?: string streamingFailedMessage?: string
} }
export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled" export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"

View File

@@ -16,6 +16,7 @@ export function checkExistKey(config: ApiConfiguration | undefined) {
config.deepSeekApiKey, config.deepSeekApiKey,
config.mistralApiKey, config.mistralApiKey,
config.vsCodeLmModelSelector, config.vsCodeLmModelSelector,
config.azureAiKey,
].some((key) => key !== undefined) ].some((key) => key !== undefined)
: false : false
} }

View File

@@ -1,100 +1,305 @@
import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { memo } from "react" import { Fzf } from "fzf"
import React, { KeyboardEvent, memo, useEffect, useMemo, useRef, useState } from "react"
import debounce from "debounce"
import { useExtensionState } from "../../context/ExtensionStateContext" import { useExtensionState } from "../../context/ExtensionStateContext"
import { vscode } from "../../utils/vscode"
import { highlightFzfMatch } from "../../utils/highlight"
import { Pane } from "vscrui" import { Pane } from "vscrui"
import { azureAiModelInfoSaneDefaults } from "../../../../src/shared/api" import { azureAiModelInfoSaneDefaults } from "../../../../src/shared/api"
import styled from "styled-components"
const AzureAiModelPicker: React.FC = () => { const AzureAiModelPicker: React.FC = () => {
const { apiConfiguration, handleInputChange } = useExtensionState() const { apiConfiguration, setApiConfiguration, azureAiModels, onUpdateApiConfig } = useExtensionState()
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.apiModelId || "")
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(-1)
const dropdownRef = useRef<HTMLDivElement>(null)
const itemRefs = useRef<(HTMLDivElement | null)[]>([])
const dropdownListRef = useRef<HTMLDivElement>(null)
const handleModelChange = (newModelId: string) => {
const apiConfig = {
...apiConfiguration,
apiModelId: newModelId,
}
setApiConfiguration(apiConfig)
onUpdateApiConfig(apiConfig)
setSearchTerm(newModelId)
}
useEffect(() => {
if (apiConfiguration?.apiModelId && apiConfiguration?.apiModelId !== searchTerm) {
setSearchTerm(apiConfiguration?.apiModelId)
}
}, [apiConfiguration, searchTerm])
const debouncedRefreshModels = useMemo(
() =>
debounce((endpoint: string, key: string) => {
vscode.postMessage({
type: "refreshAzureAiModels",
values: {
endpoint,
key,
},
})
}, 50),
[],
)
useEffect(() => {
if (!apiConfiguration?.azureAiEndpoint || !apiConfiguration?.azureAiKey) {
return
}
debouncedRefreshModels(apiConfiguration.azureAiEndpoint, apiConfiguration.azureAiKey)
return () => {
debouncedRefreshModels.clear()
}
}, [apiConfiguration?.azureAiEndpoint, apiConfiguration?.azureAiKey, debouncedRefreshModels])
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsDropdownVisible(false)
}
}
document.addEventListener("mousedown", handleClickOutside)
return () => {
document.removeEventListener("mousedown", handleClickOutside)
}
}, [])
const modelIds = useMemo(() => {
return azureAiModels.sort((a, b) => a.localeCompare(b))
}, [azureAiModels])
const searchableItems = useMemo(() => {
return modelIds.map((id) => ({
id,
html: id,
}))
}, [modelIds])
const fzf = useMemo(() => {
return new Fzf(searchableItems, {
selector: (item) => item.html,
})
}, [searchableItems])
const modelSearchResults = useMemo(() => {
if (!searchTerm) return searchableItems
const searchResults = fzf.find(searchTerm)
return searchResults.map((result) => ({
...result.item,
html: highlightFzfMatch(result.item.html, Array.from(result.positions), "model-item-highlight"),
}))
}, [searchableItems, searchTerm, fzf])
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (!isDropdownVisible) return
switch (event.key) {
case "ArrowDown":
event.preventDefault()
setSelectedIndex((prev) => (prev < modelSearchResults.length - 1 ? prev + 1 : prev))
break
case "ArrowUp":
event.preventDefault()
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev))
break
case "Enter":
event.preventDefault()
if (selectedIndex >= 0 && selectedIndex < modelSearchResults.length) {
handleModelChange(modelSearchResults[selectedIndex].id)
setIsDropdownVisible(false)
}
break
case "Escape":
setIsDropdownVisible(false)
setSelectedIndex(-1)
break
}
}
useEffect(() => {
setSelectedIndex(-1)
if (dropdownListRef.current) {
dropdownListRef.current.scrollTop = 0
}
}, [searchTerm])
useEffect(() => {
if (selectedIndex >= 0 && itemRefs.current[selectedIndex]) {
itemRefs.current[selectedIndex]?.scrollIntoView({
block: "nearest",
behavior: "smooth",
})
}
}, [selectedIndex])
return ( return (
<div style={{ display: "flex", flexDirection: "column", rowGap: "5px" }}> <>
<VSCodeTextField <style>
value={apiConfiguration?.azureAiEndpoint || ""} {`
style={{ width: "100%" }} .model-item-highlight {
type="url" background-color: var(--vscode-editor-findMatchHighlightBackground);
onChange={handleInputChange("azureAiEndpoint")} color: inherit;
placeholder="https://your-endpoint.region.inference.ai.azure.com"> }
<span style={{ fontWeight: 500 }}>Base URL</span> `}
</VSCodeTextField> </style>
<div style={{ display: "flex", flexDirection: "column", rowGap: "5px" }}>
<VSCodeTextField
value={apiConfiguration?.azureAiEndpoint || ""}
style={{ width: "100%" }}
type="url"
onChange={(e) => {
const apiConfig = {
...apiConfiguration,
azureAiEndpoint: (e.target as HTMLInputElement).value,
}
setApiConfiguration(apiConfig)
onUpdateApiConfig(apiConfig)
}}
placeholder="https://your-endpoint.region.inference.ai.azure.com">
<span style={{ fontWeight: 500 }}>Base URL</span>
</VSCodeTextField>
<VSCodeTextField <VSCodeTextField
value={apiConfiguration?.azureAiKey || ""} value={apiConfiguration?.azureAiKey || ""}
style={{ width: "100%" }} style={{ width: "100%" }}
type="password" type="password"
onChange={handleInputChange("azureAiKey")} onChange={(e) => {
placeholder="Enter API Key..."> const apiConfig = {
<span style={{ fontWeight: 500 }}>API Key</span> ...apiConfiguration,
</VSCodeTextField> azureAiKey: (e.target as HTMLInputElement).value,
}
setApiConfiguration(apiConfig)
onUpdateApiConfig(apiConfig)
}}
placeholder="Enter API Key...">
<span style={{ fontWeight: 500 }}>API Key</span>
</VSCodeTextField>
<VSCodeTextField <DropdownWrapper ref={dropdownRef}>
value={apiConfiguration?.apiModelId || ""} <VSCodeTextField
style={{ width: "100%" }} id="model-search"
type="text" placeholder="Search and select a model..."
onChange={handleInputChange("apiModelId")} value={searchTerm}
placeholder="Enter model deployment name..."> onInput={(e) => {
<span style={{ fontWeight: 500 }}>Model Deployment Name</span> handleModelChange((e.target as HTMLInputElement)?.value)
</VSCodeTextField> setIsDropdownVisible(true)
}}
<Pane onFocus={() => setIsDropdownVisible(true)}
title="Model Configuration" onKeyDown={handleKeyDown}
open={false} style={{ width: "100%", zIndex: AZURE_MODEL_PICKER_Z_INDEX, position: "relative" }}>
actions={[ <span style={{ fontWeight: 500 }}>Model Deployment Name</span>
{ {searchTerm && (
iconName: "refresh", <div
onClick: () => className="input-icon-button codicon codicon-close"
handleInputChange("azureAiModelConfig")({ aria-label="Clear search"
target: { value: azureAiModelInfoSaneDefaults }, onClick={() => {
}), handleModelChange("")
}, setIsDropdownVisible(true)
]}> }}
<div slot="end"
style={{ style={{
padding: 15, display: "flex",
backgroundColor: "var(--vscode-editor-background)", justifyContent: "center",
}}> alignItems: "center",
<p height: "100%",
style={{ }}
fontSize: "12px", />
color: "var(--vscode-descriptionForeground)", )}
margin: "0 0 15px 0", </VSCodeTextField>
lineHeight: "1.4", {isDropdownVisible && (
}}> <DropdownList ref={dropdownListRef}>
Configure capabilities for your deployed model. {modelSearchResults.map((item, index) => (
</p> <DropdownItem
key={item.id}
ref={(el) => (itemRefs.current[index] = el)}
isSelected={index === selectedIndex}
onMouseEnter={() => setSelectedIndex(index)}
onClick={() => {
handleModelChange(item.id)
setIsDropdownVisible(false)
}}
dangerouslySetInnerHTML={{
__html: item.html,
}}
/>
))}
</DropdownList>
)}
</DropdownWrapper>
<Pane
title="Model Configuration"
open={false}
actions={[
{
iconName: "refresh",
onClick: () => {
const apiConfig = {
...apiConfiguration,
azureAiModelConfig: azureAiModelInfoSaneDefaults,
}
setApiConfiguration(apiConfig)
onUpdateApiConfig(apiConfig)
},
},
]}>
<div <div
style={{ style={{
backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)", padding: 15,
padding: "12px", backgroundColor: "var(--vscode-editor-background)",
borderRadius: "4px",
marginTop: "8px",
}}> }}>
<span <p
style={{ style={{
fontSize: "11px", fontSize: "12px",
fontWeight: 500, color: "var(--vscode-descriptionForeground)",
color: "var(--vscode-editor-foreground)", margin: "0 0 15px 0",
display: "block", lineHeight: "1.4",
marginBottom: "10px",
}}> }}>
Model Features Configure capabilities for your deployed model.
</span> </p>
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}> <div
<VSCodeTextField style={{
value={ backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
apiConfiguration?.azureAiModelConfig?.contextWindow?.toString() || padding: "12px",
azureAiModelInfoSaneDefaults.contextWindow?.toString() || borderRadius: "4px",
"" marginTop: "8px",
} }}>
type="text" <span
style={{ width: "100%" }} style={{
onChange={(e: any) => { fontSize: "11px",
const parsed = parseInt(e.target.value) fontWeight: 500,
handleInputChange("azureAiModelConfig")({ color: "var(--vscode-editor-foreground)",
target: { display: "block",
value: { marginBottom: "10px",
}}>
Model Features
</span>
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<VSCodeTextField
value={
apiConfiguration?.azureAiModelConfig?.contextWindow?.toString() ||
azureAiModelInfoSaneDefaults.contextWindow?.toString() ||
""
}
type="text"
style={{ width: "100%" }}
onChange={(e: any) => {
const parsed = parseInt(e.target.value)
const apiConfig = {
...apiConfiguration,
azureAiModelConfig: {
...(apiConfiguration?.azureAiModelConfig || ...(apiConfiguration?.azureAiModelConfig ||
azureAiModelInfoSaneDefaults), azureAiModelInfoSaneDefaults),
contextWindow: contextWindow:
@@ -104,43 +309,81 @@ const AzureAiModelPicker: React.FC = () => {
? azureAiModelInfoSaneDefaults.contextWindow ? azureAiModelInfoSaneDefaults.contextWindow
: parsed, : parsed,
}, },
}, }
}) setApiConfiguration(apiConfig)
}} onUpdateApiConfig(apiConfig)
placeholder="e.g. 128000"> }}
<span style={{ fontWeight: 500 }}>Context Window Size</span> placeholder="e.g. 128000">
</VSCodeTextField> <span style={{ fontWeight: 500 }}>Context Window Size</span>
<p </VSCodeTextField>
style={{ <p
fontSize: "11px", style={{
color: "var(--vscode-descriptionForeground)", fontSize: "11px",
marginTop: "4px", color: "var(--vscode-descriptionForeground)",
}}> marginTop: "4px",
Total tokens the model can process in a single request. }}>
</p> Total tokens the model can process in a single request.
</p>
</div>
</div> </div>
</div> </div>
</div> </Pane>
</Pane>
<p <p
style={{ style={{
fontSize: "12px", fontSize: "12px",
marginTop: "5px", marginTop: "5px",
color: "var(--vscode-descriptionForeground)", color: "var(--vscode-descriptionForeground)",
}}> }}>
Configure your Azure AI Model Inference endpoint and model deployment. API keys are stored locally. Configure your Azure AI Model Inference endpoint and model deployment. API keys are stored locally.
{!apiConfiguration?.azureAiKey && ( {!apiConfiguration?.azureAiKey && (
<VSCodeLink <VSCodeLink
href="https://learn.microsoft.com/azure/ai-foundry/model-inference/reference/reference-model-inference-chat-completions" href="https://learn.microsoft.com/azure/ai-foundry/model-inference/reference/reference-model-inference-chat-completions"
style={{ display: "inline", fontSize: "inherit" }}> style={{ display: "inline", fontSize: "inherit" }}>
{" "} {" "}
Learn more about Azure AI Model Inference. Learn more about Azure AI Model Inference.
</VSCodeLink> </VSCodeLink>
)} )}
</p> </p>
</div> </div>
</>
) )
} }
export default memo(AzureAiModelPicker) export default memo(AzureAiModelPicker)
// Dropdown
const DropdownWrapper = styled.div`
position: relative;
width: 100%;
`
export const AZURE_MODEL_PICKER_Z_INDEX = 1_000
const DropdownList = styled.div`
position: absolute;
top: calc(100% - 3px);
left: 0;
width: calc(100% - 2px);
max-height: 200px;
overflow-y: auto;
background-color: var(--vscode-dropdown-background);
border: 1px solid var(--vscode-list-activeSelectionBackground);
z-index: ${AZURE_MODEL_PICKER_Z_INDEX - 1};
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
`
const DropdownItem = styled.div<{ isSelected: boolean }>`
padding: 5px 10px;
cursor: pointer;
word-break: break-all;
white-space: normal;
background-color: ${({ isSelected }) => (isSelected ? "var(--vscode-list-activeSelectionBackground)" : "inherit")};
&:hover {
background-color: var(--vscode-list-activeSelectionBackground);
}
`

View File

@@ -25,6 +25,7 @@ export interface ExtensionStateContextType extends ExtensionState {
glamaModels: Record<string, ModelInfo> glamaModels: Record<string, ModelInfo>
openRouterModels: Record<string, ModelInfo> openRouterModels: Record<string, ModelInfo>
openAiModels: string[] openAiModels: string[]
azureAiModels: string[]
mcpServers: McpServer[] mcpServers: McpServer[]
filePaths: string[] filePaths: string[]
openedTabs: Array<{ label: string; isActive: boolean; path?: string }> openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
@@ -123,6 +124,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
}) })
const [openAiModels, setOpenAiModels] = useState<string[]>([]) const [openAiModels, setOpenAiModels] = useState<string[]>([])
const [azureAiModels, setAzureAiModels] = useState<string[]>([])
const [mcpServers, setMcpServers] = useState<McpServer[]>([]) const [mcpServers, setMcpServers] = useState<McpServer[]>([])
const setListApiConfigMeta = useCallback( const setListApiConfigMeta = useCallback(
@@ -247,6 +249,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
glamaModels, glamaModels,
openRouterModels, openRouterModels,
openAiModels, openAiModels,
azureAiModels,
mcpServers, mcpServers,
filePaths, filePaths,
openedTabs, openedTabs,