diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 80ecd75ae4..a147ca1b86 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -496,7 +496,11 @@ const ApiOptions = ({ )} {selectedProvider === "openai-native" && ( - + )} {selectedProvider === "mistral" && ( diff --git a/webview-ui/src/components/settings/ModelInfoView.tsx b/webview-ui/src/components/settings/ModelInfoView.tsx index 5091fb1a68..7705bcf0a1 100644 --- a/webview-ui/src/components/settings/ModelInfoView.tsx +++ b/webview-ui/src/components/settings/ModelInfoView.tsx @@ -25,7 +25,14 @@ export const ModelInfoView = ({ }: ModelInfoViewProps) => { const { t } = useAppTranslation() - const infoItems = [ + // Show tiered pricing table for OpenAI Native when model supports non-standard tiers + const allowedTiers = + (modelInfo?.allowedServiceTiers || []).filter((tier) => tier === "flex" || tier === "priority") ?? [] + const tierPricing = modelInfo?.serviceTierPricing + const shouldShowTierPricingTable = apiProvider === "openai-native" && allowedTiers.length > 0 && !!tierPricing + const fmt = (n?: number) => (typeof n === "number" ? `${formatPrice(n)}` : "—") + + const baseInfoItems = [ typeof modelInfo?.contextWindow === "number" && modelInfo.contextWindow > 0 && ( <> {t("settings:modelInfo.contextWindow")}{" "} @@ -53,6 +60,21 @@ export const ModelInfoView = ({ supportsLabel={t("settings:modelInfo.supportsPromptCache")} doesNotSupportLabel={t("settings:modelInfo.noPromptCache")} />, + apiProvider === "gemini" && ( + + {selectedModelId.includes("pro-preview") + ? t("settings:modelInfo.gemini.billingEstimate") + : t("settings:modelInfo.gemini.freeRequests", { + count: selectedModelId && selectedModelId.includes("flash") ? 15 : 2, + })}{" "} + + {t("settings:modelInfo.gemini.pricingDetails")} + + + ), + ].filter(Boolean) + + const priceInfoItems = [ modelInfo?.inputPrice !== undefined && modelInfo.inputPrice > 0 && ( <> {t("settings:modelInfo.inputPrice")}:{" "} @@ -77,20 +99,10 @@ export const ModelInfoView = ({ {formatPrice(modelInfo.cacheWritesPrice || 0)} / 1M tokens ), - apiProvider === "gemini" && ( - - {selectedModelId.includes("pro-preview") - ? t("settings:modelInfo.gemini.billingEstimate") - : t("settings:modelInfo.gemini.freeRequests", { - count: selectedModelId && selectedModelId.includes("flash") ? 15 : 2, - })}{" "} - - {t("settings:modelInfo.gemini.pricingDetails")} - - - ), ].filter(Boolean) + const infoItems = shouldShowTierPricingTable ? baseInfoItems : [...baseInfoItems, ...priceInfoItems] + return ( <> {modelInfo?.description && ( @@ -106,6 +118,62 @@ export const ModelInfoView = ({
{item}
))} + + {shouldShowTierPricingTable && ( +
+
+ Pricing by service tier (price per 1M tokens) +
+
+ + + + + + + + + + + + + + + + + {allowedTiers.includes("flex") && ( + + + + + + + )} + {allowedTiers.includes("priority") && ( + + + + + + + )} + +
TierInputOutputCache reads
Standard{fmt(modelInfo?.inputPrice)}{fmt(modelInfo?.outputPrice)}{fmt(modelInfo?.cacheReadsPrice)}
Flex + {fmt(tierPricing?.flex?.inputPrice ?? modelInfo?.inputPrice)} + + {fmt(tierPricing?.flex?.outputPrice ?? modelInfo?.outputPrice)} + + {fmt(tierPricing?.flex?.cacheReadsPrice ?? modelInfo?.cacheReadsPrice)} +
Priority + {fmt(tierPricing?.priority?.inputPrice ?? modelInfo?.inputPrice)} + + {fmt(tierPricing?.priority?.outputPrice ?? modelInfo?.outputPrice)} + + {fmt(tierPricing?.priority?.cacheReadsPrice ?? modelInfo?.cacheReadsPrice)} +
+
+
+ )} ) } diff --git a/webview-ui/src/components/settings/providers/OpenAI.tsx b/webview-ui/src/components/settings/providers/OpenAI.tsx index e2f7857fe0..666d786e1f 100644 --- a/webview-ui/src/components/settings/providers/OpenAI.tsx +++ b/webview-ui/src/components/settings/providers/OpenAI.tsx @@ -2,19 +2,21 @@ import { useCallback, useState } from "react" import { Checkbox } from "vscrui" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import type { ProviderSettings } from "@roo-code/types" +import type { ModelInfo, ProviderSettings } from "@roo-code/types" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StandardTooltip } from "@src/components/ui" import { inputEventTransform } from "../transforms" type OpenAIProps = { apiConfiguration: ProviderSettings setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + selectedModelInfo?: ModelInfo } -export const OpenAI = ({ apiConfiguration, setApiConfigurationField }: OpenAIProps) => { +export const OpenAI = ({ apiConfiguration, setApiConfigurationField, selectedModelInfo }: OpenAIProps) => { const { t } = useAppTranslation() const [openAiNativeBaseUrlSelected, setOpenAiNativeBaseUrlSelected] = useState( @@ -72,6 +74,44 @@ export const OpenAI = ({ apiConfiguration, setApiConfigurationField }: OpenAIPro {t("settings:providers.getOpenAiApiKey")} )} + + {(() => { + const allowedTiers = (selectedModelInfo?.allowedServiceTiers || []).filter( + (t) => t === "flex" || t === "priority", + ) + if (allowedTiers.length === 0) return null + + return ( +
+
+ + + + +
+ + +
+ ) + })()} ) }