Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/cloud-provider-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---

Support structured AWS access keys and Google Cloud service-account JSON when connecting Bedrock and Vertex AI in VS Code.
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ interface ViewState {

type Prompt = NonNullable<ProviderAuthMethod["prompts"]>[number]

function fallbackMethods(label: string): ProviderAuthMethod[] {
return [{ type: "api", label }]
}

function formatError(value: unknown, fallback: string): string {
if (value && typeof value === "object" && "message" in value) {
const message = (value as { message?: unknown }).message
Expand Down Expand Up @@ -70,15 +66,77 @@ const ProviderConnectDialog: Component<ProviderConnectDialogProps> = (props) =>
const item = createMemo(() => provider.providers()[props.providerID])
const name = () => item()?.name ?? props.providerID
const methods = createMemo<ProviderAuthMethod[]>(() => {
const list =
provider.authMethods()[props.providerID] ?? fallbackMethods(language.t("provider.connect.method.apiKey"))
const fallback = (): ProviderAuthMethod[] => {
if (props.providerID === "amazon-bedrock") {
return [
{
type: "api",
label: language.t("provider.connect.bedrock.method.accessKeys"),
prompts: [
{
type: "text",
key: "secretAccessKey",
message: language.t("provider.connect.bedrock.secretAccessKey.label"),
placeholder: language.t("provider.connect.bedrock.secretAccessKey.placeholder"),
},
{
type: "text",
key: "sessionToken",
message: language.t("provider.connect.bedrock.sessionToken.label"),
placeholder: language.t("provider.connect.bedrock.sessionToken.placeholder"),
},
{
type: "text",
key: "region",
message: language.t("provider.connect.bedrock.region.label"),
placeholder: language.t("provider.connect.bedrock.region.placeholder"),
},
],
},
{ type: "api", label: language.t("provider.connect.bedrock.method.apiKey") },
]
}
if (props.providerID === "google-vertex") {
return [
{
type: "api",
label: language.t("provider.connect.vertex.method.serviceAccount"),
prompts: [
{
type: "text",
key: "project",
message: language.t("provider.connect.vertex.project.label"),
placeholder: language.t("provider.connect.vertex.project.placeholder"),
},
{
type: "text",
key: "location",
message: language.t("provider.connect.vertex.location.label"),
placeholder: language.t("provider.connect.vertex.location.placeholder"),
},
],
},
]
}
return [{ type: "api", label: language.t("provider.connect.method.apiKey") }]
}
const list = provider.authMethods()[props.providerID] ?? fallback()
if (props.oauthOnly) return list.filter((item) => item.type === "oauth")
return list
})
const method = createMemo(() => {
const index = state.methodIndex
return index === undefined ? undefined : methods()[index]
})
const bedrockKeys = () =>
props.providerID === "amazon-bedrock" && method()?.prompts?.some((prompt) => prompt.key === "secretAccessKey")
const vertexCredentials = () => props.providerID === "google-vertex" && method()?.type === "api"

function optional(prompt: Prompt) {
if (bedrockKeys() && prompt.key === "sessionToken") return true
if (vertexCredentials() && prompt.key === "project") return true
return false
}

function promptLabel(prompt: Prompt) {
if (props.providerID === "azure" && prompt.key === "endpointType") {
Expand Down Expand Up @@ -294,6 +352,12 @@ const ProviderConnectDialog: Component<ProviderConnectDialogProps> = (props) =>
const apiKeyOptional = () => isLocalProviderOptionalApiKey(props.providerID)

function apiKeyDescription() {
if (bedrockKeys()) {
return language.t("provider.connect.bedrock.description")
}
if (vertexCredentials()) {
return language.t("provider.connect.vertex.description")
}
if (props.providerID === ATOMIC_CHAT_PROVIDER_KEY) {
return language.t("provider.connect.atomicChat.description")
}
Expand All @@ -304,33 +368,76 @@ const ProviderConnectDialog: Component<ProviderConnectDialogProps> = (props) =>
}

function apiKeyLabel() {
if (bedrockKeys()) {
return language.t("provider.connect.bedrock.accessKeyId.label")
}
if (vertexCredentials()) {
return language.t("provider.connect.vertex.credentials.label")
}
if (apiKeyOptional()) {
return language.t("provider.connect.apiKey.label.optional", { provider: name() })
}
return language.t("provider.connect.apiKey.label", { provider: name() })
}

function apiKeyRequired() {
if (bedrockKeys()) return language.t("provider.connect.bedrock.accessKeyId.required")
if (vertexCredentials()) return language.t("provider.connect.vertex.credentials.required")
return language.t("provider.connect.apiKey.required")
}

function submit(e: SubmitEvent) {
e.preventDefault()
const trimmed = value().trim()
const apiKey = trimmed || (apiKeyOptional() ? LOCAL_PROVIDER_API_KEY_PLACEHOLDER : "")
if (!apiKey) {
setState({ ...state, error: language.t("provider.connect.apiKey.required"), field: "apiKey" })
setState({ ...state, error: apiKeyRequired(), field: "apiKey" })
return
}
const serviceAccount = (() => {
if (!vertexCredentials()) return undefined
try {
const parsed = JSON.parse(apiKey) as Record<string, unknown>
if (parsed.type !== "service_account") return undefined
if (typeof parsed.client_email !== "string" || !parsed.client_email.trim()) return undefined
if (typeof parsed.private_key !== "string" || !parsed.private_key.trim()) return undefined
return parsed
} catch {
return undefined
}
})()
if (vertexCredentials() && !serviceAccount) {
setState({ ...state, error: language.t("provider.connect.vertex.credentials.invalid"), field: "apiKey" })
return
}
const metadata: Record<string, string> = {}
for (const prompt of prompts()) {
const field = (fields[prompt.key] ?? "").trim()
if (!field) {
if (!field && !optional(prompt)) {
setState({
...state,
error: language.t("provider.connect.prompt.required", { field: promptLabel(prompt) }),
field: prompt.key,
})
return
}
if (!field) continue
metadata[prompt.key] = field
}
if (
vertexCredentials() &&
!metadata.project &&
!(typeof serviceAccount?.project_id === "string" && serviceAccount.project_id.trim())
) {
setState({
...state,
error: language.t("provider.connect.vertex.project.required"),
field: "project",
})
return
}
if (bedrockKeys()) metadata.authType = "accessKey"
if (vertexCredentials()) metadata.authType = "serviceAccount"
connect(apiKey, Object.keys(metadata).length > 0 ? metadata : undefined)
}

Expand All @@ -343,12 +450,23 @@ const ProviderConnectDialog: Component<ProviderConnectDialogProps> = (props) =>
<div class="provider-connect-body">{apiKeyDescription()}</div>
<TextField
autofocus
type="password"
type={vertexCredentials() ? "text" : "password"}
Comment thread
chrarnoldus marked this conversation as resolved.
multiline={vertexCredentials()}
style={{
"max-height": vertexCredentials() ? "min(240px, 35vh)" : undefined,
"overflow-y": vertexCredentials() ? "auto" : undefined,
}}
autocomplete="off"
spellcheck={false}
Comment thread
chrarnoldus marked this conversation as resolved.
label={apiKeyLabel()}
placeholder={
apiKeyOptional()
? language.t("provider.connect.apiKey.placeholder.optional")
: language.t("provider.connect.apiKey.placeholder")
bedrockKeys()
? language.t("provider.connect.bedrock.accessKeyId.placeholder")
: vertexCredentials()
? language.t("provider.connect.vertex.credentials.placeholder")
: apiKeyOptional()
? language.t("provider.connect.apiKey.placeholder.optional")
: language.t("provider.connect.apiKey.placeholder")
}
value={value()}
onChange={setValue}
Expand All @@ -360,7 +478,9 @@ const ProviderConnectDialog: Component<ProviderConnectDialogProps> = (props) =>
<Switch>
<Match when={prompt.type === "text"}>
<TextField
type="text"
type={bedrockKeys() && ["secretAccessKey", "sessionToken"].includes(prompt.key) ? "password" : "text"}
autocomplete="off"
spellcheck={false}
label={promptLabel(prompt)}
placeholder={promptPlaceholder(prompt)}
value={fields[prompt.key] ?? ""}
Expand Down
3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/ar.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/br.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/bs.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/cloud-provider.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/da.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { dict as en } from "./en"

type Keys = keyof typeof en

import { cloudProviderDict } from "./cloud-provider"

export const anacondaDesktopDict = {
"provider.anaconda.title.connect": "Anaconda Desktop verbinden",
"provider.anaconda.title.manage": "Anaconda Desktop verwalten",
Expand Down Expand Up @@ -59,6 +61,7 @@ export const anacondaDesktopDict = {

export const dict = {
...anacondaDesktopDict,
...cloudProviderDict,

"command.provider.connect": "Anbieter verbinden",

Expand Down
3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/en.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { cloudProviderDict } from "./cloud-provider"

export const anacondaDesktopDict = {
"provider.anaconda.title.connect": "Connect Anaconda Desktop",
"provider.anaconda.title.manage": "Manage Anaconda Desktop",
Expand Down Expand Up @@ -52,6 +54,7 @@ export const anacondaDesktopDict = {

export const dict = {
...anacondaDesktopDict,
...cloudProviderDict,

"command.provider.connect": "Connect provider",

Expand Down
3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/es.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/fa.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/fr.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/it.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading