mirror of
https://github.com/cline/cline.git
synced 2026-09-06 12:28:08 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 903dd0ec1c | |||
| 89287dbcf6 | |||
| 07a2d1721e |
@@ -20,12 +20,13 @@ import { useOcaAuth } from "../hooks/useOcaAuth"
|
||||
import { useScrollableList } from "../hooks/useScrollableList"
|
||||
import { type DetectedSources, detectImportSources, type ImportSource } from "../utils/import-configs"
|
||||
import { isEnterKey, isMouseEscapeSequence } from "../utils/input"
|
||||
import { applyBedrockConfig, applyProviderConfig } from "../utils/provider-config"
|
||||
import { applyBedrockConfig, applyProviderConfig, applyVertexConfig } from "../utils/provider-config"
|
||||
import { useValidProviders } from "../utils/providers"
|
||||
import { ApiKeyInput } from "./ApiKeyInput"
|
||||
import { StaticRobotFrame } from "./AsciiMotionCli"
|
||||
import { BedrockCustomModelFlow } from "./BedrockCustomModelFlow"
|
||||
import { type BedrockConfig, BedrockSetup } from "./BedrockSetup"
|
||||
import { type VertexConfig, VertexSetup } from "./VertexSetup"
|
||||
import {
|
||||
FeaturedModelPicker,
|
||||
getFeaturedModelAtIndex,
|
||||
@@ -52,6 +53,7 @@ type AuthStep =
|
||||
| "cline_model"
|
||||
| "openai_codex_auth"
|
||||
| "bedrock"
|
||||
| "vertex"
|
||||
| "import"
|
||||
| "bedrock_custom"
|
||||
|
||||
@@ -177,6 +179,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
const [importSources, setImportSources] = useState<DetectedSources>({ codex: false, opencode: false })
|
||||
const [importSource, setImportSource] = useState<ImportSource | null>(null)
|
||||
const [bedrockConfig, setBedrockConfig] = useState<BedrockConfig | null>(null)
|
||||
const [vertexConfig, setVertexConfig] = useState<VertexConfig | null>(null)
|
||||
|
||||
// OCA auth hook - enabled when step is oca_auth
|
||||
const handleOcaAuthSuccess = useCallback(async () => {
|
||||
@@ -369,16 +372,23 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
const handleProviderSelect = useCallback(
|
||||
(value: string) => {
|
||||
setSelectedProvider(value)
|
||||
if (value === "oca") {
|
||||
// Show employee check screen before starting auth
|
||||
setStep("oca_employee_check")
|
||||
} else if (value === "openai-codex") {
|
||||
setStep("openai_codex_auth")
|
||||
startOpenAiCodexAuth()
|
||||
} else if (value === "bedrock") {
|
||||
setStep("bedrock")
|
||||
} else {
|
||||
setStep("apikey")
|
||||
switch (value) {
|
||||
case "oca":
|
||||
setStep("oca_employee_check")
|
||||
break
|
||||
case "openai-codex":
|
||||
setStep("openai_codex_auth")
|
||||
startOpenAiCodexAuth()
|
||||
break
|
||||
case "bedrock":
|
||||
setStep("bedrock")
|
||||
break
|
||||
case "vertex":
|
||||
setStep("vertex")
|
||||
break
|
||||
default:
|
||||
setStep("apikey")
|
||||
break
|
||||
}
|
||||
},
|
||||
[startOcaAuth, startOpenAiCodexAuth],
|
||||
@@ -434,6 +444,12 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
modelId: model,
|
||||
controller,
|
||||
})
|
||||
} else if (selectedProvider === "vertex" && vertexConfig) {
|
||||
await applyVertexConfig({
|
||||
vertexConfig,
|
||||
modelId: model,
|
||||
controller,
|
||||
})
|
||||
} else {
|
||||
await applyProviderConfig({
|
||||
providerId: selectedProvider,
|
||||
@@ -454,7 +470,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setStep("error")
|
||||
}
|
||||
},
|
||||
[selectedProvider, apiKey, bedrockConfig, controller],
|
||||
[selectedProvider, apiKey, bedrockConfig, vertexConfig, controller],
|
||||
)
|
||||
|
||||
const handleModelIdSubmit = useCallback(
|
||||
@@ -502,6 +518,11 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setStep("modelid")
|
||||
}, [])
|
||||
|
||||
const handleVertexComplete = useCallback((config: VertexConfig) => {
|
||||
setVertexConfig(config)
|
||||
setStep("modelid")
|
||||
}, [])
|
||||
|
||||
const handleImportComplete = useCallback(() => {
|
||||
setStep("success")
|
||||
}, [])
|
||||
@@ -567,18 +588,17 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setApiKey("")
|
||||
setStep("provider")
|
||||
break
|
||||
case "modelid":
|
||||
case "modelid": {
|
||||
setModelId("")
|
||||
// Go back to cline_model if we came from there (Cline provider)
|
||||
if (selectedProvider === "cline") {
|
||||
setStep("cline_model")
|
||||
} else if (selectedProvider === "bedrock") {
|
||||
// Bedrock skips the API key step — go back to Bedrock setup
|
||||
setStep("bedrock")
|
||||
} else {
|
||||
setStep("apikey")
|
||||
// Each provider has a different step before model selection
|
||||
const prevStep: Record<string, AuthStep> = {
|
||||
cline: "cline_model",
|
||||
bedrock: "bedrock",
|
||||
vertex: "vertex",
|
||||
}
|
||||
setStep(prevStep[selectedProvider] ?? "apikey")
|
||||
break
|
||||
}
|
||||
case "baseurl":
|
||||
setBaseUrl("")
|
||||
setStep("modelid")
|
||||
@@ -604,6 +624,10 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setBedrockConfig(null)
|
||||
setStep("provider")
|
||||
break
|
||||
case "vertex":
|
||||
setVertexConfig(null)
|
||||
setStep("provider")
|
||||
break
|
||||
case "import":
|
||||
setImportSource(null)
|
||||
setStep("menu")
|
||||
@@ -786,6 +810,18 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
/>
|
||||
)
|
||||
|
||||
case "vertex":
|
||||
return (
|
||||
<VertexSetup
|
||||
isActive={step === "vertex"}
|
||||
onCancel={() => {
|
||||
setVertexConfig(null)
|
||||
setStep("provider")
|
||||
}}
|
||||
onComplete={handleVertexComplete}
|
||||
/>
|
||||
)
|
||||
|
||||
case "bedrock_custom":
|
||||
return (
|
||||
<BedrockCustomModelFlow
|
||||
@@ -837,6 +873,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
"cline_model",
|
||||
"openai_codex_auth",
|
||||
"bedrock",
|
||||
"vertex",
|
||||
"error",
|
||||
].includes(step)
|
||||
|
||||
|
||||
@@ -28,10 +28,11 @@ import { useStdinContext } from "../context/StdinContext"
|
||||
import { useClineFeaturedModels } from "../hooks/useClineFeaturedModels"
|
||||
import { useOcaAuth } from "../hooks/useOcaAuth"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
import { applyBedrockConfig, applyProviderConfig } from "../utils/provider-config"
|
||||
import { applyBedrockConfig, applyProviderConfig, applyVertexConfig } from "../utils/provider-config"
|
||||
import { ApiKeyInput } from "./ApiKeyInput"
|
||||
import { BedrockCustomModelFlow } from "./BedrockCustomModelFlow"
|
||||
import { type BedrockConfig, BedrockSetup } from "./BedrockSetup"
|
||||
import { type VertexConfig, VertexSetup } from "./VertexSetup"
|
||||
import { Checkbox } from "./Checkbox"
|
||||
import {
|
||||
FeaturedModelPicker,
|
||||
@@ -167,6 +168,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
const [isPickingLanguage, setIsPickingLanguage] = useState(false)
|
||||
const [isEnteringApiKey, setIsEnteringApiKey] = useState(false)
|
||||
const [isConfiguringBedrock, setIsConfiguringBedrock] = useState(false)
|
||||
const [isConfiguringVertex, setIsConfiguringVertex] = useState(false)
|
||||
const [isWaitingForCodexAuth, setIsWaitingForCodexAuth] = useState(false)
|
||||
const [isShowingOcaEmployeeCheck, setIsShowingOcaEmployeeCheck] = useState(false)
|
||||
const [codexAuthError, setCodexAuthError] = useState<string | null>(null)
|
||||
@@ -1150,6 +1152,14 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
return
|
||||
}
|
||||
|
||||
// Special handling for Vertex - needs project ID and region
|
||||
if (providerId === "vertex") {
|
||||
setPendingProvider(providerId)
|
||||
setIsPickingProvider(false)
|
||||
setIsConfiguringVertex(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this provider needs an API key
|
||||
const keyField = ProviderToApiKeyMap[providerId as ApiProvider]
|
||||
if (keyField) {
|
||||
@@ -1205,6 +1215,21 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
[controller, refreshModelIds],
|
||||
)
|
||||
|
||||
// Handle Vertex configuration complete
|
||||
const handleVertexComplete = useCallback(
|
||||
(vertexConfig: VertexConfig) => {
|
||||
// Update UI state first for responsiveness
|
||||
setProvider("vertex")
|
||||
refreshModelIds()
|
||||
setIsConfiguringVertex(false)
|
||||
setPendingProvider(null)
|
||||
|
||||
// Apply config and rebuild API handler in background
|
||||
applyVertexConfig({ vertexConfig, controller })
|
||||
},
|
||||
[controller, refreshModelIds],
|
||||
)
|
||||
|
||||
// Handle saving edited value
|
||||
const handleSave = useCallback(() => {
|
||||
const item = items[selectedIndex]
|
||||
@@ -1433,7 +1458,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
return
|
||||
}
|
||||
},
|
||||
{ isActive: isRawModeSupported && !isEnteringApiKey && !isConfiguringBedrock && !isShowingOcaEmployeeCheck },
|
||||
{ isActive: isRawModeSupported && !isEnteringApiKey && !isConfiguringBedrock && !isConfiguringVertex && !isShowingOcaEmployeeCheck },
|
||||
)
|
||||
|
||||
// Render content
|
||||
@@ -1484,6 +1509,19 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
if (isConfiguringVertex) {
|
||||
return (
|
||||
<VertexSetup
|
||||
isActive={isConfiguringVertex}
|
||||
onCancel={() => {
|
||||
setIsConfiguringVertex(false)
|
||||
setPendingProvider(null)
|
||||
}}
|
||||
onComplete={handleVertexComplete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (isWaitingForCodexAuth) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
@@ -1814,6 +1852,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
isPickingLanguage ||
|
||||
isEnteringApiKey ||
|
||||
isConfiguringBedrock ||
|
||||
isConfiguringVertex ||
|
||||
isWaitingForCodexAuth ||
|
||||
!!codexAuthError ||
|
||||
isPickingOrganization ||
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import VertexData from "@shared/providers/vertex.json"
|
||||
import { Box, Text, useInput } from "ink"
|
||||
import React, { useCallback, useMemo, useState } from "react"
|
||||
import { COLORS } from "../constants/colors"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { useScrollableList } from "../hooks/useScrollableList"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
|
||||
type VertexStep = "project_id" | "region"
|
||||
|
||||
export interface VertexConfig {
|
||||
vertexProjectId: string
|
||||
vertexRegion: string
|
||||
}
|
||||
|
||||
interface VertexSetupProps {
|
||||
isActive: boolean
|
||||
onComplete: (config: VertexConfig) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const VERTEX_REGIONS = VertexData.regions
|
||||
const REGION_ROWS = 8
|
||||
|
||||
/**
|
||||
* Inline text input for the project ID field
|
||||
*/
|
||||
const ProjectIdInput: React.FC<{
|
||||
label: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
onSubmit: () => void
|
||||
onCancel: () => void
|
||||
isActive: boolean
|
||||
placeholder?: string
|
||||
hint?: string
|
||||
}> = ({ label, value, onChange, onSubmit, onCancel, isActive, placeholder, hint }) => {
|
||||
const { isRawModeSupported } = useStdinContext()
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
if (isMouseEscapeSequence(input)) return
|
||||
if (key.escape) {
|
||||
onCancel()
|
||||
} else if (key.return) {
|
||||
onSubmit()
|
||||
} else if (key.backspace || key.delete) {
|
||||
onChange(value.slice(0, -1))
|
||||
} else if (input && !key.ctrl && !key.meta) {
|
||||
onChange(value + input)
|
||||
}
|
||||
},
|
||||
{ isActive: isActive && isRawModeSupported },
|
||||
)
|
||||
|
||||
const description = hint || (placeholder ? `e.g. ${placeholder}` : undefined)
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text color="white">{label}</Text>
|
||||
{description && <Text color="gray">{description}</Text>}
|
||||
<Text> </Text>
|
||||
<Box>
|
||||
<Text color="white">{value}</Text>
|
||||
<Text inverse> </Text>
|
||||
</Box>
|
||||
<Text> </Text>
|
||||
<Text color="gray">Enter to continue, Esc to go back</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const VertexSetup: React.FC<VertexSetupProps> = ({ isActive, onComplete, onCancel }) => {
|
||||
const { isRawModeSupported } = useStdinContext()
|
||||
|
||||
const [step, setStep] = useState<VertexStep>("project_id")
|
||||
const [projectId, setProjectId] = useState("")
|
||||
const [regionSearch, setRegionSearch] = useState("")
|
||||
const [regionIndex, setRegionIndex] = useState(0)
|
||||
|
||||
const filteredRegions = useMemo(() => {
|
||||
const search = regionSearch.toLowerCase().trim()
|
||||
if (!search) {
|
||||
return VERTEX_REGIONS
|
||||
}
|
||||
return VERTEX_REGIONS.filter((r) => r.toLowerCase().includes(search))
|
||||
}, [regionSearch])
|
||||
|
||||
const {
|
||||
visibleStart: regionVisibleStart,
|
||||
visibleCount: regionVisibleCount,
|
||||
showTopIndicator: showRegionTop,
|
||||
showBottomIndicator: showRegionBottom,
|
||||
} = useScrollableList(filteredRegions.length, regionIndex, REGION_ROWS)
|
||||
|
||||
const visibleRegions = useMemo(
|
||||
() => filteredRegions.slice(regionVisibleStart, regionVisibleStart + regionVisibleCount),
|
||||
[filteredRegions, regionVisibleStart, regionVisibleCount],
|
||||
)
|
||||
|
||||
const goBack = useCallback(() => {
|
||||
switch (step) {
|
||||
case "project_id":
|
||||
onCancel()
|
||||
break
|
||||
case "region":
|
||||
setStep("project_id")
|
||||
break
|
||||
}
|
||||
}, [step, onCancel])
|
||||
|
||||
const getSelectedRegion = useCallback(() => {
|
||||
if (filteredRegions.length > 0 && regionIndex >= 0 && regionIndex < filteredRegions.length) {
|
||||
return filteredRegions[regionIndex]
|
||||
}
|
||||
return regionSearch.trim() || "us-east5"
|
||||
}, [filteredRegions, regionIndex, regionSearch])
|
||||
|
||||
const finish = useCallback(() => {
|
||||
const config: VertexConfig = {
|
||||
vertexProjectId: projectId.trim(),
|
||||
vertexRegion: getSelectedRegion(),
|
||||
}
|
||||
onComplete(config)
|
||||
}, [projectId, getSelectedRegion, onComplete])
|
||||
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
if (isMouseEscapeSequence(input)) return
|
||||
|
||||
if (step === "region") {
|
||||
if (key.escape) {
|
||||
goBack()
|
||||
} else if (key.upArrow && filteredRegions.length > 0) {
|
||||
setRegionIndex((prev) => (prev > 0 ? prev - 1 : filteredRegions.length - 1))
|
||||
} else if (key.downArrow && filteredRegions.length > 0) {
|
||||
setRegionIndex((prev) => (prev < filteredRegions.length - 1 ? prev + 1 : 0))
|
||||
} else if (key.return && (filteredRegions.length > 0 || regionSearch.trim())) {
|
||||
finish()
|
||||
} else if (key.backspace || key.delete) {
|
||||
setRegionSearch((prev) => prev.slice(0, -1))
|
||||
setRegionIndex(0)
|
||||
} else if (input && !key.ctrl && !key.meta) {
|
||||
setRegionSearch((prev) => prev + input)
|
||||
setRegionIndex(0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ isActive: isActive && isRawModeSupported && step === "region" },
|
||||
)
|
||||
|
||||
if (step === "project_id") {
|
||||
return (
|
||||
<ProjectIdInput
|
||||
hint="Your Google Cloud project ID (e.g. my-gcp-project)"
|
||||
isActive={isActive}
|
||||
label="Google Cloud Project ID"
|
||||
onCancel={goBack}
|
||||
onChange={setProjectId}
|
||||
onSubmit={() => {
|
||||
if (projectId.trim()) setStep("region")
|
||||
}}
|
||||
placeholder="my-gcp-project"
|
||||
value={projectId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if (step === "region") {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text color="white">Google Cloud Region</Text>
|
||||
<Text> </Text>
|
||||
<Box>
|
||||
<Text color="gray">Search or enter custom region: </Text>
|
||||
<Text color="white">{regionSearch}</Text>
|
||||
<Text inverse> </Text>
|
||||
</Box>
|
||||
<Text> </Text>
|
||||
{showRegionTop && <Text color="gray">... {regionVisibleStart} more above</Text>}
|
||||
{visibleRegions.map((region, i) => {
|
||||
const actualIndex = regionVisibleStart + i
|
||||
return (
|
||||
<Box key={region}>
|
||||
<Text color={actualIndex === regionIndex ? COLORS.primaryBlue : undefined}>
|
||||
{actualIndex === regionIndex ? "❯ " : " "}
|
||||
{region}
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
{showRegionBottom && (
|
||||
<Text color="gray">
|
||||
... {filteredRegions.length - regionVisibleStart - regionVisibleCount} more below
|
||||
</Text>
|
||||
)}
|
||||
<Text> </Text>
|
||||
<Text color="gray">Type to search, arrows to navigate, Enter to select, Esc to go back</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ describe("CLI Commands", () => {
|
||||
.option("-k, --apikey <key>", "API key")
|
||||
.option("-m, --modelid <id>", "Model ID")
|
||||
.option("-b, --baseurl <url>", "Base URL")
|
||||
.option("--vertex-project-id <id>", "Google Cloud Project ID")
|
||||
.option("--vertex-region <region>", "Google Cloud Region")
|
||||
.option("-v, --verbose", "Verbose output")
|
||||
.option("-c, --cwd <path>", "Working directory")
|
||||
.option("--config <path>", "Configuration directory")
|
||||
@@ -340,6 +342,30 @@ describe("CLI Commands", () => {
|
||||
expect(authCmd.opts().apikey).toBe("key123")
|
||||
expect(authCmd.opts().modelid).toBe("claude-sonnet-4-20250514")
|
||||
})
|
||||
|
||||
it("should parse --vertex-project-id option", () => {
|
||||
const authCmd = program.commands.find((c) => c.name() === "auth")!
|
||||
const args = ["--vertex-project-id", "my-gcp-project"]
|
||||
authCmd.parse(args, { from: "user" })
|
||||
expect(authCmd.opts().vertexProjectId).toBe("my-gcp-project")
|
||||
})
|
||||
|
||||
it("should parse --vertex-region option", () => {
|
||||
const authCmd = program.commands.find((c) => c.name() === "auth")!
|
||||
const args = ["--vertex-region", "us-east5"]
|
||||
authCmd.parse(args, { from: "user" })
|
||||
expect(authCmd.opts().vertexRegion).toBe("us-east5")
|
||||
})
|
||||
|
||||
it("should parse vertex quick setup flags together", () => {
|
||||
const authCmd = program.commands.find((c) => c.name() === "auth")!
|
||||
const args = ["-p", "vertex", "-m", "gemini-3-flash-preview", "--vertex-project-id", "my-project", "--vertex-region", "us-east5"]
|
||||
authCmd.parse(args, { from: "user" })
|
||||
expect(authCmd.opts().provider).toBe("vertex")
|
||||
expect(authCmd.opts().modelid).toBe("gemini-3-flash-preview")
|
||||
expect(authCmd.opts().vertexProjectId).toBe("my-project")
|
||||
expect(authCmd.opts().vertexRegion).toBe("us-east5")
|
||||
})
|
||||
})
|
||||
|
||||
describe("mcp command", () => {
|
||||
|
||||
+41
-4
@@ -715,9 +715,16 @@ async function showConfig(options: { config?: string }) {
|
||||
*/
|
||||
async function performQuickAuthSetup(
|
||||
ctx: CliContext,
|
||||
options: { provider: string; apikey: string; modelid: string; baseurl?: string },
|
||||
options: {
|
||||
provider: string
|
||||
apikey: string
|
||||
modelid: string
|
||||
baseurl?: string
|
||||
vertexProjectId?: string
|
||||
vertexRegion?: string
|
||||
},
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const { provider, apikey, modelid, baseurl } = options
|
||||
const { provider, apikey, modelid, baseurl, vertexProjectId, vertexRegion } = options
|
||||
|
||||
const normalizedProvider = provider.toLowerCase().trim()
|
||||
|
||||
@@ -733,6 +740,24 @@ async function performQuickAuthSetup(
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedProvider === "vertex") {
|
||||
if (!modelid || !vertexProjectId || !vertexRegion) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Vertex provider requires --modelid, --vertex-project-id, and --vertex-region flags.",
|
||||
}
|
||||
}
|
||||
const { applyVertexConfig } = await import("./utils/provider-config")
|
||||
await applyVertexConfig({
|
||||
vertexConfig: { vertexProjectId, vertexRegion },
|
||||
modelId: modelid,
|
||||
controller: ctx.controller,
|
||||
})
|
||||
StateManager.get().setGlobalState("welcomeViewCompleted", true)
|
||||
await StateManager.get().flushPendingState()
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
if (baseurl && !["openai", "openai-native"].includes(normalizedProvider)) {
|
||||
return { success: false, error: "Base URL is only supported for OpenAI and OpenAI-compatible providers" }
|
||||
}
|
||||
@@ -758,13 +783,21 @@ async function runAuth(options: {
|
||||
apikey?: string
|
||||
modelid?: string
|
||||
baseurl?: string
|
||||
vertexProjectId?: string
|
||||
vertexRegion?: string
|
||||
verbose?: boolean
|
||||
cwd?: string
|
||||
config?: string
|
||||
}) {
|
||||
const ctx = await initializeCli({ ...options, enableAuth: true })
|
||||
|
||||
const hasQuickSetupFlags = options.provider && options.apikey && options.modelid
|
||||
// Vertex uses project-id + region instead of API key (no apikey required).
|
||||
// Treat any vertex provider invocation with partial flags as a quick-setup attempt
|
||||
// so the error path inside performQuickAuthSetup is reached instead of silently
|
||||
// falling to interactive mode (which hangs in non-TTY/CI environments).
|
||||
const isVertexProvider = options.provider?.toLowerCase() === "vertex"
|
||||
const isVertexAttempt = isVertexProvider && (!!options.modelid || !!options.vertexProjectId || !!options.vertexRegion)
|
||||
const hasQuickSetupFlags = isVertexAttempt || (options.provider && options.apikey && options.modelid)
|
||||
|
||||
telemetryService.captureHostEvent("auth_command", hasQuickSetupFlags ? "quick_setup" : "interactive")
|
||||
|
||||
@@ -772,9 +805,11 @@ async function runAuth(options: {
|
||||
if (hasQuickSetupFlags) {
|
||||
const result = await performQuickAuthSetup(ctx, {
|
||||
provider: options.provider!,
|
||||
apikey: options.apikey!,
|
||||
apikey: options.apikey || "",
|
||||
modelid: options.modelid!,
|
||||
baseurl: options.baseurl,
|
||||
vertexProjectId: options.vertexProjectId,
|
||||
vertexRegion: options.vertexRegion,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
@@ -871,6 +906,8 @@ program
|
||||
.option("-k, --apikey <key>", "API key for the provider")
|
||||
.option("-m, --modelid <id>", "Model ID to configure (e.g., gpt-4o, claude-sonnet-4-6, kimi-k2.5)")
|
||||
.option("-b, --baseurl <url>", "Base URL (optional, only for openai provider)")
|
||||
.option("--vertex-project-id <id>", "Google Cloud Project ID (for vertex provider)")
|
||||
.option("--vertex-region <region>", "Google Cloud Region (for vertex provider)")
|
||||
.option("-v, --verbose", "Show verbose output")
|
||||
.option("-c, --cwd <path>", "Working directory for the task")
|
||||
.option("--config <path>", "Path to Cline configuration directory")
|
||||
|
||||
@@ -12,6 +12,7 @@ import { refreshVercelAiGatewayModels } from "@/core/controller/models/refreshVe
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import type { BedrockConfig } from "../components/BedrockSetup"
|
||||
import { getDefaultModelId } from "../components/ModelPicker"
|
||||
import type { VertexConfig } from "../components/VertexSetup"
|
||||
|
||||
export interface ApplyProviderConfigOptions {
|
||||
providerId: string
|
||||
@@ -150,3 +151,44 @@ export async function applyBedrockConfig(options: ApplyBedrockConfigOptions): Pr
|
||||
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
|
||||
}
|
||||
}
|
||||
|
||||
export interface ApplyVertexConfigOptions {
|
||||
vertexConfig: VertexConfig
|
||||
modelId?: string
|
||||
controller?: Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Vertex AI provider configuration to state.
|
||||
* Handles GCP-specific fields (project ID, region).
|
||||
* Authentication uses Google Application Default Credentials (ADC).
|
||||
*/
|
||||
export async function applyVertexConfig(options: ApplyVertexConfigOptions): Promise<void> {
|
||||
const { vertexConfig, modelId, controller } = options
|
||||
const stateManager = StateManager.get()
|
||||
|
||||
const config: Record<string, string> = {
|
||||
actModeApiProvider: "vertex",
|
||||
planModeApiProvider: "vertex",
|
||||
vertexProjectId: vertexConfig.vertexProjectId,
|
||||
vertexRegion: vertexConfig.vertexRegion,
|
||||
}
|
||||
|
||||
const finalModelId = modelId || getDefaultModelId("vertex")
|
||||
if (finalModelId) {
|
||||
const actModelKey = getProviderModelIdKey("vertex" as ApiProvider, "act")
|
||||
const planModelKey = getProviderModelIdKey("vertex" as ApiProvider, "plan")
|
||||
if (actModelKey) config[actModelKey] = finalModelId
|
||||
if (planModelKey) config[planModelKey] = finalModelId
|
||||
}
|
||||
|
||||
stateManager.setApiConfiguration(config)
|
||||
await stateManager.flushPendingState()
|
||||
|
||||
if (controller?.task) {
|
||||
const currentMode = stateManager.getGlobalSettingsKey("mode")
|
||||
const apiConfig = stateManager.getApiConfiguration()
|
||||
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user