mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
add IBM cloud pak for data support for embedded watsonx models for code indexing..
This commit is contained in:
parent
0e44af6836
commit
a190c73cdf
9 changed files with 583 additions and 108 deletions
|
|
@ -4,7 +4,7 @@ export type WatsonxAIModelId = keyof typeof watsonxAiModels
|
|||
export const watsonxAiDefaultModelId: WatsonxAIModelId = "ibm/granite-3-3-8b-instruct"
|
||||
|
||||
// Common model properties
|
||||
const baseModelInfo: ModelInfo = {
|
||||
export const baseModelInfo: ModelInfo = {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export const getModels = async (options: GetModelsOptions): Promise<ModelRecord>
|
|||
models = await getVercelAiGatewayModels()
|
||||
break
|
||||
case "watsonx":
|
||||
models = await getWatsonxModels(options.apiKey)
|
||||
models = await getWatsonxModels(options.apiKey, false)
|
||||
break
|
||||
default: {
|
||||
// Ensures router is exhaustively checked if RouterName is a strict union
|
||||
|
|
|
|||
|
|
@ -1,28 +1,65 @@
|
|||
import { ModelInfo } from "@roo-code/types"
|
||||
import { IamAuthenticator } from "ibm-cloud-sdk-core"
|
||||
import { IamAuthenticator, CloudPakForDataAuthenticator } from "ibm-cloud-sdk-core"
|
||||
import { WatsonXAI } from "@ibm-cloud/watsonx-ai"
|
||||
|
||||
/**
|
||||
* Fetches available watsonx models
|
||||
*
|
||||
* @param apiKey - The watsonx API key
|
||||
* @param apiKey - The watsonx API key (for IBM Cloud or Cloud Pak with API key auth)
|
||||
* @param embedded - Whether to fetch embedding models (true) or LLM models (false)
|
||||
* @param projectId - Optional project ID for watsonx
|
||||
* @param baseUrl - Optional base URL for the watsonx API
|
||||
* @param platform - Optional platform type (ibmCloud or cloudPak)
|
||||
* @param username - Optional username for Cloud Pak for Data
|
||||
* @param password - Optional password for Cloud Pak for Data (when using password auth)
|
||||
* @returns A promise resolving to an object with model IDs as keys and model info as values
|
||||
*/
|
||||
export async function getWatsonxModels(
|
||||
apiKey: string,
|
||||
embedded: boolean,
|
||||
projectId?: string,
|
||||
baseUrl?: string,
|
||||
platform: "ibmCloud" | "cloudPak" = "ibmCloud",
|
||||
username?: string,
|
||||
password?: string,
|
||||
): Promise<Record<string, ModelInfo>> {
|
||||
try {
|
||||
const service = WatsonXAI.newInstance({
|
||||
let options: any = {
|
||||
version: "2024-05-31",
|
||||
serviceUrl: baseUrl || "https://us-south.ml.cloud.ibm.com",
|
||||
authenticator: new IamAuthenticator({
|
||||
}
|
||||
|
||||
if (platform === "ibmCloud" || !platform) {
|
||||
options.authenticator = new IamAuthenticator({
|
||||
apikey: apiKey,
|
||||
}),
|
||||
})
|
||||
})
|
||||
} else if (platform === "cloudPak") {
|
||||
if (!baseUrl) {
|
||||
throw new Error("Base URL is required for IBM Cloud Pak for Data")
|
||||
}
|
||||
|
||||
if (username) {
|
||||
if (password) {
|
||||
options.authenticator = new CloudPakForDataAuthenticator({
|
||||
url: baseUrl,
|
||||
username: username,
|
||||
password: password,
|
||||
})
|
||||
} else if (apiKey) {
|
||||
options.authenticator = new CloudPakForDataAuthenticator({
|
||||
url: baseUrl,
|
||||
username: username,
|
||||
apikey: apiKey,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
options.authenticator = new IamAuthenticator({
|
||||
apikey: apiKey,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const service = WatsonXAI.newInstance(options)
|
||||
|
||||
let knownModels: Record<string, ModelInfo> = {}
|
||||
|
||||
|
|
@ -36,7 +73,7 @@ export async function getWatsonxModels(
|
|||
for (const model of modelsList) {
|
||||
const modelId = model.id || model.name || model.model_id
|
||||
const modelInfo = JSON.stringify(model).toLowerCase()
|
||||
if (modelId && !modelInfo.includes("embed") && !modelInfo.includes("rtrvr")) {
|
||||
if (modelId && !embedded && !modelInfo.includes("embed") && !modelInfo.includes("rtrvr")) {
|
||||
const contextWindow = model.context_length || model.max_input_tokens || 8192
|
||||
const maxTokens = model.max_output_tokens || Math.floor(contextWindow / 2)
|
||||
|
||||
|
|
@ -45,17 +82,28 @@ export async function getWatsonxModels(
|
|||
maxTokens,
|
||||
supportsPromptCache: false,
|
||||
}
|
||||
} else {
|
||||
if (modelId && embedded && modelInfo.includes("embed") && modelInfo.includes("rtrvr")) {
|
||||
const contextWindow = model.context_length || model.max_input_tokens || 8192
|
||||
const maxTokens = model.max_output_tokens || Math.floor(contextWindow / 2)
|
||||
|
||||
knownModels[modelId] = {
|
||||
contextWindow,
|
||||
maxTokens,
|
||||
supportsPromptCache: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.warn("Error fetching models from IBM watsonx API:", apiError)
|
||||
} catch (error) {
|
||||
console.warn("Error fetching models from IBM watsonx API:", error)
|
||||
return {}
|
||||
}
|
||||
|
||||
return knownModels
|
||||
} catch (error) {
|
||||
console.error("Error fetching IBM watsonx models:", error)
|
||||
} catch (apiError) {
|
||||
console.error("Error fetching IBM watsonx models:", apiError)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
type ClineMessage,
|
||||
type TelemetrySetting,
|
||||
TelemetryEventName,
|
||||
UserSettingsConfig,
|
||||
UserSettingsConfig
|
||||
} from "@roo-code/types"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
|
@ -964,21 +964,58 @@ export const webviewMessageHandler = async (
|
|||
provider.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
|
||||
break
|
||||
case "requestWatsonxModels":
|
||||
if (message?.values?.apiKey) {
|
||||
if (message?.values) {
|
||||
try {
|
||||
const watsonxModels = await getWatsonxModels(message.values.apiKey, message.values.projectId)
|
||||
const formattedModels: Record<string, { dimension: number }> = {}
|
||||
Object.entries(watsonxModels).forEach(([modelId]) => {
|
||||
formattedModels[modelId] = {
|
||||
dimension: 1536,
|
||||
const {
|
||||
apiKey,
|
||||
projectId,
|
||||
platform = "ibmCloud",
|
||||
baseUrl,
|
||||
username,
|
||||
authType = "apiKey",
|
||||
password,
|
||||
region,
|
||||
} = message.values
|
||||
|
||||
if (!apiKey && !(username && (authType === "password" ? password : apiKey))) {
|
||||
console.error("Missing authentication credentials for IBM watsonx models")
|
||||
provider.postMessageToWebview({
|
||||
type: "watsonxModels",
|
||||
watsonxModels: {},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let effectiveBaseUrl = baseUrl
|
||||
if (platform === "ibmCloud" && region && !baseUrl) {
|
||||
const regionToUrl: Record<string, string> = {
|
||||
"us-south": "https://us-south.ml.cloud.ibm.com",
|
||||
"eu-de": "https://eu-de.ml.cloud.ibm.com",
|
||||
"eu-gb": "https://eu-gb.ml.cloud.ibm.com",
|
||||
"jp-tok": "https://jp-tok.ml.cloud.ibm.com",
|
||||
"au-syd": "https://au-syd.ml.cloud.ibm.com",
|
||||
"ca-tor": "https://ca-tor.ml.cloud.ibm.com",
|
||||
"ap-south-1": "https://ap-south-1.aws.wxai.ibm.com",
|
||||
}
|
||||
})
|
||||
effectiveBaseUrl = regionToUrl[region] || "https://us-south.ml.cloud.ibm.com"
|
||||
}
|
||||
|
||||
const watsonxModels = await getWatsonxModels(
|
||||
apiKey,
|
||||
false,
|
||||
projectId,
|
||||
effectiveBaseUrl,
|
||||
platform,
|
||||
username,
|
||||
authType === "password" ? password : undefined,
|
||||
)
|
||||
|
||||
provider.postMessageToWebview({
|
||||
type: "watsonxModels",
|
||||
watsonxModels: formattedModels,
|
||||
watsonxModels: watsonxModels,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch watsonx models:", error)
|
||||
console.error("Failed to fetch IBM watsonx models:", error)
|
||||
provider.postMessageToWebview({
|
||||
type: "watsonxModels",
|
||||
watsonxModels: {},
|
||||
|
|
@ -986,6 +1023,66 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
}
|
||||
break
|
||||
case "requestEmbeddedWatsonxModels":
|
||||
if (message?.values) {
|
||||
try {
|
||||
const {
|
||||
apiKey,
|
||||
projectId,
|
||||
platform = "ibmCloud",
|
||||
baseUrl,
|
||||
username,
|
||||
authType = "apiKey",
|
||||
password,
|
||||
region,
|
||||
} = message.values
|
||||
|
||||
if (!apiKey && !(username && (authType === "password" ? password : apiKey))) {
|
||||
console.error("Missing authentication credentials for IBM watsonx embedded models")
|
||||
provider.postMessageToWebview({
|
||||
type: "embeddedWatsonxModels",
|
||||
embeddedWatsonxModels: {},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let effectiveBaseUrl = baseUrl
|
||||
if (platform === "ibmCloud" && region && !baseUrl) {
|
||||
const regionToUrl: Record<string, string> = {
|
||||
"us-south": "https://us-south.ml.cloud.ibm.com",
|
||||
"eu-de": "https://eu-de.ml.cloud.ibm.com",
|
||||
"eu-gb": "https://eu-gb.ml.cloud.ibm.com",
|
||||
"jp-tok": "https://jp-tok.ml.cloud.ibm.com",
|
||||
"au-syd": "https://au-syd.ml.cloud.ibm.com",
|
||||
"ca-tor": "https://ca-tor.ml.cloud.ibm.com",
|
||||
"ap-south-1": "https://ap-south-1.aws.wxai.ibm.com",
|
||||
}
|
||||
effectiveBaseUrl = regionToUrl[region] || "https://us-south.ml.cloud.ibm.com"
|
||||
}
|
||||
|
||||
const watsonxModels = await getWatsonxModels(
|
||||
apiKey,
|
||||
true,
|
||||
projectId,
|
||||
effectiveBaseUrl,
|
||||
platform as "ibmCloud" | "cloudPak",
|
||||
username,
|
||||
authType === "password" ? password : undefined,
|
||||
)
|
||||
|
||||
provider.postMessageToWebview({
|
||||
type: "embeddedWatsonxModels",
|
||||
embeddedWatsonxModels: watsonxModels,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch IBM watsonx embedded models:", error)
|
||||
provider.postMessageToWebview({
|
||||
type: "embeddedWatsonxModels",
|
||||
embeddedWatsonxModels: {},
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
case "requestHuggingFaceModels":
|
||||
try {
|
||||
const { getHuggingFaceModelsWithMetadata } = await import("../../api/providers/fetchers/huggingface")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { t } from "../../../i18n"
|
|||
import { TelemetryEventName } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { WatsonXAI } from "@ibm-cloud/watsonx-ai"
|
||||
import { IamAuthenticator } from "ibm-cloud-sdk-core"
|
||||
import { IamAuthenticator, CloudPakForDataAuthenticator } from "ibm-cloud-sdk-core"
|
||||
|
||||
/**
|
||||
* IBM watsonx embedder implementation using the native IBM Cloud watsonx.ai package.
|
||||
|
|
@ -25,21 +25,59 @@ export class WatsonxEmbedder implements IEmbedder {
|
|||
* @param apiKey The watsonx API key for authentication
|
||||
* @param modelId The model ID to use (defaults to ibm/slate-125m-english-rtrvr-v2)
|
||||
* @param projectId Optional IBM Cloud project ID for watsonx
|
||||
* @param proxyUrl Optional proxy URL for connecting through MCP servers
|
||||
* @param platform Optional platform type (ibmCloud or cloudPak)
|
||||
* @param baseUrl Optional base URL for the service (required for cloudPak)
|
||||
* @param region Optional region for IBM Cloud (defaults to us-south)
|
||||
* @param username Optional username for Cloud Pak for Data
|
||||
* @param password Optional password for Cloud Pak for Data
|
||||
*/
|
||||
constructor(apiKey: string, modelId?: string, projectId?: string) {
|
||||
if (!apiKey) {
|
||||
constructor(
|
||||
apiKey: string,
|
||||
modelId?: string,
|
||||
projectId?: string,
|
||||
platform: "ibmCloud" | "cloudPak" = "ibmCloud",
|
||||
baseUrl?: string,
|
||||
region: string = "us-south",
|
||||
username?: string,
|
||||
password?: string,
|
||||
) {
|
||||
if (!apiKey && !(username && password)) {
|
||||
throw new Error(t("embeddings:validation.apiKeyRequired"))
|
||||
}
|
||||
this.modelId = modelId || WatsonxEmbedder.DEFAULT_MODEL
|
||||
this.projectId = projectId
|
||||
|
||||
const options: any = {
|
||||
let options: any = {
|
||||
version: WatsonxEmbedder.WATSONX_VERSION,
|
||||
authenticator: new IamAuthenticator({
|
||||
}
|
||||
|
||||
if (platform === "ibmCloud") {
|
||||
options.authenticator = new IamAuthenticator({
|
||||
apikey: apiKey,
|
||||
}),
|
||||
serviceUrl: `https://${WatsonxEmbedder.WATSONX_REGION}.ml.cloud.ibm.com`,
|
||||
})
|
||||
options.serviceUrl = baseUrl || `https://${region}.ml.cloud.ibm.com`
|
||||
} else if (platform === "cloudPak") {
|
||||
if (!baseUrl) {
|
||||
throw new Error("Base URL is required for IBM Cloud Pak for Data")
|
||||
}
|
||||
|
||||
if (username) {
|
||||
if (password) {
|
||||
options.authenticator = new CloudPakForDataAuthenticator({
|
||||
url: baseUrl,
|
||||
username: username,
|
||||
password: password,
|
||||
})
|
||||
} else if (apiKey) {
|
||||
options.authenticator = new CloudPakForDataAuthenticator({
|
||||
url: baseUrl,
|
||||
username: username,
|
||||
apikey: apiKey,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
options.serviceUrl = baseUrl
|
||||
}
|
||||
|
||||
this.watsonxClient = new WatsonXAI(options)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ import type {
|
|||
ClineMessage,
|
||||
MarketplaceItem,
|
||||
TodoItem,
|
||||
CloudUserInfo,
|
||||
OrganizationAllowList,
|
||||
ShareVisibility,
|
||||
QueuedMessage,
|
||||
ModelInfo,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { GitCommit } from "../utils/git"
|
||||
|
|
@ -80,6 +77,7 @@ export interface ExtensionMessage {
|
|||
| "lmStudioModels"
|
||||
| "vsCodeLmModels"
|
||||
| "watsonxModels"
|
||||
| "embeddedWatsonxModels"
|
||||
| "huggingFaceModels"
|
||||
| "vsCodeLmApiAvailable"
|
||||
| "updatePrompt"
|
||||
|
|
@ -153,7 +151,8 @@ export interface ExtensionMessage {
|
|||
ollamaModels?: ModelRecord
|
||||
lmStudioModels?: ModelRecord
|
||||
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
|
||||
watsonxModels?: Record<string, { dimension: number }>
|
||||
watsonxModels?: Record<string, ModelInfo>
|
||||
embeddedWatsonxModels?: Record<string, ModelInfo>
|
||||
huggingFaceModels?: Array<{
|
||||
id: string
|
||||
object: string
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export interface WebviewMessage {
|
|||
| "requestLmStudioModels"
|
||||
| "requestVsCodeLmModels"
|
||||
| "requestWatsonxModels"
|
||||
| "requestEmbeddedWatsonxModels"
|
||||
| "requestHuggingFaceModels"
|
||||
| "openImage"
|
||||
| "saveImage"
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@ interface LocalCodeIndexSettings {
|
|||
codebaseIndexVercelAiGatewayApiKey?: string
|
||||
codebaseIndexWatsonxApiKey?: string
|
||||
codebaseIndexWatsonxProjectId?: string
|
||||
watsonxPlatform?: "ibmCloud" | "cloudPak"
|
||||
watsonxBaseUrl?: string
|
||||
watsonxRegion?: string
|
||||
watsonxUsername?: string
|
||||
watsonxPassword?: string
|
||||
watsonxAuthType?: "apiKey" | "password"
|
||||
}
|
||||
|
||||
// Validation schema for codebase index settings
|
||||
|
|
@ -158,6 +164,12 @@ const createValidationSchema = (provider: EmbedderProvider, t: any) => {
|
|||
codebaseIndexEmbedderModelId: z
|
||||
.string()
|
||||
.min(1, t("settings:codeIndex.validation.modelSelectionRequired")),
|
||||
watsonxPlatform: z.enum(["ibmCloud", "cloudPak"]).optional().default("ibmCloud"),
|
||||
watsonxRegion: z.string().optional(),
|
||||
watsonxBaseUrl: z.string().optional(),
|
||||
watsonxUsername: z.string().optional(),
|
||||
watsonxPassword: z.string().optional(),
|
||||
watsonxAuthType: z.enum(["apiKey", "password"]).optional().default("apiKey"),
|
||||
})
|
||||
|
||||
default:
|
||||
|
|
@ -180,6 +192,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
|
||||
const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle")
|
||||
const [saveError, setSaveError] = useState<string | null>(null)
|
||||
const [refreshingModels, setRefreshingModels] = useState(false)
|
||||
|
||||
// Form validation state
|
||||
const [formErrors, setFormErrors] = useState<Record<string, string>>({})
|
||||
|
|
@ -207,6 +220,12 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
codebaseIndexVercelAiGatewayApiKey: "",
|
||||
codebaseIndexWatsonxApiKey: "",
|
||||
codebaseIndexWatsonxProjectId: "",
|
||||
watsonxPlatform: "ibmCloud",
|
||||
watsonxBaseUrl: "https://us-south.ml.cloud.ibm.com",
|
||||
watsonxRegion: "us-south",
|
||||
watsonxUsername: "",
|
||||
watsonxPassword: "",
|
||||
watsonxAuthType: "apiKey",
|
||||
})
|
||||
|
||||
// Initial settings state - stores the settings when popover opens
|
||||
|
|
@ -244,6 +263,12 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
codebaseIndexVercelAiGatewayApiKey: "",
|
||||
codebaseIndexWatsonxApiKey: "",
|
||||
codebaseIndexWatsonxProjectId: "",
|
||||
watsonxPlatform: "ibmCloud" as "ibmCloud" | "cloudPak",
|
||||
watsonxBaseUrl: "https://us-south.ml.cloud.ibm.com",
|
||||
watsonxRegion: "us-south",
|
||||
watsonxUsername: "",
|
||||
watsonxPassword: "",
|
||||
watsonxAuthType: "apiKey" as "apiKey" | "password",
|
||||
}
|
||||
setInitialSettings(settings)
|
||||
setCurrentSettings(settings)
|
||||
|
|
@ -273,27 +298,6 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
return () => window.removeEventListener("message", handleMessage)
|
||||
}, [open])
|
||||
|
||||
// Request WatsonX models when provider is selected and API key is available
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentSettings.codebaseIndexEmbedderProvider === "watsonx" &&
|
||||
currentSettings.codebaseIndexWatsonxApiKey &&
|
||||
currentSettings.codebaseIndexWatsonxApiKey !== SECRET_PLACEHOLDER
|
||||
) {
|
||||
vscode.postMessage({
|
||||
type: "requestWatsonxModels",
|
||||
values: {
|
||||
apiKey: currentSettings.codebaseIndexWatsonxApiKey,
|
||||
projectId: currentSettings.codebaseIndexWatsonxProjectId,
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [
|
||||
currentSettings.codebaseIndexEmbedderProvider,
|
||||
currentSettings.codebaseIndexWatsonxApiKey,
|
||||
currentSettings.codebaseIndexWatsonxProjectId,
|
||||
])
|
||||
|
||||
// Use a ref to capture current settings for the save handler
|
||||
const currentSettingsRef = useRef(currentSettings)
|
||||
currentSettingsRef.current = currentSettings
|
||||
|
|
@ -333,16 +337,47 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
setSaveStatus("idle")
|
||||
setSaveError(null)
|
||||
}
|
||||
} else if (event.data.type === "watsonxModels" && event.data.watsonxModels) {
|
||||
// Update the extension state context with the watsonx models
|
||||
// The models will be automatically available through the codebaseIndexModels context
|
||||
console.log("Received WatsonX models:", event.data.watsonxModels)
|
||||
} else if (event.data.type === "embeddedWatsonxModels" && event.data.embeddedWatsonxModels) {
|
||||
try {
|
||||
console.log("Received IBM Embeded watsonx models:", event.data.embeddedWatsonxModels)
|
||||
const embeddedWatsonxModels: Record<string, { dimension: number }> = {}
|
||||
if (
|
||||
!event.data.embeddedWatsonxModels ||
|
||||
Object.keys(event.data.embeddedWatsonxModels).length === 0
|
||||
) {
|
||||
console.warn("No models received from server, adding default model")
|
||||
embeddedWatsonxModels["ibm/slate-125m-english-rtrvr-v2"] = { dimension: 1536 }
|
||||
} else {
|
||||
Object.keys(event.data.embeddedWatsonxModels).forEach((modelId) => {
|
||||
embeddedWatsonxModels[modelId] = {
|
||||
dimension: 1536,
|
||||
}
|
||||
})
|
||||
}
|
||||
if (codebaseIndexModels) {
|
||||
codebaseIndexModels.watsonx = { ...embeddedWatsonxModels }
|
||||
console.log("Updated watsonx models in context:", codebaseIndexModels.watsonx)
|
||||
}
|
||||
setCurrentSettings((prev) => ({ ...prev }))
|
||||
} catch (error) {
|
||||
console.error("Error processing watsonx models:", error)
|
||||
if (codebaseIndexModels) {
|
||||
codebaseIndexModels.watsonx = {
|
||||
"ibm/slate-125m-english-rtrvr-v2": { dimension: 1536 },
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setRefreshingModels(false)
|
||||
}
|
||||
} else if (event.data.type === "embeddedWatsonxModelsError") {
|
||||
console.error("Error fetching watsonx models:", event.data.error)
|
||||
setRefreshingModels(false)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage)
|
||||
return () => window.removeEventListener("message", handleMessage)
|
||||
}, [t, cwd])
|
||||
}, [t, cwd, codebaseIndexModels, currentSettings.codebaseIndexEmbedderProvider])
|
||||
|
||||
// Listen for secret status
|
||||
useEffect(() => {
|
||||
|
|
@ -758,7 +793,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-2 mt-4">
|
||||
<label className="text-sm font-medium">
|
||||
{t("settings:codeIndex.modelLabel")}
|
||||
</label>
|
||||
|
|
@ -1123,28 +1158,211 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
|
||||
{currentSettings.codebaseIndexEmbedderProvider === "watsonx" && (
|
||||
<>
|
||||
{/* IBM watsonx Platform Selection */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{t("settings:codeIndex.watsonxApiKeyLabel")}
|
||||
</label>
|
||||
<VSCodeTextField
|
||||
type="password"
|
||||
value={currentSettings.codebaseIndexWatsonxApiKey || ""}
|
||||
onInput={(e: any) =>
|
||||
updateSetting("codebaseIndexWatsonxApiKey", e.target.value)
|
||||
}
|
||||
placeholder={t("settings:codeIndex.watsonxApiKeyPlaceholder")}
|
||||
className={cn("w-full", {
|
||||
"border-red-500": formErrors.watsonxApiKey,
|
||||
})}
|
||||
/>
|
||||
{formErrors.watsonxApiKey && (
|
||||
<p className="text-xs text-vscode-errorForeground mt-1 mb-0">
|
||||
{formErrors.watsonxApiKey}
|
||||
</p>
|
||||
)}
|
||||
<label className="text-sm font-medium">IBM watsonx Platform</label>
|
||||
<Select
|
||||
value={currentSettings.watsonxPlatform || "ibmCloud"}
|
||||
onValueChange={(value) => {
|
||||
updateSetting("watsonxPlatform", value)
|
||||
if (value === "ibmCloud") {
|
||||
// Set IBM Cloud defaults
|
||||
updateSetting("watsonxRegion", "us-south")
|
||||
updateSetting(
|
||||
"watsonxBaseUrl",
|
||||
"https://us-south.ml.cloud.ibm.com",
|
||||
)
|
||||
updateSetting("watsonxUsername", "")
|
||||
updateSetting("watsonxPassword", "")
|
||||
updateSetting("watsonxAuthType", "apiKey")
|
||||
} else {
|
||||
// Set Cloud Pak for Data defaults
|
||||
updateSetting("watsonxRegion", "")
|
||||
updateSetting("watsonxBaseUrl", "")
|
||||
updateSetting("watsonxAuthType", "apiKey")
|
||||
}
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ibmCloud">IBM Cloud</SelectItem>
|
||||
<SelectItem value="cloudPak">IBM Cloud Pak for Data</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* IBM Cloud specific fields */}
|
||||
{(!currentSettings.watsonxPlatform ||
|
||||
currentSettings.watsonxPlatform === "ibmCloud") && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{t("settings:codeIndex.watsonxApiKeyLabel")}
|
||||
</label>
|
||||
<VSCodeTextField
|
||||
type="password"
|
||||
value={currentSettings.codebaseIndexWatsonxApiKey || ""}
|
||||
onInput={(e: any) =>
|
||||
updateSetting(
|
||||
"codebaseIndexWatsonxApiKey",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
placeholder={t(
|
||||
"settings:codeIndex.watsonxApiKeyPlaceholder",
|
||||
)}
|
||||
className={cn("w-full", {
|
||||
"border-red-500": formErrors.watsonxApiKey,
|
||||
})}
|
||||
/>
|
||||
{formErrors.watsonxApiKey && (
|
||||
<p className="text-xs text-vscode-errorForeground mt-1 mb-0">
|
||||
{formErrors.watsonxApiKey}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
IBM watsonx Region
|
||||
</label>
|
||||
<Select
|
||||
value={currentSettings.watsonxRegion || "us-south"}
|
||||
onValueChange={(region: string) => {
|
||||
updateSetting("watsonxRegion", region)
|
||||
const regionToUrl: Record<string, string> = {
|
||||
"us-south": "https://us-south.ml.cloud.ibm.com",
|
||||
"eu-de": "https://eu-de.ml.cloud.ibm.com",
|
||||
"eu-gb": "https://eu-gb.ml.cloud.ibm.com",
|
||||
"jp-tok": "https://jp-tok.ml.cloud.ibm.com",
|
||||
"au-syd": "https://au-syd.ml.cloud.ibm.com",
|
||||
"ca-tor": "https://ca-tor.ml.cloud.ibm.com",
|
||||
"ap-south-1": "https://ap-south-1.aws.wxai.ibm.com",
|
||||
}
|
||||
updateSetting(
|
||||
"watsonxBaseUrl",
|
||||
regionToUrl[region] || "",
|
||||
)
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="us-south">
|
||||
Dallas (us-south.ml.cloud.ibm.com)
|
||||
</SelectItem>
|
||||
<SelectItem value="eu-de">
|
||||
Frankfurt (eu-de.ml.cloud.ibm.com)
|
||||
</SelectItem>
|
||||
<SelectItem value="eu-gb">
|
||||
London (eu-gb.ml.cloud.ibm.com)
|
||||
</SelectItem>
|
||||
<SelectItem value="jp-tok">
|
||||
Tokyo (jp-tok.ml.cloud.ibm.com)
|
||||
</SelectItem>
|
||||
<SelectItem value="au-syd">
|
||||
Sydney (au-syd.ml.cloud.ibm.com)
|
||||
</SelectItem>
|
||||
<SelectItem value="ca-tor">
|
||||
Toronto (ca-tor.ml.cloud.ibm.com)
|
||||
</SelectItem>
|
||||
<SelectItem value="ap-south-1">
|
||||
Mumbai (ap-south-1.aws.wxai.ibm.com)
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Cloud Pak for Data specific fields */}
|
||||
{currentSettings.watsonxPlatform === "cloudPak" && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
IBM Cloud Pak for Data URL
|
||||
</label>
|
||||
<VSCodeTextField
|
||||
value={currentSettings.watsonxBaseUrl || ""}
|
||||
onInput={(e: any) =>
|
||||
updateSetting("watsonxBaseUrl", e.target.value)
|
||||
}
|
||||
placeholder="https://your-cp4d-instance.example.com"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Username</label>
|
||||
<VSCodeTextField
|
||||
value={currentSettings.watsonxUsername || ""}
|
||||
onInput={(e: any) =>
|
||||
updateSetting("watsonxUsername", e.target.value)
|
||||
}
|
||||
placeholder="Username"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
Authentication Type
|
||||
</label>
|
||||
<Select
|
||||
value={currentSettings.watsonxAuthType || "apiKey"}
|
||||
onValueChange={(authType) => {
|
||||
updateSetting("watsonxAuthType", authType)
|
||||
if (authType === "apiKey") {
|
||||
updateSetting("watsonxPassword", "")
|
||||
} else {
|
||||
updateSetting("codebaseIndexWatsonxApiKey", "")
|
||||
}
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="apiKey">API Key</SelectItem>
|
||||
<SelectItem value="password">Password</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{currentSettings.watsonxAuthType === "apiKey" ? (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">API Key</label>
|
||||
<VSCodeTextField
|
||||
type="password"
|
||||
value={currentSettings.codebaseIndexWatsonxApiKey || ""}
|
||||
onInput={(e: any) =>
|
||||
updateSetting(
|
||||
"codebaseIndexWatsonxApiKey",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
placeholder="API Key"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Password</label>
|
||||
<VSCodeTextField
|
||||
type="password"
|
||||
value={currentSettings.watsonxPassword || ""}
|
||||
onInput={(e: any) =>
|
||||
updateSetting("watsonxPassword", e.target.value)
|
||||
}
|
||||
placeholder="Password"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Common fields for both platforms */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{t("settings:codeIndex.watsonxProjectIdLabel") || "Project ID"}
|
||||
|
|
@ -1156,7 +1374,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
}
|
||||
placeholder={
|
||||
t("settings:codeIndex.watsonxProjectIdPlaceholder") ||
|
||||
"Optional IBM Cloud project ID"
|
||||
"IBM Cloud project ID"
|
||||
}
|
||||
className={cn("w-full", {
|
||||
"border-red-500": formErrors.watsonxProjectId,
|
||||
|
|
@ -1169,6 +1387,45 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Refresh Models Button for IBM watsonx */}
|
||||
<div className="space-y-2 mt-4">
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={() => {
|
||||
setRefreshingModels(true)
|
||||
vscode.postMessage({
|
||||
type: "requestEmbeddedWatsonxModels",
|
||||
values: {
|
||||
apiKey: currentSettings.codebaseIndexWatsonxApiKey,
|
||||
projectId:
|
||||
currentSettings.codebaseIndexWatsonxProjectId,
|
||||
platform: currentSettings.watsonxPlatform,
|
||||
baseUrl: currentSettings.watsonxBaseUrl,
|
||||
username: currentSettings.watsonxUsername,
|
||||
authType: currentSettings.watsonxAuthType,
|
||||
password: currentSettings.watsonxPassword,
|
||||
region: currentSettings.watsonxRegion,
|
||||
},
|
||||
})
|
||||
}}
|
||||
disabled={
|
||||
refreshingModels ||
|
||||
!currentSettings.codebaseIndexWatsonxApiKey ||
|
||||
currentSettings.codebaseIndexWatsonxApiKey ===
|
||||
SECRET_PLACEHOLDER
|
||||
}
|
||||
className="w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
{refreshingModels ? (
|
||||
<span className="codicon codicon-loading codicon-modifier-spin" />
|
||||
) : (
|
||||
<span className="codicon codicon-refresh" />
|
||||
)}
|
||||
{refreshingModels ? "Loading Models..." : "Refresh Models"}
|
||||
</div>
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{t("settings:codeIndex.modelLabel")}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import { useCallback, useState, useEffect, useRef } from "react"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import { watsonxAiDefaultModelId, type ProviderSettings } from "@roo-code/types"
|
||||
|
||||
import { ModelInfo, watsonxAiDefaultModelId, type ProviderSettings } from "@roo-code/types"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui"
|
||||
import { ExtensionMessage } from "@roo/ExtensionMessage"
|
||||
|
||||
import { inputEventTransform } from "../transforms"
|
||||
import { OrganizationAllowList } from "@roo/cloud"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { RouterName } from "@roo/api"
|
||||
import { ModelPicker } from "../ModelPicker"
|
||||
|
||||
|
|
@ -50,10 +46,11 @@ export const WatsonxAI = ({
|
|||
modelValidationError,
|
||||
}: WatsonxAIProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const { routerModels } = useExtensionState()
|
||||
const [watsonxModels, setWatsonxModels] = useState<Record<string, ModelInfo> | null>(null)
|
||||
const [refreshStatus, setRefreshStatus] = useState<"idle" | "loading" | "success" | "error">("idle")
|
||||
const [refreshError, setRefreshError] = useState<string | undefined>()
|
||||
const watsonxErrorJustReceived = useRef(false)
|
||||
const initialModelFetchAttempted = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!apiConfiguration.watsonxPlatform) {
|
||||
|
|
@ -116,17 +113,27 @@ export const WatsonxAI = ({
|
|||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent<ExtensionMessage>) => {
|
||||
const message = event.data
|
||||
console.log("Received message:", message.type, message)
|
||||
|
||||
if (message.type === "singleRouterModelFetchResponse" && !message.success) {
|
||||
const providerName = message.values?.provider as RouterName
|
||||
if (providerName === "watsonx") {
|
||||
console.log("Received error response for watsonx:", message.error)
|
||||
watsonxErrorJustReceived.current = true
|
||||
setRefreshStatus("error")
|
||||
setRefreshError(message.error)
|
||||
}
|
||||
} else if (message.type === "routerModels") {
|
||||
} else if (message.type === "watsonxModels") {
|
||||
console.log("Received watsonxModels:", message.watsonxModels)
|
||||
setWatsonxModels(message.watsonxModels ?? {})
|
||||
if (refreshStatus === "loading") {
|
||||
if (!watsonxErrorJustReceived.current) {
|
||||
console.log("Setting refresh status to success")
|
||||
setRefreshStatus("success")
|
||||
} else {
|
||||
// Reset the flag after handling the error
|
||||
console.log("Resetting error flag")
|
||||
watsonxErrorJustReceived.current = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -147,24 +154,36 @@ export const WatsonxAI = ({
|
|||
)
|
||||
|
||||
const handleRefreshModels = useCallback(() => {
|
||||
console.log("Refresh models clicked")
|
||||
setRefreshStatus("loading")
|
||||
setRefreshError(undefined)
|
||||
watsonxErrorJustReceived.current = false
|
||||
|
||||
const apiKey = apiConfiguration.watsonxApiKey
|
||||
const platform = apiConfiguration.watsonxPlatform
|
||||
const customUrl = apiConfiguration.watsonxBaseUrl || ""
|
||||
const username = apiConfiguration.watsonxUsername
|
||||
const authType = apiConfiguration.watsonxAuthType
|
||||
const password = apiConfiguration.watsonxPassword
|
||||
const projectId = apiConfiguration.watsonxProjectId
|
||||
|
||||
let baseUrl = ""
|
||||
if (platform === "ibmCloud") {
|
||||
baseUrl = REGION_TO_URL[selectedRegion as keyof typeof REGION_TO_URL]
|
||||
} else {
|
||||
baseUrl = customUrl
|
||||
setApiConfigurationField("watsonxBaseUrl", baseUrl)
|
||||
baseUrl = apiConfiguration.watsonxBaseUrl || ""
|
||||
}
|
||||
|
||||
console.log("Refresh models config:", {
|
||||
platform,
|
||||
baseUrl,
|
||||
hasApiKey: !!apiKey,
|
||||
hasUsername: !!username,
|
||||
authType,
|
||||
hasPassword: !!password,
|
||||
hasProjectId: !!projectId,
|
||||
selectedRegion,
|
||||
})
|
||||
|
||||
if (platform === "ibmCloud" && (!apiKey || !baseUrl)) {
|
||||
setRefreshStatus("error")
|
||||
setRefreshError(t("settings:providers.refreshModels.missingConfig"))
|
||||
|
|
@ -197,21 +216,33 @@ export const WatsonxAI = ({
|
|||
}
|
||||
}
|
||||
|
||||
console.log("Sending requestWatsonxModels message")
|
||||
vscode.postMessage({
|
||||
type: "requestRouterModels",
|
||||
type: "requestWatsonxModels",
|
||||
values: {
|
||||
watsonxPlatform: apiConfiguration.watsonxPlatform,
|
||||
watsonxBaseUrl: apiConfiguration.watsonxBaseUrl,
|
||||
watsonxApiKey: apiConfiguration.watsonxApiKey,
|
||||
watsonxProjectId: apiConfiguration.watsonxProjectId,
|
||||
watsonxModelId: apiConfiguration.watsonxModelId,
|
||||
watsonxUsername: apiConfiguration.watsonxUsername,
|
||||
watsonxAuthType: apiConfiguration.watsonxAuthType,
|
||||
watsonxPassword: apiConfiguration.watsonxPassword,
|
||||
watsonxRegion: apiConfiguration.watsonxRegion,
|
||||
apiKey: apiKey,
|
||||
projectId: projectId,
|
||||
platform: platform,
|
||||
baseUrl: baseUrl,
|
||||
username: username,
|
||||
authType: authType,
|
||||
password: password,
|
||||
region: selectedRegion,
|
||||
},
|
||||
})
|
||||
}, [apiConfiguration, setRefreshStatus, setRefreshError, t, selectedRegion, setApiConfigurationField])
|
||||
}, [apiConfiguration, setRefreshStatus, setRefreshError, t, selectedRegion])
|
||||
|
||||
// Refresh models when component mounts if API key is available
|
||||
useEffect(() => {
|
||||
if (
|
||||
!initialModelFetchAttempted.current &&
|
||||
apiConfiguration.watsonxApiKey &&
|
||||
(!watsonxModels || Object.keys(watsonxModels).length === 0)
|
||||
) {
|
||||
initialModelFetchAttempted.current = true
|
||||
handleRefreshModels()
|
||||
}
|
||||
}, [apiConfiguration.watsonxApiKey, watsonxModels, handleRefreshModels])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -348,7 +379,10 @@ export const WatsonxAI = ({
|
|||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleRefreshModels}
|
||||
onClick={() => {
|
||||
console.log("Refresh button clicked")
|
||||
handleRefreshModels()
|
||||
}}
|
||||
disabled={
|
||||
refreshStatus === "loading" ||
|
||||
(apiConfiguration.watsonxPlatform === "ibmCloud" && !apiConfiguration.watsonxApiKey) ||
|
||||
|
|
@ -358,7 +392,8 @@ export const WatsonxAI = ({
|
|||
(apiConfiguration.watsonxAuthType === "apiKey" && !apiConfiguration.watsonxApiKey) ||
|
||||
(apiConfiguration.watsonxAuthType === "password" && !apiConfiguration.watsonxPassword)))
|
||||
}
|
||||
className="w-full mt-4">
|
||||
className="w-full mt-4"
|
||||
title={t("settings:providers.refreshModels.tooltip") || "Refresh available models"}>
|
||||
<div className="flex items-center gap-2">
|
||||
{refreshStatus === "loading" ? (
|
||||
<span className="codicon codicon-loading codicon-modifier-spin" />
|
||||
|
|
@ -387,7 +422,7 @@ export const WatsonxAI = ({
|
|||
<ModelPicker
|
||||
apiConfiguration={apiConfiguration}
|
||||
defaultModelId={watsonxAiDefaultModelId}
|
||||
models={routerModels?.watsonx ?? {}}
|
||||
models={watsonxModels && Object.keys(watsonxModels).length > 0 ? watsonxModels : {}}
|
||||
modelIdKey="watsonxModelId"
|
||||
serviceName="IBM watsonx"
|
||||
serviceUrl="https://cloud.ibm.com/apidocs/watsonx-ai#list-foundation-model-specs"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue