feat: add AWS Bedrock service tier support

- Add awsBedrockServiceTier field to provider settings schema
- Add tier-specific pricing for Claude models (FLEX ~20% discount, PRIORITY ~20% premium)
- Update Bedrock provider to include service_tier in API requests
- Add service tier selector UI in Bedrock settings
- Apply tier-specific pricing in cost calculations

Implements #9874
This commit is contained in:
Roo Code 2025-12-05 21:45:08 +00:00
parent dd92453276
commit 176c0eecc2
4 changed files with 139 additions and 5 deletions

View file

@ -236,6 +236,8 @@ const bedrockSchema = apiModelIdProviderModelSchema.extend({
awsBedrockEndpointEnabled: z.boolean().optional(),
awsBedrockEndpoint: z.string().optional(),
awsBedrock1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window.
// AWS Bedrock service tier for models that support it (STANDARD, FLEX, PRIORITY)
awsBedrockServiceTier: serviceTierSchema.optional(),
})
const vertexSchema = apiModelIdProviderModelSchema.extend({

View file

@ -27,6 +27,25 @@ export const bedrockModels = {
minTokensPerCachePoint: 1024,
maxCachePoints: 4,
cachableFields: ["system", "messages", "tools"],
// Service tier pricing (FLEX ~20% discount, PRIORITY ~20% premium)
tiers: [
{
name: "flex" as const,
contextWindow: 200_000,
inputPrice: 2.4, // 20% discount
outputPrice: 12.0, // 20% discount
cacheWritesPrice: 3.0, // 20% discount
cacheReadsPrice: 0.24, // 20% discount
},
{
name: "priority" as const,
contextWindow: 200_000,
inputPrice: 3.6, // 20% premium
outputPrice: 18.0, // 20% premium
cacheWritesPrice: 4.5, // 20% premium
cacheReadsPrice: 0.36, // 20% premium
},
],
},
"amazon.nova-pro-v1:0": {
maxTokens: 5000,
@ -184,6 +203,25 @@ export const bedrockModels = {
minTokensPerCachePoint: 2048,
maxCachePoints: 4,
cachableFields: ["system", "messages", "tools"],
// Service tier pricing (FLEX ~20% discount, PRIORITY ~20% premium)
tiers: [
{
name: "flex" as const,
contextWindow: 200_000,
inputPrice: 0.64, // 20% discount
outputPrice: 3.2, // 20% discount
cacheWritesPrice: 0.8, // 20% discount
cacheReadsPrice: 0.064, // 20% discount
},
{
name: "priority" as const,
contextWindow: 200_000,
inputPrice: 0.96, // 20% premium
outputPrice: 4.8, // 20% premium
cacheWritesPrice: 1.2, // 20% premium
cacheReadsPrice: 0.096, // 20% premium
},
],
},
"anthropic.claude-haiku-4-5-20251001-v1:0": {
maxTokens: 8192,
@ -235,6 +273,21 @@ export const bedrockModels = {
supportsNativeTools: true,
inputPrice: 0.25,
outputPrice: 1.25,
// Service tier pricing (FLEX ~20% discount, PRIORITY ~20% premium)
tiers: [
{
name: "flex" as const,
contextWindow: 200_000,
inputPrice: 0.2, // 20% discount
outputPrice: 1.0, // 20% discount
},
{
name: "priority" as const,
contextWindow: 200_000,
inputPrice: 0.3, // 20% premium
outputPrice: 1.5, // 20% premium
},
],
},
"anthropic.claude-2-1-v1:0": {
maxTokens: 4096,

View file

@ -53,13 +53,14 @@ interface BedrockInferenceConfig {
}
// Define interface for Bedrock additional model request fields
// This includes thinking configuration, 1M context beta, and other model-specific parameters
// This includes thinking configuration, 1M context beta, service tier, and other model-specific parameters
interface BedrockAdditionalModelFields {
thinking?: {
type: "enabled"
budget_tokens: number
}
anthropic_beta?: string[]
service_tier?: "STANDARD" | "FLEX" | "PRIORITY"
[key: string]: any // Add index signature to be compatible with DocumentType
}
@ -433,6 +434,28 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
additionalModelRequestFields.anthropic_beta = anthropicBetas
}
// Add service tier if specified and model supports it
if (this.options.awsBedrockServiceTier && modelConfig.info.tiers && modelConfig.info.tiers.length > 0) {
if (!additionalModelRequestFields) {
additionalModelRequestFields = {} as BedrockAdditionalModelFields
}
// Convert from lowercase to uppercase for API
const tierMap: Record<string, "STANDARD" | "FLEX" | "PRIORITY"> = {
default: "STANDARD",
flex: "FLEX",
priority: "PRIORITY",
}
const mappedTier = tierMap[this.options.awsBedrockServiceTier as string]
if (mappedTier) {
additionalModelRequestFields.service_tier = mappedTier
logger.info("Service tier specified for Bedrock request", {
ctx: "bedrock",
modelId: modelConfig.id,
serviceTier: mappedTier,
})
}
}
// Build tool configuration if native tools are enabled
let toolConfig: ToolConfiguration | undefined
if (useNativeTools && metadata?.tools) {
@ -1027,15 +1050,18 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
reasoningBudget?: number
} {
if (this.costModelConfig?.id?.trim().length > 0) {
// Apply service tier pricing if specified
const tierAdjustedInfo = this.applyServiceTierPricing(this.costModelConfig.info)
// Get model params for cost model config
const params = getModelParams({
format: "anthropic",
modelId: this.costModelConfig.id,
model: this.costModelConfig.info,
model: tierAdjustedInfo,
settings: this.options,
defaultTemperature: BEDROCK_DEFAULT_TEMPERATURE,
})
return { ...this.costModelConfig, ...params }
return { ...this.costModelConfig, info: tierAdjustedInfo, ...params }
}
let modelConfig = undefined
@ -1080,17 +1106,20 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
}
}
// Apply service tier pricing if specified
const tierAdjustedInfo = this.applyServiceTierPricing(modelConfig.info)
// Get model params including reasoning configuration
const params = getModelParams({
format: "anthropic",
modelId: modelConfig.id,
model: modelConfig.info,
model: tierAdjustedInfo,
settings: this.options,
defaultTemperature: BEDROCK_DEFAULT_TEMPERATURE,
})
// Don't override maxTokens/contextWindow here; handled in getModelById (and includes user overrides)
return { ...modelConfig, ...params } as {
return { ...modelConfig, info: tierAdjustedInfo, ...params } as {
id: BedrockModelId | string
info: ModelInfo
maxTokens?: number
@ -1230,6 +1259,33 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
return false
}
/************************************************************************************
*
* SERVICE TIER PRICING
*
*************************************************************************************/
/**
* Returns a shallow-cloned ModelInfo with pricing overridden for the given tier, if available.
* If no tier or no overrides exist, the original ModelInfo is returned.
*/
private applyServiceTierPricing(info: ModelInfo): ModelInfo {
const tier = this.options.awsBedrockServiceTier
if (!tier || tier === "default") return info
// Find the tier with matching name in the tiers array
const tierInfo = info.tiers?.find((t) => t.name === tier)
if (!tierInfo) return info
return {
...info,
inputPrice: tierInfo.inputPrice ?? info.inputPrice,
outputPrice: tierInfo.outputPrice ?? info.outputPrice,
cacheReadsPrice: tierInfo.cacheReadsPrice ?? info.cacheReadsPrice,
cacheWritesPrice: tierInfo.cacheWritesPrice ?? info.cacheWritesPrice,
}
}
/************************************************************************************
*
* ERROR HANDLING

View file

@ -35,6 +35,9 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo
!!apiConfiguration?.apiModelId &&
BEDROCK_GLOBAL_INFERENCE_MODEL_IDS.includes(apiConfiguration.apiModelId as any)
// Check if the selected model supports service tiers
const supportsServiceTiers = !!(selectedModelInfo?.tiers && selectedModelInfo.tiers.length > 0)
// Update the endpoint enabled state when the configuration changes
useEffect(() => {
setAwsEndpointSelected(!!apiConfiguration?.awsBedrockEndpointEnabled)
@ -150,6 +153,26 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo
</SelectContent>
</Select>
</div>
{supportsServiceTiers && (
<div>
<label className="block font-medium mb-1">Service Tier</label>
<Select
value={apiConfiguration?.awsBedrockServiceTier || "default"}
onValueChange={(value) => setApiConfigurationField("awsBedrockServiceTier", value)}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select service tier" />
</SelectTrigger>
<SelectContent>
<SelectItem value="default">Standard (Default)</SelectItem>
<SelectItem value="flex">Flex (Lower cost, higher latency)</SelectItem>
<SelectItem value="priority">Priority (Lower latency, higher cost)</SelectItem>
</SelectContent>
</Select>
<div className="text-sm text-vscode-descriptionForeground mt-1">
Choose the service tier based on your cost and latency requirements
</div>
</div>
)}
{supportsGlobalInference && (
<Checkbox
checked={apiConfiguration?.awsUseGlobalInference || false}