mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Nighttrek/bedrock credential manager (#1667)
* added initial AWS bedrock support * fixed formatting * updated to fix persistence * added changeset
This commit is contained in:
parent
99bba4af27
commit
076b1e39e6
5 changed files with 106 additions and 37 deletions
5
.changeset/tasty-readers-move.md
Normal file
5
.changeset/tasty-readers-move.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Added support for AWS provider profiles using the AWS CLI to make the profile. enabling long lived connections to AWS bedrock
|
||||
|
|
@ -3,6 +3,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
|||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { fromIni } from "@aws-sdk/credential-providers"
|
||||
|
||||
// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
|
||||
export class AwsBedrockHandler implements ApiHandler {
|
||||
|
|
@ -11,17 +12,31 @@ export class AwsBedrockHandler implements ApiHandler {
|
|||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = new AnthropicBedrock({
|
||||
// Authenticate by either providing the keys below or use the default AWS credential providers, such as
|
||||
// using ~/.aws/credentials or the "AWS_SECRET_ACCESS_KEY" and "AWS_ACCESS_KEY_ID" environment variables.
|
||||
...(this.options.awsAccessKey ? { awsAccessKey: this.options.awsAccessKey } : {}),
|
||||
...(this.options.awsSecretKey ? { awsSecretKey: this.options.awsSecretKey } : {}),
|
||||
...(this.options.awsSessionToken ? { awsSessionToken: this.options.awsSessionToken } : {}),
|
||||
|
||||
// awsRegion changes the aws region to which the request is made. By default, we read AWS_REGION,
|
||||
// and if that's not present, we default to us-east-1. Note that we do not read ~/.aws/config for the region.
|
||||
awsRegion: this.options.awsRegion,
|
||||
})
|
||||
const clientConfig: any = {
|
||||
awsRegion: this.options.awsRegion || "us-east-1",
|
||||
}
|
||||
|
||||
if (this.options.awsUseProfile) {
|
||||
// Use profile-based credentials if enabled
|
||||
if (this.options.awsProfile) {
|
||||
clientConfig.credentials = fromIni({
|
||||
profile: this.options.awsProfile,
|
||||
})
|
||||
} else {
|
||||
// Use default profile if no specific profile is set
|
||||
clientConfig.credentials = fromIni()
|
||||
}
|
||||
} else if (this.options.awsAccessKey && this.options.awsSecretKey) {
|
||||
// Use direct credentials if provided
|
||||
clientConfig.awsAccessKey = this.options.awsAccessKey
|
||||
clientConfig.awsSecretKey = this.options.awsSecretKey
|
||||
if (this.options.awsSessionToken) {
|
||||
clientConfig.awsSessionToken = this.options.awsSessionToken
|
||||
}
|
||||
}
|
||||
|
||||
this.client = new AnthropicBedrock(clientConfig)
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ type GlobalStateKey =
|
|||
| "apiModelId"
|
||||
| "awsRegion"
|
||||
| "awsUseCrossRegionInference"
|
||||
| "awsProfile"
|
||||
| "awsUseProfile"
|
||||
| "vertexProjectId"
|
||||
| "vertexRegion"
|
||||
| "lastShownAnnouncementId"
|
||||
|
|
@ -431,6 +433,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
awsSessionToken,
|
||||
awsRegion,
|
||||
awsUseCrossRegionInference,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
|
|
@ -465,6 +469,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.storeSecret("awsSessionToken", awsSessionToken)
|
||||
await this.updateGlobalState("awsRegion", awsRegion)
|
||||
await this.updateGlobalState("awsUseCrossRegionInference", awsUseCrossRegionInference)
|
||||
await this.updateGlobalState("awsProfile", awsProfile)
|
||||
await this.updateGlobalState("awsUseProfile", awsUseProfile)
|
||||
await this.updateGlobalState("vertexProjectId", vertexProjectId)
|
||||
await this.updateGlobalState("vertexRegion", vertexRegion)
|
||||
await this.updateGlobalState("openAiBaseUrl", openAiBaseUrl)
|
||||
|
|
@ -1364,6 +1370,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
awsSessionToken,
|
||||
awsRegion,
|
||||
awsUseCrossRegionInference,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
|
|
@ -1409,6 +1417,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.getSecret("awsSessionToken") as Promise<string | undefined>,
|
||||
this.getGlobalState("awsRegion") as Promise<string | undefined>,
|
||||
this.getGlobalState("awsUseCrossRegionInference") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("awsProfile") as Promise<string | undefined>,
|
||||
this.getGlobalState("awsUseProfile") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("vertexProjectId") as Promise<string | undefined>,
|
||||
this.getGlobalState("vertexRegion") as Promise<string | undefined>,
|
||||
this.getGlobalState("openAiBaseUrl") as Promise<string | undefined>,
|
||||
|
|
@ -1471,6 +1481,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
awsSessionToken,
|
||||
awsRegion,
|
||||
awsUseCrossRegionInference,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ export interface ApiHandlerOptions {
|
|||
awsSessionToken?: string
|
||||
awsRegion?: string
|
||||
awsUseCrossRegionInference?: boolean
|
||||
awsUseProfile?: boolean
|
||||
awsProfile?: string
|
||||
vertexProjectId?: string
|
||||
vertexRegion?: string
|
||||
openAiBaseUrl?: string
|
||||
|
|
|
|||
|
|
@ -445,30 +445,56 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
flexDirection: "column",
|
||||
gap: 5,
|
||||
}}>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsAccessKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsAccessKey")}
|
||||
placeholder="Enter Access Key...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Access Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsSecretKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsSecretKey")}
|
||||
placeholder="Enter Secret Key...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Secret Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsSessionToken || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsSessionToken")}
|
||||
placeholder="Enter Session Token...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Session Token</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeRadioGroup
|
||||
value={apiConfiguration?.awsUseProfile ? "profile" : "credentials"}
|
||||
onChange={(e) => {
|
||||
const value = (e.target as HTMLInputElement)?.value
|
||||
const useProfile = value === "profile"
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
awsUseProfile: useProfile,
|
||||
})
|
||||
}}>
|
||||
<VSCodeRadio value="credentials">AWS Credentials</VSCodeRadio>
|
||||
<VSCodeRadio value="profile">AWS Profile</VSCodeRadio>
|
||||
</VSCodeRadioGroup>
|
||||
|
||||
{apiConfiguration?.awsUseProfile ? (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsProfile || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("awsProfile")}
|
||||
placeholder="Enter profile name (default if empty)">
|
||||
<span style={{ fontWeight: 500 }}>AWS Profile Name</span>
|
||||
</VSCodeTextField>
|
||||
) : (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsAccessKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsAccessKey")}
|
||||
placeholder="Enter Access Key...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Access Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsSecretKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsSecretKey")}
|
||||
placeholder="Enter Secret Key...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Secret Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsSessionToken || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsSessionToken")}
|
||||
placeholder="Enter Session Token...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Session Token</span>
|
||||
</VSCodeTextField>
|
||||
</>
|
||||
)}
|
||||
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 1} className="dropdown-container">
|
||||
<label htmlFor="aws-region-dropdown">
|
||||
<span style={{ fontWeight: 500 }}>AWS Region</span>
|
||||
|
|
@ -523,9 +549,18 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Authenticate by either providing the keys above or use the default AWS credential providers, i.e.
|
||||
~/.aws/credentials or environment variables. These credentials are only used locally to make API requests
|
||||
from this extension.
|
||||
{apiConfiguration?.awsUseProfile ? (
|
||||
<>
|
||||
Using AWS Profile credentials from ~/.aws/credentials. Leave profile name empty to use the default
|
||||
profile. These credentials are only used locally to make API requests from this extension.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Authenticate by either providing the keys above or use the default AWS credential providers, i.e.
|
||||
~/.aws/credentials or environment variables. These credentials are only used locally to make API
|
||||
requests from this extension.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue