Skip to content
2 changes: 2 additions & 0 deletions proto/cline/models.proto
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ message ModelsApiConfiguration {
optional OpenRouterModelInfo plan_mode_vercel_ai_gateway_model_info = 130;
optional string plan_mode_oca_model_id = 131;
optional OcaModelInfo plan_mode_oca_model_info = 132;
repeated string plan_mode_oca_vector_ids = 133;


// Act mode configurations
Expand Down Expand Up @@ -395,4 +396,5 @@ message ModelsApiConfiguration {
optional OpenRouterModelInfo act_mode_vercel_ai_gateway_model_info = 230;
optional string act_mode_oca_model_id = 231;
optional OcaModelInfo act_mode_oca_model_info = 232;
repeated string act_mode_oca_vector_ids = 233;
}
23 changes: 23 additions & 0 deletions proto/cline/vectors.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
syntax = "proto3";

package cline;
import "cline/common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;

// Service for vector-related operations
service VectorsService {
// Fetches available vectors from OCA
rpc refreshOcaVectors(StringRequest) returns (VectorStores);
}

message VectorStoreInfo {
string id = 1;
string name = 2;
string description = 3;
}

message VectorStores {
map<string, VectorStoreInfo> vectors = 1;
optional string error = 2;
}
1 change: 1 addition & 0 deletions src/core/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ function createHandlerForProvider(
? options.planModeOcaModelInfo?.supportsPromptCache
: options.actModeOcaModelInfo?.supportsPromptCache,
taskId: options.ulid,
vectorIds: mode === "plan" ? options.planModeOcaVectorIds : options.actModeOcaVectorIds,
})
default:
return new AnthropicHandler({
Expand Down
26 changes: 24 additions & 2 deletions src/core/api/providers/oca.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface OcaHandlerOptions extends CommonApiHandlerOptions {
thinkingBudgetTokens?: number
ocaUsePromptCache?: boolean
taskId?: string
vectorIds?: string[]
}

export class OcaHandler implements ApiHandler {
Expand Down Expand Up @@ -180,7 +181,15 @@ export class OcaHandler implements ApiHandler {
return message
})

const stream = await client.chat.completions.create({
const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = []
if (this.getVectorStores().length > 0) {
tools.push({
type: "file_search",
vector_store_ids: this.getVectorStores(),
} as any)
}

const requestObject: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: this.options.ocaModelId || liteLlmDefaultModelId,
messages: [enhancedSystemMessage, ...enhancedMessages],
temperature,
Expand All @@ -192,7 +201,12 @@ export class OcaHandler implements ApiHandler {
...(this.options.taskId && {
litellm_session_id: `cline-${this.options.taskId}`,
}), // Add session ID for LiteLLM tracking
})
tools,
}

console.log("Input to OCA chat completions: ", requestObject)

const stream = await client.chat.completions.create(requestObject)

const inputCost = (await this.calculateCost(1e6, 0)) || 0
const outputCost = (await this.calculateCost(0, 1e6)) || 0
Expand Down Expand Up @@ -258,4 +272,12 @@ export class OcaHandler implements ApiHandler {
info: this.options.ocaModelInfo || liteLlmModelInfoSaneDefaults,
}
}

getVectorStores() {
if (this.options.vectorIds) {
return this.options.vectorIds
} else {
return []
}
}
}
1 change: 1 addition & 0 deletions src/core/controller/models/refreshOcaModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export async function refreshOcaModels(controller: Controller, request: StringRe
Logger.log(`Making refresh oca model request with customer opc-request-id: ${headers["opc-request-id"]}`)
const response = await axios.get(modelsUrl, { headers, ...getAxiosSettings() })
if (response.data?.data) {
console.log("Model response: ", response.data)
if (response.data.data.length === 0) {
HostProvider.window.showMessage({
type: ShowMessageType.ERROR,
Expand Down
103 changes: 103 additions & 0 deletions src/core/controller/vectors/refreshOcaVectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { StringRequest } from "@shared/proto/cline/common"
import { VectorStoreInfo, VectorStores } from "@shared/proto/cline/vectors"
import axios from "axios"
import { HostProvider } from "@/hosts/host-provider"
import { OcaAuthService } from "@/services/auth/oca/OcaAuthService"
import { DEFAULT_OCA_BASE_URL } from "@/services/auth/oca/utils/constants"
import { createOcaHeaders, getAxiosSettings } from "@/services/auth/oca/utils/utils"
import { Logger } from "@/services/logging/Logger"
import { ShowMessageType } from "@/shared/proto/index.host"
import { Controller } from ".."

/**
* Refreshes the Oca models and returns the updated model list
* @param controller The controller instance
* @param request Empty request object
* @returns Response containing the Oca models
*/
export async function refreshOcaVectors(controller: Controller, request: StringRequest): Promise<VectorStores> {
const vectors: Record<string, VectorStoreInfo> = {}
const ocaAccessToken = await OcaAuthService.getInstance().getAuthToken()
if (!ocaAccessToken) {
HostProvider.window.showMessage({
type: ShowMessageType.ERROR,
message: "Not authenticated with OCA. Please sign in first.",
})
return VectorStores.create({ error: "Not authenticated with OCA" })
}
const baseUrl = request.value || DEFAULT_OCA_BASE_URL
const vectorsUrl = `${baseUrl}/vector_store/list`
const headers = await createOcaHeaders(ocaAccessToken!, "vectors-refresh")
try {
Logger.log(`Making refresh oca vector request with customer opc-request-id: ${headers["opc-request-id"]}`)
const response = await axios.get(vectorsUrl, { headers, ...getAxiosSettings() })
if (response.data && response.data.data) {
const vectorIds: string[] = []
for (const vectorStore of response.data.data) {
const vectorStoreId = vectorStore.vector_store_id
if (typeof vectorStoreId !== "string" || !vectorStoreId) {
continue
}
vectors[vectorStoreId] = VectorStoreInfo.create({
id: vectorStoreId,
name: vectorStore.vector_store_name,
description: vectorStore.vector_store_description,
})
vectorIds.push(vectorStoreId)
}
console.log("Oca vectors fetched", vectors)

// Fetch current config
const apiConfiguration = controller.stateManager.getApiConfiguration()
const updatedConfig = { ...apiConfiguration }

// Which mode(s) to update?
const planActSeparateModelsSetting = controller.stateManager.getGlobalSettingsKey("planActSeparateModelsSetting")
const currentMode = controller.stateManager.getGlobalSettingsKey("mode")
const planModeSelectedVectorId: string[] = apiConfiguration?.planModeOcaVectorIds
? apiConfiguration?.planModeOcaVectorIds.filter(
(vectorId) => vectorIds.filter((secondVectorId) => vectorId === secondVectorId).length >= 1,
)
: []
const actModeSelectedVectorId: string[] = apiConfiguration?.actModeOcaVectorIds
? apiConfiguration?.actModeOcaVectorIds.filter(
(vectorId) => vectorIds.filter((secondVectorId) => vectorId === secondVectorId).length >= 1,
)
: []

// Save new model selection(s) to configuration object, per plan/act mode setting
if (planActSeparateModelsSetting) {
if (currentMode === "plan") {
updatedConfig.planModeOcaVectorIds = planModeSelectedVectorId
} else {
updatedConfig.actModeOcaVectorIds = actModeSelectedVectorId
}
} else {
updatedConfig.planModeOcaVectorIds = planModeSelectedVectorId
updatedConfig.actModeOcaVectorIds = actModeSelectedVectorId
}

controller.stateManager.setApiConfiguration(updatedConfig)

HostProvider.window.showMessage({
type: ShowMessageType.INFORMATION,
message: `Refreshed Oca knowledge bases from ${baseUrl}`,
})
} else {
console.error("Invalid response from oca API")
HostProvider.window.showMessage({
type: ShowMessageType.INFORMATION,
message: `Failed to fetch Oca vectors. Please check your configuration from ${baseUrl}`,
})
}
} catch (error) {
console.error("Error fetching oca vectors:", error)
const errorMsg = error.message || "Error refreshing Oca knowledge bases"
HostProvider.window.showMessage({
type: ShowMessageType.ERROR,
message: errorMsg,
})
return VectorStores.create({ error: errorMsg })
}
return VectorStores.create({ vectors })
}
6 changes: 6 additions & 0 deletions src/core/storage/StateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ export class StateManager {
planModeVercelAiGatewayModelInfo,
planModeOcaModelId,
planModeOcaModelInfo,
planModeOcaVectorIds,
// Act mode configurations
actModeApiProvider,
actModeApiModelId,
Expand Down Expand Up @@ -480,6 +481,7 @@ export class StateManager {
actModeVercelAiGatewayModelInfo,
actModeOcaModelId,
actModeOcaModelInfo,
actModeOcaVectorIds,
} = apiConfiguration

// Batch update global state keys
Expand Down Expand Up @@ -518,6 +520,7 @@ export class StateManager {
planModeVercelAiGatewayModelInfo,
planModeOcaModelId,
planModeOcaModelInfo,
planModeOcaVectorIds,

// Act mode configuration updates
actModeApiProvider,
Expand Down Expand Up @@ -553,6 +556,7 @@ export class StateManager {
actModeVercelAiGatewayModelInfo,
actModeOcaModelId,
actModeOcaModelInfo,
actModeOcaVectorIds,

// Global state updates
awsRegion,
Expand Down Expand Up @@ -993,6 +997,7 @@ export class StateManager {
this.globalStateCache["planModeVercelAiGatewayModelInfo"],
planModeOcaModelId: this.globalStateCache["planModeOcaModelId"],
planModeOcaModelInfo: this.globalStateCache["planModeOcaModelInfo"],
planModeOcaVectorIds: this.globalStateCache["planModeOcaVectorIds"],

// Act mode configurations
actModeApiProvider: this.taskStateCache["actModeApiProvider"] || this.globalStateCache["actModeApiProvider"],
Expand Down Expand Up @@ -1055,6 +1060,7 @@ export class StateManager {
this.globalStateCache["actModeVercelAiGatewayModelInfo"],
actModeOcaModelId: this.globalStateCache["actModeOcaModelId"],
actModeOcaModelInfo: this.globalStateCache["actModeOcaModelInfo"],
actModeOcaVectorIds: this.globalStateCache["actModeOcaVectorIds"],
}
}
}
2 changes: 2 additions & 0 deletions src/core/storage/state-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export interface Settings {
planModeHuaweiCloudMaasModelInfo: ModelInfo | undefined
planModeOcaModelId: string | undefined
planModeOcaModelInfo: OcaModelInfo | undefined
planModeOcaVectorIds: string[]
// Act mode configurations
actModeApiProvider: ApiProvider
actModeApiModelId: string | undefined
Expand Down Expand Up @@ -171,6 +172,7 @@ export interface Settings {
actModeVercelAiGatewayModelInfo: ModelInfo | undefined
actModeOcaModelId: string | undefined
actModeOcaModelInfo: OcaModelInfo | undefined
actModeOcaVectorIds: string[]
}

export interface Secrets {
Expand Down
4 changes: 4 additions & 0 deletions src/core/storage/utils/state-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
>("planModeVercelAiGatewayModelInfo")
const planModeOcaModelId = context.globalState.get("planModeOcaModelId") as string | undefined
const planModeOcaModelInfo = context.globalState.get("planModeOcaModelInfo") as OcaModelInfo | undefined
const planModeOcaVectorIds = context.globalState.get("planModeOcaVectorIds") as string[] | []
// Act mode configurations
const actModeApiProvider = context.globalState.get<GlobalStateAndSettings["actModeApiProvider"]>("actModeApiProvider")
const actModeApiModelId = context.globalState.get<GlobalStateAndSettings["actModeApiModelId"]>("actModeApiModelId")
Expand Down Expand Up @@ -380,6 +381,7 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
>("actModeVercelAiGatewayModelInfo")
const actModeOcaModelId = context.globalState.get("actModeOcaModelId") as string | undefined
const actModeOcaModelInfo = context.globalState.get("actModeOcaModelInfo") as OcaModelInfo | undefined
const actModeOcaVectorIds = context.globalState.get("actModeOcaVectorIds") as string[] | []
const sapAiCoreUseOrchestrationMode =
context.globalState.get<GlobalStateAndSettings["sapAiCoreUseOrchestrationMode"]>("sapAiCoreUseOrchestrationMode")

Expand Down Expand Up @@ -492,6 +494,7 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
planModeVercelAiGatewayModelInfo,
planModeOcaModelId,
planModeOcaModelInfo,
planModeOcaVectorIds,
// Act mode configurations
actModeApiProvider: actModeApiProvider || apiProvider,
actModeApiModelId,
Expand Down Expand Up @@ -526,6 +529,7 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
actModeVercelAiGatewayModelInfo,
actModeOcaModelId,
actModeOcaModelInfo,
actModeOcaVectorIds,

// Other global fields
focusChainSettings: focusChainSettings || DEFAULT_FOCUS_CHAIN_SETTINGS,
Expand Down
5 changes: 4 additions & 1 deletion src/services/auth/oca/providers/OcaAuthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,12 @@ export class OcaAuthProvider {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
...getAxiosSettings(),
})
console.log("Successful response: ", tokenResponse)
const accessToken = tokenResponse.data.access_token
const userInfo: OcaUserInfo = await this.getUserAccountInfo(accessToken)
return { user: userInfo, apiKey: accessToken }
} catch (err: unknown) {
console.log(err)
const isAxios = (axios as any)?.isAxiosError?.(err)
const status = isAxios ? (err as any).response?.status : undefined
const data: any = isAxios ? (err as any).response?.data : undefined
Expand Down Expand Up @@ -161,6 +163,7 @@ export class OcaAuthProvider {
OcaAuthProvider.pkceStateMap.delete(state)
const discovery = await axios.get(`${idcs_url}/.well-known/openid-configuration`, { ...getAxiosSettings() })
const tokenEndpoint = discovery.data.token_endpoint
console.log("Sign In Token Endpoint: ", tokenEndpoint)
const params: any = {
grant_type: "authorization_code",
code,
Expand All @@ -183,7 +186,7 @@ export class OcaAuthProvider {
throw new Error("No ID token received from OCA")
}

// Step 2: Get access_token (this is what you'll use for APIs)
//Step 2: Get access_token (this is what you'll use for APIs)
const accessToken = tokenResponse.data.access_token
const refreshToken = tokenResponse.data.refresh_token
if (refreshToken) {
Expand Down
2 changes: 2 additions & 0 deletions src/shared/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export interface ApiHandlerOptions {
planModeVercelAiGatewayModelInfo?: ModelInfo
planModeOcaModelId?: string
planModeOcaModelInfo?: OcaModelInfo
planModeOcaVectorIds?: string[]
// Act mode configurations

// Act mode configurations
Expand Down Expand Up @@ -188,6 +189,7 @@ export interface ApiHandlerOptions {
actModeVercelAiGatewayModelInfo?: ModelInfo
actModeOcaModelId?: string
actModeOcaModelInfo?: OcaModelInfo
actModeOcaVectorIds?: string[]
}

export type ApiConfiguration = ApiHandlerOptions &
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ function convertProtoOcaModelInfoToOcaModelInfo(info: ProtoOcaModelInfo | undefi
}
}

function convertOcaVectorIdsToProtoOcaVectorIds(vectorIds: string[] | undefined): string[] {
if (!vectorIds) {
return []
}
return vectorIds
}

// Convert application LiteLLMModelInfo to proto LiteLLMModelInfo
function convertLiteLLMModelInfoToProto(info: AppLiteLLMModelInfo | undefined): LiteLLMModelInfo | undefined {
if (!info) {
Expand Down Expand Up @@ -503,6 +510,7 @@ export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoA
planModeVercelAiGatewayModelInfo: convertModelInfoToProtoOpenRouter(config.planModeVercelAiGatewayModelInfo),
planModeOcaModelId: config.planModeOcaModelId,
planModeOcaModelInfo: convertOcaModelInfoToProtoOcaModelInfo(config.planModeOcaModelInfo),
planModeOcaVectorIds: convertOcaVectorIdsToProtoOcaVectorIds(config.planModeOcaVectorIds),

// Act mode configurations
actModeApiProvider: config.actModeApiProvider ? convertApiProviderToProto(config.actModeApiProvider) : undefined,
Expand Down Expand Up @@ -538,6 +546,7 @@ export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoA
actModeVercelAiGatewayModelInfo: convertModelInfoToProtoOpenRouter(config.actModeVercelAiGatewayModelInfo),
actModeOcaModelId: config.actModeOcaModelId,
actModeOcaModelInfo: convertOcaModelInfoToProtoOcaModelInfo(config.actModeOcaModelInfo),
actModeOcaVectorIds: convertOcaVectorIdsToProtoOcaVectorIds(config.actModeOcaVectorIds),
}
}

Expand Down Expand Up @@ -655,6 +664,7 @@ export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguratio
planModeVercelAiGatewayModelInfo: convertProtoToModelInfo(protoConfig.planModeVercelAiGatewayModelInfo),
planModeOcaModelId: protoConfig.planModeOcaModelId,
planModeOcaModelInfo: convertProtoOcaModelInfoToOcaModelInfo(protoConfig.planModeOcaModelInfo),
planModeOcaVectorIds: protoConfig.planModeOcaVectorIds,

// Act mode configurations
actModeApiProvider:
Expand Down Expand Up @@ -691,5 +701,6 @@ export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguratio
actModeVercelAiGatewayModelInfo: convertProtoToModelInfo(protoConfig.actModeVercelAiGatewayModelInfo),
actModeOcaModelId: protoConfig.actModeOcaModelId,
actModeOcaModelInfo: convertProtoOcaModelInfoToOcaModelInfo(protoConfig.actModeOcaModelInfo),
actModeOcaVectorIds: protoConfig.actModeOcaVectorIds,
}
}
Loading
Loading