This guide provides detailed information about every file involved in the CodeAgent provider integration, including exact line numbers, code snippets, and what you can customize.
- Backend Integration Files
- Frontend UI Files
- Type Definitions
- Proto Definitions
- State Management
- Utility Files
- Complete Customization Workflow
File: src/core/api/providers/codeagent.ts
Lines: 1-145
Purpose: Core API communication logic
import { Anthropic } from "@anthropic-ai/sdk"
import { ModelInfo, OpenAiCompatibleModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
import { calculateApiCostOpenAI } from "@utils/cost"
import OpenAI from "openai"
import { ApiHandler, CommonApiHandlerOptions } from "../index"
import { withRetry } from "../retry"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
interface CodeAgentHandlerOptions extends CommonApiHandlerOptions {
codeagentBaseUrl?: string
codeagentApiKey?: string
codeagentModelId?: string
codeagentModelInfo?: ModelInfo
}What you can customize:
- Add additional imports for custom utilities
- Extend
CodeAgentHandlerOptionswith custom fields:interface CodeAgentHandlerOptions extends CommonApiHandlerOptions { codeagentBaseUrl?: string codeagentApiKey?: string codeagentModelId?: string codeagentModelInfo?: ModelInfo // Add custom options codeagentOrganization?: string codeagentTimeout?: number codeagentCustomHeaders?: Record<string, string> }
private ensureClient(): OpenAI {
if (!this.client) {
if (!this.options.codeagentApiKey) {
throw new Error("CodeAgent API key is required")
}
if (!this.options.codeagentBaseUrl) {
throw new Error("CodeAgent Base URL is required")
}
try {
// Ensure the base URL is properly formatted for OpenAI SDK
// The OpenAI SDK expects baseURL without /v1 suffix (it adds it automatically)
let baseUrl = this.options.codeagentBaseUrl.trim()
// Remove trailing slashes
baseUrl = baseUrl.replace(/\/+$/, "")
// Remove /v1 suffix if present (OpenAI SDK adds it)
if (baseUrl.endsWith("/v1")) {
baseUrl = baseUrl.slice(0, -3)
}
this.client = new OpenAI({
baseURL: baseUrl,
apiKey: this.options.codeagentApiKey,
defaultHeaders: {
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline-CodeAgent",
},
})
} catch (error: any) {
throw new Error(`Error creating CodeAgent client: ${error.message}`)
}
}
return this.client
}What you can customize:
-
Custom URL processing:
// Add custom domain validation if (!baseUrl.includes("your-domain.com")) { throw new Error("Only your-domain.com backends are supported") } // Add custom port handling if (!baseUrl.includes(":")) { baseUrl = `${baseUrl}:8000` // Add default port }
-
Custom headers:
defaultHeaders: { "HTTP-Referer": "https://cline.bot", "X-Title": "Cline-CodeAgent", "X-Organization": this.options.codeagentOrganization, "X-API-Version": "v1", ...this.options.codeagentCustomHeaders, },
-
Timeout configuration:
this.client = new OpenAI({ baseURL: baseUrl, apiKey: this.options.codeagentApiKey, timeout: this.options.codeagentTimeout || 60000, defaultHeaders: { /* ... */ }, })
-
Custom error messages:
if (!this.options.codeagentApiKey) { throw new Error("Please provide your CodeAgent API key in settings") }
@withRetry()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const client = this.ensureClient()
const modelId = this.options.codeagentModelId ?? ""
const modelInfo = this.options.codeagentModelInfo as OpenAiCompatibleModelInfo | undefined
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
]
// Use model info if available, otherwise use sane defaults (similar to OpenAI handler)
const temperature = modelInfo?.temperature ?? openAiModelInfoSaneDefaults.temperature
const maxTokens = modelInfo?.maxTokens && modelInfo.maxTokens > 0 ? modelInfo.maxTokens : undefined
const stream = await client.chat.completions.create({
model: modelId,
messages: openAiMessages,
temperature,
max_tokens: maxTokens,
stream: true,
stream_options: { include_usage: true },
})What you can customize:
-
Additional request parameters:
const stream = await client.chat.completions.create({ model: modelId, messages: openAiMessages, temperature, max_tokens: maxTokens, stream: true, stream_options: { include_usage: true }, // Add custom parameters top_p: modelInfo?.topP ?? 1.0, frequency_penalty: modelInfo?.frequencyPenalty ?? 0, presence_penalty: modelInfo?.presencePenalty ?? 0, stop: modelInfo?.stopSequences, })
-
Custom message formatting:
// Add custom message preprocessing const processedMessages = openAiMessages.map(msg => ({ ...msg, // Add custom metadata metadata: { source: "cline", timestamp: Date.now() } }))
-
Model-specific logic:
// Different parameters for different models let temperature = modelInfo?.temperature ?? openAiModelInfoSaneDefaults.temperature if (modelId.includes("reasoning")) { temperature = undefined // Reasoning models don't use temperature }
let lastUsage: OpenAI.CompletionUsage | undefined
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {
yield {
type: "text",
text: delta.content,
}
}
if (chunk.usage) {
lastUsage = chunk.usage
}
}
if (lastUsage) {
const inputTokens = lastUsage.prompt_tokens || 0
const outputTokens = lastUsage.completion_tokens || 0
// Support for prompt caching if the backend provides it
const cacheWriteTokens = (lastUsage as any).prompt_tokens_details?.caching_tokens || undefined
const cacheReadTokens = (lastUsage as any).prompt_tokens_details?.cached_tokens || undefined
const model = this.getModel()
const totalCost = calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
yield {
type: "usage",
inputTokens: inputTokens,
outputTokens: outputTokens,
cacheWriteTokens: cacheWriteTokens,
cacheReadTokens: cacheReadTokens,
totalCost: totalCost,
}
}What you can customize:
-
Custom usage extraction:
// If your backend uses different field names const cacheWriteTokens = (lastUsage as any).cache_creation_tokens || (lastUsage as any).prompt_tokens_details?.caching_tokens || undefined const cacheReadTokens = (lastUsage as any).cache_read_tokens || (lastUsage as any).prompt_tokens_details?.cached_tokens || undefined
-
Additional metrics:
yield { type: "usage", inputTokens: inputTokens, outputTokens: outputTokens, cacheWriteTokens: cacheWriteTokens, cacheReadTokens: cacheReadTokens, totalCost: totalCost, // Add custom metrics latency: (lastUsage as any).latency_ms, modelVersion: (lastUsage as any).model_version, }
-
Custom logging:
if (lastUsage) { console.log("CodeAgent Usage:", { input: inputTokens, output: outputTokens, cost: totalCost, timestamp: new Date().toISOString() }) }
getModel(): { id: string; info: ModelInfo } {
const modelId = this.options.codeagentModelId
const modelInfo = this.options.codeagentModelInfo
if (modelId && modelInfo) {
return { id: modelId, info: modelInfo }
}
// Default model info - mimics OpenAI defaults
// These will be replaced by actual model info fetched from the backend
return {
id: modelId || "gpt-4",
info: {
maxTokens: openAiModelInfoSaneDefaults.maxTokens,
contextWindow: 128000,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
description: "CodeAgent model",
},
}
}What you can customize:
-
Default model configuration:
return { id: modelId || "your-default-model", info: { maxTokens: 16384, // Your default contextWindow: 200000, // Your default supportsImages: true, supportsPromptCache: true, // If your backend supports it inputPrice: 0.001, // Your pricing outputPrice: 0.003, // Your pricing description: "Custom CodeAgent model", }, }
-
Model-specific defaults:
if (modelId?.includes("vision")) { return { id: modelId, info: { supportsImages: true, maxTokens: 4096, // Vision model defaults } } }
File: src/core/controller/models/refreshCodeAgentModels.ts
Lines: 1-95
Purpose: Fetch and parse model list from backend
import type { ModelInfo } from "@shared/api"
import { GlobalFileNames } from "../../storage/disk"
export async function refreshCodeAgentModels(
baseURL?: string,
apiKey?: string,
): Promise<Record<string, ModelInfo>> {
if (!baseURL || !apiKey) {
throw new Error("CodeAgent Base URL and API Key are required to fetch models")
}
try {
const response = await fetch(`${baseURL}/v1/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const data = await response.json()What you can customize:
-
Custom endpoint:
// If your backend uses a different endpoint const response = await fetch(`${baseURL}/api/models`, { // ... }) // Or with query parameters const response = await fetch(`${baseURL}/v1/models?include_details=true`, { // ... })
-
Additional headers:
const response = await fetch(`${baseURL}/v1/models`, { headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", "X-API-Version": "v1", "Accept": "application/json", }, })
-
Timeout handling:
const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 10000) // 10s timeout try { const response = await fetch(`${baseURL}/v1/models`, { headers: { /* ... */ }, signal: controller.signal, }) clearTimeout(timeoutId) } catch (error) { if (error.name === 'AbortError') { throw new Error("Request timed out") } throw error }
const models: Record<string, ModelInfo> = {}
// Support different response structures
const modelList = data.data || data.models || data
if (!Array.isArray(modelList)) {
throw new Error("Invalid models response format")
}
for (const model of modelList) {
// Get model ID from various possible field names
const modelId = model.id || model.model_id || model.name
if (!modelId) {
console.warn("Skipping model without ID:", model)
continue
}
// Extract model information with fallbacks
const maxTokens =
model.max_tokens ||
model.maxTokens ||
model.max_completion_tokens ||
8192
const contextWindow =
model.context_window ||
model.contextWindow ||
model.context_length ||
model.max_context_length ||
128000
const supportsImages =
model.supports_images ??
model.supportsImages ??
model.vision ??
model.multimodal ??
true
const supportsPromptCache =
model.supports_prompt_cache ??
model.supportsPromptCache ??
false
const inputPrice =
model.input_price ??
model.inputPrice ??
model.prompt_price ??
0
const outputPrice =
model.output_price ??
model.outputPrice ??
model.completion_price ??
0
const description =
model.description ||
model.desc ||
`${modelId}`
models[modelId] = {
maxTokens,
contextWindow,
supportsImages,
supportsPromptCache,
inputPrice,
outputPrice,
description,
}
}What you can customize:
-
Add custom fields:
// Extend ModelInfo to include custom fields models[modelId] = { maxTokens, contextWindow, supportsImages, supportsPromptCache, inputPrice, outputPrice, description, // Add custom fields temperature: model.default_temperature ?? 0, topP: model.default_top_p ?? 1.0, capabilities: model.capabilities || [], modelFamily: model.family || "unknown", }
-
Custom field extraction:
// If your backend uses completely different field names const maxTokens = model.max_output_length || model.response_token_limit || 8192 const inputPrice = model.pricing?.input || model.cost?.prompt || 0
-
Model filtering:
for (const model of modelList) { const modelId = model.id || model.model_id || model.name // Skip certain models if (modelId.includes("deprecated")) { continue } // Only include specific models if (!modelId.startsWith("your-prefix-")) { continue } // Filter by capability if (!model.capabilities?.includes("chat")) { continue } // ... rest of extraction }
-
Pricing calculation:
// If your backend uses different pricing structure const inputPrice = model.pricing?.input_per_1k ? model.pricing.input_per_1k / 1000 // Convert to per-million : 0 const outputPrice = model.pricing?.output_per_1k ? model.pricing.output_per_1k / 1000 : 0
// Cache the models to disk
const fs = await import("fs/promises")
const path = await import("path")
await controller.stateManager.storageManager.ensureGlobalDirectoryExists()
const modelsPath = path.join(
controller.stateManager.storageManager.getGlobalStoragePath(),
GlobalFileNames.codeagentModels,
)
await fs.writeFile(modelsPath, JSON.stringify(models, null, 2))
return modelsWhat you can customize:
-
Cache duration:
// Add timestamp to cache const cacheData = { timestamp: Date.now(), models: models, } await fs.writeFile(modelsPath, JSON.stringify(cacheData, null, 2)) // Check cache age before fetching const cacheAge = Date.now() - cacheData.timestamp if (cacheAge < 3600000) { // 1 hour return cacheData.models }
-
Error handling:
try { await fs.writeFile(modelsPath, JSON.stringify(models, null, 2)) } catch (error) { console.error("Failed to cache models:", error) // Continue without caching }
File: webview-ui/src/components/settings/providers/CodeAgentProvider.tsx
Lines: 1-110
Purpose: Settings UI for CodeAgent configuration
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { memo } from "react"
import { useExtensionState } from "../../../context/ExtensionStateContext"
import { validateApiConfiguration } from "../../../utils/validate"
import CodeAgentModelPicker from "../CodeAgentModelPicker"
import { vscode } from "../../../utils/vscode"
import type { ApiConfiguration } from "@shared/api"
interface CodeAgentProviderProps {
apiConfiguration?: ApiConfiguration
setApiConfiguration: (config: ApiConfiguration) => void
}
const CodeAgentProvider = ({ apiConfiguration, setApiConfiguration }: CodeAgentProviderProps) => {
const { mode } = useExtensionState()
const handleInputChange = (field: keyof ApiConfiguration) => (e: any) => {
const value = e.target.value
setApiConfiguration({
...apiConfiguration,
[field]: value,
})
}What you can customize:
-
Add custom fields:
// Add organization field <VSCodeTextField value={apiConfiguration?.codeagentOrganization || ""} placeholder="your-organization" onInput={handleInputChange("codeagentOrganization")} > <span className="...">Organization ID (Optional)</span> </VSCodeTextField>
-
Custom validation:
const handleInputChange = (field: keyof ApiConfiguration) => (e: any) => { let value = e.target.value // Custom validation/transformation if (field === "codeagentBaseUrl") { // Auto-add https:// if (value && !value.startsWith("http")) { value = `https://${value}` } } setApiConfiguration({ ...apiConfiguration, [field]: value, }) }
return (
<div className="...">
<div className="...">
<VSCodeTextField
value={apiConfiguration?.codeagentBaseUrl || ""}
style={{ width: "100%" }}
placeholder="http://localhost:8000"
onInput={handleInputChange("codeagentBaseUrl")}
>
<span className="...">
CodeAgent Base URL
<span className="..."> *</span>
</span>
</VSCodeTextField>
<p className="...">
The base URL of your CodeAgent backend (e.g., http://localhost:8000 or
https://api.your-backend.com)
</p>
</div>
<div className="...">
<VSCodeTextField
value={apiConfiguration?.codeagentApiKey || ""}
style={{ width: "100%" }}
type="password"
placeholder="sk-..."
onInput={handleInputChange("codeagentApiKey")}
>
<span className="...">
CodeAgent API Key
<span className="..."> *</span>
</span>
</VSCodeTextField>
<p className="...">
Your API key for authenticating with the CodeAgent backend
</p>
</div>
<div className="...">
<CodeAgentModelPicker
key={`codeagent-${mode}`}
mode={mode}
modelId={currentModelId}
modelInfo={currentModelInfo}
onSelect={handleModelSelect}
apiConfiguration={apiConfiguration}
setApiConfiguration={setApiConfiguration}
/>
</div>
</div>
)What you can customize:
-
Change placeholders and help text:
<VSCodeTextField value={apiConfiguration?.codeagentBaseUrl || ""} placeholder="https://api.mycompany.com" // Your default onInput={handleInputChange("codeagentBaseUrl")} > <span className="..."> MyCompany API Endpoint {/* Your label */} </span> </VSCodeTextField> <p className="..."> Enter your company's API endpoint URL </p>
-
Add validation indicators:
const [isValidUrl, setIsValidUrl] = useState(true) const handleUrlChange = (e: any) => { const value = e.target.value try { new URL(value) setIsValidUrl(true) } catch { setIsValidUrl(false) } handleInputChange("codeagentBaseUrl")(e) } <VSCodeTextField value={apiConfiguration?.codeagentBaseUrl || ""} onInput={handleUrlChange} className={isValidUrl ? "" : "error"} > <span>CodeAgent Base URL</span> </VSCodeTextField> {!isValidUrl && <p className="error">Invalid URL format</p>}
-
Add test connection button:
const testConnection = async () => { try { const response = await fetch( `${apiConfiguration?.codeagentBaseUrl}/v1/models`, { headers: { Authorization: `Bearer ${apiConfiguration?.codeagentApiKey}`, }, } ) if (response.ok) { vscode.postMessage({ type: "showInformationMessage", text: "✓ Connection successful!", }) } else { throw new Error(`HTTP ${response.status}`) } } catch (error) { vscode.postMessage({ type: "showErrorMessage", text: `✗ Connection failed: ${error.message}`, }) } } <VSCodeButton onClick={testConnection}> Test Connection </VSCodeButton>
File: webview-ui/src/components/settings/CodeAgentModelPicker.tsx
Lines: 1-250
Purpose: Searchable dropdown for model selection
const fuse = useMemo(() => {
const modelArray = Object.entries(codeagentModels).map(([id, info]) => ({
id,
info,
}))
return new Fuse(modelArray, {
keys: ["id", "info.description"],
threshold: 0.3,
includeScore: true,
})
}, [codeagentModels])What you can customize:
-
Search configuration:
return new Fuse(modelArray, { keys: [ { name: "id", weight: 2 }, // Prioritize ID matches { name: "info.description", weight: 1 }, { name: "info.modelFamily", weight: 1.5 }, // Custom field ], threshold: 0.2, // Stricter matching includeScore: true, shouldSort: true, minMatchCharLength: 2, })
-
Custom sorting:
const sortedModels = useMemo(() => { return Object.entries(codeagentModels) .sort(([aId, aInfo], [bId, bInfo]) => { // Sort by context window (descending) return (bInfo.contextWindow || 0) - (aInfo.contextWindow || 0) // Or sort by name return aId.localeCompare(bId) // Or custom priority if (aId.includes("premium")) return -1 if (bId.includes("premium")) return 1 return 0 }) }, [codeagentModels])
<div className="model-item">
<div className="model-id">{model.id}</div>
{model.info.description && (
<div className="model-description">
{model.info.description}
</div>
)}
<div className="model-metadata">
{model.info.contextWindow && (
<span className="model-metadata-item">
{(model.info.contextWindow / 1000).toFixed(0)}k context
</span>
)}
{model.info.maxTokens && (
<span className="model-metadata-item">
{(model.info.maxTokens / 1000).toFixed(0)}k max output
</span>
)}
</div>
</div>What you can customize:
-
Display format:
<div className="model-item"> {/* Add icon based on capabilities */} {model.info.supportsImages && <span>📷</span>} {model.info.supportsPromptCache && <span>⚡</span>} <div className="model-id-container"> <div className="model-id">{model.id}</div> {/* Add badge for new models */} {model.info.isNew && <span className="badge">NEW</span>} </div> {/* Custom description */} <div className="model-description"> {model.info.description || `${model.id} - AI Model`} </div> {/* Enhanced metadata */} <div className="model-metadata"> <span>🧠 {(model.info.contextWindow / 1000).toFixed(0)}k</span> <span>⚡ {(model.info.maxTokens / 1000).toFixed(0)}k</span> {model.info.inputPrice > 0 && ( <span>💰 ${model.info.inputPrice.toFixed(3)}/M</span> )} {/* Add model family */} {model.info.modelFamily && ( <span className="family">{model.info.modelFamily}</span> )} </div> </div>
-
Grouping models:
const groupedModels = useMemo(() => { const groups: Record<string, Array<{id: string, info: ModelInfo}>> = {} Object.entries(codeagentModels).forEach(([id, info]) => { const family = info.modelFamily || "Other" if (!groups[family]) groups[family] = [] groups[family].push({ id, info }) }) return groups }, [codeagentModels]) // Render {Object.entries(groupedModels).map(([family, models]) => ( <div key={family} className="model-group"> <div className="group-header">{family}</div> {models.map(model => ( <div key={model.id} className="model-item"> {/* ... */} </div> ))} </div> ))}
File: src/shared/api.ts
Lines: 50-100, 213-240, 3400-3500
Purpose: TypeScript type definitions
export type ApiProvider =
// ... other providers
| "codeagent"What you can customize:
- Provider name (must match across all files)
export interface ApiConfiguration {
// ... other fields
codeagentApiKey?: string
codeagentBaseUrl?: string
// Plan mode
planModeCodeagentModelId?: string
planModeCodeagentModelInfo?: ModelInfo
// Act mode
actModeCodeagentModelId?: string
actModeCodeagentModelInfo?: ModelInfo
}What you can customize:
Add custom fields:
export interface ApiConfiguration {
// Existing fields
codeagentApiKey?: string
codeagentBaseUrl?: string
// Add custom fields
codeagentOrganization?: string
codeagentTimeout?: number
codeagentRetryAttempts?: number
codeagentCustomHeaders?: Record<string, string>
// Mode-specific configs
planModeCodeagentModelId?: string
planModeCodeagentModelInfo?: ModelInfo
actModeCodeagentModelId?: string
actModeCodeagentModelInfo?: ModelInfo
}export interface ModelInfo {
maxTokens?: number
contextWindow?: number
supportsImages?: boolean
supportsPromptCache: boolean
inputPrice?: number
outputPrice?: number
thinkingConfig?: {
maxBudget?: number
outputPrice?: number
outputPriceTiers?: PriceTier[]
}
supportsGlobalEndpoint?: boolean
cacheWritesPrice?: number
cacheReadsPrice?: number
description?: string
tiers?: {
contextWindow: number
inputPrice?: number
outputPrice?: number
cacheWritesPrice?: number
cacheReadsPrice?: number
}[]
}
export interface OpenAiCompatibleModelInfo extends ModelInfo {
temperature?: number
isR1FormatRequired?: boolean
}What you can customize:
Extend with custom fields:
export interface CodeAgentModelInfo extends OpenAiCompatibleModelInfo {
// Add CodeAgent-specific fields
modelFamily?: "gpt" | "claude" | "custom"
capabilities?: string[]
isNew?: boolean
releaseDate?: string
maxBatchSize?: number
defaultSystemPrompt?: string
}
// Use in ApiConfiguration
export interface ApiConfiguration {
// ...
planModeCodeagentModelInfo?: CodeAgentModelInfo
actModeCodeagentModelInfo?: CodeAgentModelInfo
}File: proto/cline/models.proto
Lines: 76-77, 133-134, 233-234, ~280
Purpose: Data structure for serialization
message ModelsApiConfiguration {
// Global configuration (lines 76-77)
optional string codeagent_api_key = 76;
optional string codeagent_base_url = 77;
// Plan mode (lines 133-134)
optional string plan_mode_codeagent_model_id = 133;
optional OpenRouterModelInfo plan_mode_codeagent_model_info = 134;
// Act mode (lines 233-234)
optional string act_mode_codeagent_model_id = 233;
optional OpenRouterModelInfo act_mode_codeagent_model_info = 234;
}
// Service definition (around line 280)
service ModelsService {
rpc refreshCodeAgentModels(google.protobuf.Empty) returns (google.protobuf.StringValue);
}What you can customize:
-
Add global fields:
message ModelsApiConfiguration { // Existing optional string codeagent_api_key = 76; optional string codeagent_base_url = 77; // Add new fields (use next available numbers) optional string codeagent_organization = 78; optional int32 codeagent_timeout = 79; optional int32 codeagent_retry_attempts = 80; }
-
Add mode-specific fields:
// Plan mode optional string plan_mode_codeagent_model_id = 133; optional OpenRouterModelInfo plan_mode_codeagent_model_info = 134; optional int32 plan_mode_codeagent_temperature = 135; // New optional int32 plan_mode_codeagent_max_retries = 136; // New
Important: After modifying proto files, run:
npm run protosFile: webview-ui/src/context/ExtensionStateContext.tsx
Lines: 80-100, 200-220
Purpose: Global state management
export interface ExtensionState {
// ... other state
codeagentModels: Record<string, ModelInfo>
}What you can customize:
Add custom state:
export interface ExtensionState {
// Existing
codeagentModels: Record<string, ModelInfo>
// Add custom state
codeagentConnectionStatus: "connected" | "disconnected" | "testing"
codeagentLastFetch: number
codeagentModelGroups: Record<string, string[]>
}const setCodeAgentModels = useCallback((models: Record<string, ModelInfo>) => {
setState((prevState) => ({
...prevState,
codeagentModels: models,
}))
}, [])What you can customize:
Add custom setters:
const setCodeAgentConnectionStatus = useCallback(
(status: "connected" | "disconnected" | "testing") => {
setState((prevState) => ({
...prevState,
codeagentConnectionStatus: status,
}))
},
[]
)
const updateCodeAgentLastFetch = useCallback(() => {
setState((prevState) => ({
...prevState,
codeagentLastFetch: Date.now(),
}))
}, [])File: webview-ui/src/utils/validate.ts
Lines: 60-80
Purpose: Input validation
case "codeagent":
if (!apiConfiguration.codeagentBaseUrl?.trim()) {
return "CodeAgent Base URL is required"
}
if (!apiConfiguration.codeagentApiKey?.trim()) {
return "CodeAgent API Key is required"
}
breakWhat you can customize:
-
Enhanced validation:
case "codeagent": // URL validation if (!apiConfiguration.codeagentBaseUrl?.trim()) { return "CodeAgent Base URL is required" } // URL format validation try { const url = new URL(apiConfiguration.codeagentBaseUrl) if (!url.protocol.startsWith('http')) { return "CodeAgent Base URL must use HTTP or HTTPS" } } catch { return "Invalid CodeAgent Base URL format" } // API key validation if (!apiConfiguration.codeagentApiKey?.trim()) { return "CodeAgent API Key is required" } if (apiConfiguration.codeagentApiKey.length < 10) { return "CodeAgent API Key appears to be invalid" } // Custom field validation if (apiConfiguration.codeagentOrganization) { if (!/^[a-z0-9-]+$/.test(apiConfiguration.codeagentOrganization)) { return "Organization ID can only contain lowercase letters, numbers, and hyphens" } } break
-
Async validation:
// For testing connection during validation const testCodeAgentConnection = async (config: ApiConfiguration) => { try { const response = await fetch(`${config.codeagentBaseUrl}/v1/models`, { headers: { Authorization: `Bearer ${config.codeagentApiKey}`, }, }) return response.ok } catch { return false } }
File: webview-ui/src/components/settings/utils/providerUtils.ts
Lines: 150-200
Purpose: Provider field management
export const providerFields: Record<ApiProvider, string[]> = {
// ... other providers
codeagent: [
"codeagentApiKey",
"codeagentBaseUrl",
],
}What you can customize:
Add custom fields:
export const providerFields: Record<ApiProvider, string[]> = {
codeagent: [
"codeagentApiKey",
"codeagentBaseUrl",
// Add custom fields
"codeagentOrganization",
"codeagentTimeout",
"codeagentRetryAttempts",
],
}File: src/core/storage/disk.ts
Lines: 40-50
Purpose: File storage configuration
export const GlobalFileNames = {
// ... other files
codeagentModels: "codeagent_models.json",
}What you can customize:
Add custom cache files:
export const GlobalFileNames = {
codeagentModels: "codeagent_models.json",
// Add custom cache files
codeagentConfig: "codeagent_config.json",
codeagentMetrics: "codeagent_metrics.json",
}// File: src/shared/api.ts
export interface ApiConfiguration {
// ... existing
codeagentOrganization?: string
}// File: proto/cline/models.proto
message ModelsApiConfiguration {
// ... existing
optional string codeagent_organization = 78;
}npm run protos// File: src/shared/proto-conversions/models/api-configuration-conversion.ts
// In convertApiConfigurationToProto:
codeagentOrganization: config.codeagentOrganization,
// In convertProtoToApiConfiguration:
codeagentOrganization: protoConfig.codeagentOrganization,// File: webview-ui/src/components/settings/providers/CodeAgentProvider.tsx
<VSCodeTextField
value={apiConfiguration?.codeagentOrganization || ""}
placeholder="my-org"
onInput={handleInputChange("codeagentOrganization")}
>
<span>Organization (Optional)</span>
</VSCodeTextField>// File: src/core/api/providers/codeagent.ts
interface CodeAgentHandlerOptions extends CommonApiHandlerOptions {
codeagentOrganization?: string
}
// In ensureClient():
defaultHeaders: {
"X-Organization": this.options.codeagentOrganization,
}// File: webview-ui/src/components/settings/utils/providerUtils.ts
export const providerFields: Record<ApiProvider, string[]> = {
codeagent: [
"codeagentApiKey",
"codeagentBaseUrl",
"codeagentOrganization",
],
}- ✅
src/core/api/providers/codeagent.ts- API handler - ✅
src/core/controller/models/refreshCodeAgentModels.ts- Model fetching - ✅
src/core/api/index.ts- Provider switch case - ✅
src/core/storage/disk.ts- Cache configuration - ✅
src/shared/api.ts- Type definitions - ✅
src/shared/proto-conversions/models/api-configuration-conversion.ts- Proto conversions - ✅
proto/cline/models.proto- Proto schema
- ✅
webview-ui/src/components/settings/providers/CodeAgentProvider.tsx- Settings UI - ✅
webview-ui/src/components/settings/CodeAgentModelPicker.tsx- Model picker - ✅
webview-ui/src/components/settings/ApiOptions.tsx- Provider list - ✅
webview-ui/src/components/welcome/WelcomeView.tsx- Welcome text - ✅
webview-ui/src/context/ExtensionStateContext.tsx- State management - ✅
webview-ui/src/utils/validate.ts- Validation - ✅
webview-ui/src/components/settings/utils/providerUtils.ts- Provider utils
Last Updated: 2025-10-05
Total Customizable Files: 13
Proto Regeneration Required: After modifying .proto files, run npm run protos