Merge pull request #31884 from BerriAI/litellm_add-claude-sonnet-5-pricing

fix(pricing): rolling model registry update: Bedrock gpt-6-astra, gpt-image-2.5, Cohere rerank 4, Vertex Grok 4.3/4.6/4.20, Gemini 3.5 audio, OpenAI web search fee, xAI Imagine video, Lyria 3.5, Voyage, ChatGPT GPT-5.5/5.6, Bedrock Mantle, Scaleway dates
This commit is contained in:
Mateo Wang 2026-09-09 18:11:37 -07:00 committed by GitHub
commit 0e088337a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1542 additions and 200 deletions

View file

@ -4,6 +4,7 @@ Translating between OpenAI's `/chat/completion` format and Amazon's `/converse`
import copy
import json
import re
import time
import types
from collections.abc import Mapping
@ -293,6 +294,10 @@ class AmazonConverseConfig(BaseConfig):
llm_provider="bedrock",
)
@staticmethod
def _is_openai_gpt_reasoning_model(model: str) -> bool:
return re.search(r"openai\.gpt-\d", model) is not None
def _is_nova_2_model(self, model: str) -> bool:
"""
Check if the model is a Nova 2 model that supports reasoningConfig.
@ -423,14 +428,14 @@ class AmazonConverseConfig(BaseConfig):
Handle the reasoning_effort parameter based on the model type.
- GPT-OSS models: passed through unchanged via additionalModelRequestFields.
- OpenAI GPT-5.x models: mapped to ``reasoning.effort`` via additionalModelRequestFields.
- OpenAI GPT-5.x and GPT-6 models: mapped to ``reasoning.effort`` via additionalModelRequestFields.
- Nova 2 models: transformed to reasoningConfig.
- Anthropic models: mapped to ``thinking`` (and ``output_config.effort`` on
adaptive Claude 4.6 / 4.7).
"""
if "gpt-oss" in model:
optional_params["reasoning_effort"] = reasoning_effort
elif "openai.gpt-5" in model:
elif self._is_openai_gpt_reasoning_model(model):
reasoning: Final[BedrockConverseGptReasoningEffortBlock] = {"effort": reasoning_effort}
optional_params["reasoning"] = reasoning
elif self._is_nova_2_model(model):
@ -564,7 +569,11 @@ class AmazonConverseConfig(BaseConfig):
# only anthropic and mistral support tool choice config. otherwise (E.g. cohere) will fail the call - https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ToolChoice.html
supported_params.append("tool_choice")
if "gpt-oss" in model or "openai.gpt-5" in model or "openai.gpt-5" in base_model:
if (
"gpt-oss" in model
or self._is_openai_gpt_reasoning_model(model)
or self._is_openai_gpt_reasoning_model(base_model)
):
supported_params.append("reasoning_effort")
elif self._is_nova_2_model(model):
# Nova 2 models support reasoning_effort (transformed to reasoningConfig)
@ -920,7 +929,7 @@ class AmazonConverseConfig(BaseConfig):
optional_params["_parallel_tool_use_config"] = {
"tool_choice": {"type": "auto", "disable_parallel_tool_use": not value}
}
if param == "thinking" and "openai.gpt-5" not in model:
if param == "thinking" and not self._is_openai_gpt_reasoning_model(model):
if (
isinstance(value, dict)
and value.get("type") == "adaptive"
@ -1805,6 +1814,7 @@ class AmazonConverseConfig(BaseConfig):
data=request_data,
messages=messages,
encoding=encoding,
json_mode=json_mode,
)
def _transform_reasoning_content(self, reasoning_content_blocks: list[BedrockConverseReasoningContentBlock]) -> str:
@ -2237,6 +2247,7 @@ class AmazonConverseConfig(BaseConfig):
data: dict | str,
messages: list,
encoding,
json_mode: bool | None = None,
) -> ModelResponse:
## LOGGING
if logging_obj is not None:
@ -2247,7 +2258,9 @@ class AmazonConverseConfig(BaseConfig):
additional_args={"complete_input_dict": data},
)
json_mode: Final[bool | None] = optional_params.get("json_mode", None)
resolved_json_mode: Final[bool | None] = (
json_mode if json_mode is not None else optional_params.get("json_mode", None)
)
## RESPONSE OBJECT
try:
completion_response: Final = ConverseResponseBlock(**response.json())
@ -2339,7 +2352,7 @@ class AmazonConverseConfig(BaseConfig):
chat_completion_message["thinking_blocks"] = self._transform_thinking_blocks(reasoningContentBlocks)
chat_completion_message["content"] = content_str
filtered_tools: Final = self._filter_json_mode_tools(
json_mode=json_mode,
json_mode=resolved_json_mode,
tools=tools,
chat_completion_message=chat_completion_message,
)
@ -2363,7 +2376,7 @@ class AmazonConverseConfig(BaseConfig):
# When json_mode filtered out all synthetic tool calls the response
# is plain content, not a pending tool invocation. Fix finish_reason
# so callers (e.g. OpenAI SDK) don't misinterpret it.
if json_mode and not filtered_tools and tools:
if resolved_json_mode and not filtered_tools and tools:
initial_finish_reason = "stop"
(

View file

@ -340,6 +340,7 @@ class AmazonInvokeConfig(BaseConfig, BaseAWSLLM):
optional_params=optional_params,
litellm_params=litellm_params,
encoding=encoding,
json_mode=json_mode,
)
elif provider == "twelvelabs":
return litellm.AmazonTwelveLabsPegasusConfig().transform_response(

File diff suppressed because it is too large Load diff

View file

@ -525,6 +525,7 @@ class LiteLLMParamsTypedDict(TypedDict, total=False):
input_cost_per_second: float | None
output_cost_per_second: float | None
output_cost_per_second_480p: ReadOnly[float | None]
output_cost_per_second_720p: ReadOnly[float | None]
output_cost_per_second_1080p: float | None
output_cost_per_second_4k: ReadOnly[float | None]
num_retries: int | None

View file

@ -318,6 +318,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False):
float | None
) # video_generation tier: key output_cost_per_second_<resolution> (e.g. 1080p, 720p)
output_cost_per_second_480p: ReadOnly[float | None]
output_cost_per_second_720p: ReadOnly[float | None]
output_cost_per_second_4k: ReadOnly[float | None]
ocr_cost_per_page: float | None # for OCR models
ocr_cost_per_credit: float | None # for OCR models priced by credit
@ -3522,6 +3523,7 @@ class CustomPricingLiteLLMParams(MirroredPricingParams):
output_cost_per_second: float | None = None
output_cost_per_second_1080p: float | None = None
output_cost_per_second_480p: float | None = None
output_cost_per_second_720p: float | None = None
output_cost_per_second_4k: float | None = None
input_cost_per_pixel: float | None = None
output_cost_per_pixel: float | None = None

View file

@ -5913,6 +5913,7 @@ def _get_model_info_helper(
output_cost_per_second=_model_info.get("output_cost_per_second", None),
output_cost_per_second_1080p=_model_info.get("output_cost_per_second_1080p", None),
output_cost_per_second_480p=_model_info.get("output_cost_per_second_480p", None),
output_cost_per_second_720p=_model_info.get("output_cost_per_second_720p", None),
output_cost_per_second_4k=_model_info.get("output_cost_per_second_4k", None),
output_cost_per_video_per_second=_model_info.get("output_cost_per_video_per_second", None),
output_cost_per_image=_model_info.get("output_cost_per_image", None),

File diff suppressed because it is too large Load diff

View file

@ -478,6 +478,10 @@
"type": "number",
"minimum": 0
},
"output_cost_per_second_720p": {
"type": "number",
"minimum": 0
},
"output_cost_per_token": {
"type": "number",
"minimum": 0,

View file

@ -1,5 +1,6 @@
import os
import json
from collections.abc import Mapping, Sequence
from pathlib import Path
import pytest
@ -11,8 +12,6 @@ from litellm.types.llms.openai import FileSearchTool, ResponsesAPIResponse, WebS
from litellm.types.utils import ModelResponse, StandardBuiltInToolsParams
def test_web_search_cost_low():
web_search_options = WebSearchOptions(search_context_size="low")
model_info = litellm.get_model_info("gpt-4o-search-preview")
@ -683,12 +682,13 @@ def test_web_search_provider_prefix_fallback_does_not_misprice_non_gemini_model(
def _openai_responses_with_web_search_calls(model, num_calls):
from litellm.types.llms.openai import ResponsesAPIResponse
from openai.types.responses.response_function_web_search import (
ActionSearch,
ResponseFunctionWebSearch,
)
from litellm.types.llms.openai import ResponsesAPIResponse
output = [
ResponseFunctionWebSearch(
id=f"ws_{i}",
@ -859,11 +859,62 @@ def test_dated_search_preview_entries_carry_search_pricing(local_model_cost_map)
custom_llm_provider="openai",
standard_built_in_tools_params=None,
)
assert cost == pytest.approx(0.035), (
f"dated search-preview id must bill the $0.035 search fee, got ${cost}"
assert cost == pytest.approx(0.025), (
f"dated search-preview id must bill the $0.025 search fee, got ${cost}"
)
@pytest.mark.parametrize(
"web_search_options",
[
None,
WebSearchOptions(search_context_size="low"),
WebSearchOptions(search_context_size="medium"),
WebSearchOptions(search_context_size="high"),
],
)
def test_gpt_4o_mini_snapshot_bills_web_search_like_its_alias(
web_search_options: WebSearchOptions | None, local_model_cost_map: None
) -> None:
alias_info = litellm.get_model_info("gpt-4o-mini")
snapshot_info = litellm.get_model_info("gpt-4o-mini-2024-07-18")
assert not snapshot_info["supports_web_search"]
assert not alias_info["supports_web_search"]
snapshot_cost = StandardBuiltInToolCostTracking.get_cost_for_web_search(
web_search_options=web_search_options, model_info=snapshot_info
)
alias_cost = StandardBuiltInToolCostTracking.get_cost_for_web_search(
web_search_options=web_search_options, model_info=alias_info
)
assert snapshot_cost == alias_cost == 0.025
def test_gpt_4o_mini_web_search_price_matches_in_both_cost_maps():
repo_root = Path(__file__).parents[4]
cost_maps = tuple(
json.loads((repo_root / path).read_text(encoding="utf-8"))
for path in (
"model_prices_and_context_window.json",
"litellm/model_prices_and_context_window_backup.json",
)
)
canonical, backup = cost_maps
expected_search_price = {
"search_context_size_low": 0.025,
"search_context_size_medium": 0.025,
"search_context_size_high": 0.025,
}
for model_name in ("gpt-4o-mini", "gpt-4o-mini-2024-07-18"):
canonical_entry = canonical[model_name]
backup_entry = backup[model_name]
assert canonical_entry["search_context_cost_per_query"] == expected_search_price
assert backup_entry["search_context_cost_per_query"] == expected_search_price
assert canonical_entry == backup_entry
# Note: File search integration test removed due to complex annotation detection logic
# The unit tests in test_azure_assistant_cost_tracking.py provide comprehensive coverage

View file

@ -1,5 +1,7 @@
import json
from unittest.mock import MagicMock
import httpx
import pytest
@ -190,3 +192,45 @@ def test_get_error_class_preserves_provider_headers():
assert isinstance(error, BedrockError)
assert error.headers == {"x-amzn-RequestId": "req-invoke-500"}
assert error.response.headers["x-amzn-requestid"] == "req-invoke-500"
def test_transform_response_hands_json_mode_to_nova():
"""The invoke dispatcher forwards its json_mode argument to Nova instead of dropping it."""
from litellm.types.utils import ModelResponse
response_json = {
"output": {
"message": {
"role": "assistant",
"content": [
{
"toolUse": {
"toolUseId": "tooluse_nova_json",
"name": "json_tool_call",
"input": {"city": "Paris", "temperature": 21},
}
}
],
}
},
"stopReason": "tool_use",
"usage": {"inputTokens": 5, "outputTokens": 4, "totalTokens": 9},
}
raw_response = httpx.Response(200, json=response_json, request=httpx.Request("POST", "https://bedrock"))
result = AmazonInvokeConfig().transform_response(
model="invoke/amazon.nova-lite-v1:0",
raw_response=raw_response,
model_response=ModelResponse(),
logging_obj=MagicMock(),
request_data={},
messages=[{"role": "user", "content": "weather"}],
optional_params={},
litellm_params={},
encoding=None,
api_key=None,
json_mode=True,
)
assert result.choices[0].message.tool_calls is None
assert json.loads(result.choices[0].message.content) == {"city": "Paris", "temperature": 21}

View file

@ -382,6 +382,8 @@ def test_reasoning_with_forced_tool_choice_switches_to_auto():
"us.openai.gpt-5.6-sol",
"global.openai.gpt-5.6-terra",
"bedrock/converse/us.openai.gpt-5.6-luna",
"us.openai.gpt-6-astra",
"bedrock/converse/global.openai.gpt-6-astra",
],
)
def test_reasoning_effort_maps_to_reasoning_effort_for_openai_gpt5_converse(model, local_model_cost_map):
@ -412,6 +414,7 @@ def test_reasoning_effort_maps_to_reasoning_effort_for_openai_gpt5_converse(mode
[
"us.openai.gpt-5.6-sol",
"bedrock/converse/global.openai.gpt-5.6-luna",
"us.openai.gpt-6-astra",
],
)
def test_openai_gpt5_converse_never_forwards_thinking(model, local_model_cost_map):
@ -6727,3 +6730,41 @@ def test_forced_tool_choice_forwarded_on_converse_models_that_support_it(
)
assert result == {"any": {}}
def test_transform_response_honors_json_mode_kwarg_when_optional_params_lack_it():
response_json = {
"metrics": {"latencyMs": 900},
"output": {
"message": {
"content": [
{
"toolUse": {
"input": {"city": "Paris", "population": 2100000},
"name": "json_tool_call",
"toolUseId": "tooluse_invoke_nova_json",
}
}
],
"role": "assistant",
}
},
"stopReason": "tool_use",
"usage": {"inputTokens": 40, "outputTokens": 20, "totalTokens": 60},
}
raw_response = httpx.Response(200, json=response_json, request=httpx.Request("POST", "https://bedrock.test"))
logging_obj = MagicMock()
result = AmazonConverseConfig().transform_response(
model="bedrock/invoke/us.amazon.nova-micro-v1:0",
raw_response=raw_response,
model_response=ModelResponse(),
logging_obj=logging_obj,
request_data={},
messages=[],
optional_params={"tools": [{"type": "function", "function": {"name": "json_tool_call", "parameters": {}}}]},
litellm_params={},
encoding=None,
json_mode=True,
)
assert result.choices[0].message.tool_calls is None
assert json.loads(result.choices[0].message.content) == {"city": "Paris", "population": 2100000}

View file

@ -10,18 +10,23 @@ from unittest.mock import MagicMock, patch
import httpx
import pytest
import litellm
from litellm.llms.chatgpt.responses.transformation import ChatGPTResponsesAPIConfig
from litellm.llms.openai.common_utils import OpenAIError
from litellm.main import responses_api_bridge_check
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import LlmProviders
from litellm.utils import ProviderConfigManager
from litellm.llms.chatgpt.responses.transformation import ChatGPTResponsesAPIConfig
class TestChatGPTResponsesAPITransformation:
@pytest.mark.parametrize(
"model_name",
[
"chatgpt/gpt-5.5",
"chatgpt/gpt-5.6-luna",
"chatgpt/gpt-5.6-sol",
"chatgpt/gpt-5.6-terra",
"chatgpt/gpt-5.4",
"chatgpt/gpt-5.4-pro",
"chatgpt/gpt-5.3-chat-latest",
@ -40,6 +45,52 @@ class TestChatGPTResponsesAPITransformation:
assert isinstance(config, ChatGPTResponsesAPIConfig)
assert config.custom_llm_provider == LlmProviders.CHATGPT
@pytest.mark.parametrize(
"model_name",
[
"chatgpt/gpt-5.5",
"chatgpt/gpt-5.6-luna",
"chatgpt/gpt-5.6-sol",
"chatgpt/gpt-5.6-terra",
],
)
def test_chatgpt_responses_model_metadata(self, model_name: str, local_model_cost_map: None) -> None:
model_info = litellm.get_model_info(model_name)
assert model_info["litellm_provider"] == "chatgpt"
assert model_info["mode"] == "responses"
assert model_info["supported_endpoints"] == [
"/v1/chat/completions",
"/v1/responses",
]
assert model_info["max_input_tokens"] == 1050000
assert model_info["max_output_tokens"] == 128000
@pytest.mark.parametrize(
"model_name",
[
"gpt-5.5",
"gpt-5.6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
],
)
def test_chatgpt_models_bridge_chat_completions_to_responses(
self, model_name: str, local_model_cost_map: None
) -> None:
"""A chat completions request for these models must take the Responses bridge.
`gpt-5.6-*` also exists as an openai chat model, so an unregistered
chatgpt model resolves to mode "chat" here and never reaches the bridge.
"""
model_info, resolved_model = responses_api_bridge_check(
model=model_name,
custom_llm_provider="chatgpt",
)
assert model_info["mode"] == "responses"
assert resolved_model == model_name
@patch("litellm.llms.chatgpt.responses.transformation.Authenticator")
def test_chatgpt_responses_endpoint_url(self, mock_authenticator_class):
mock_auth_instance = MagicMock()

View file

@ -322,8 +322,8 @@ class TestModelCostEntry:
entry = json.load(f)["vertex_ai/gemini-3.5-transcribe-preview"]
assert entry["mode"] == "audio_transcription"
assert entry["litellm_provider"] == "vertex_ai"
assert entry["input_cost_per_audio_token"] == pytest.approx(2.5e-06)
assert entry["input_cost_per_token"] == pytest.approx(2.5e-06)
assert entry["input_cost_per_audio_token"] == pytest.approx(2e-06)
assert entry["input_cost_per_token"] == pytest.approx(2e-06)
assert entry["output_cost_per_token"] == pytest.approx(1.2e-05)
assert entry["supported_endpoints"] == ["/v1/audio/transcriptions"]

View file

@ -265,7 +265,7 @@ class TestSuggesterRejectsModelsWithoutToolCalling:
def test_a_model_without_forced_tool_choice_support_remains_eligible(self, local_model_cost_map):
supported_params = litellm.get_supported_openai_params(
model="amazon.nova-pro-v1:0",
model="meta.llama4-scout-17b-instruct-v1:0",
custom_llm_provider="bedrock",
)

View file

@ -56,6 +56,12 @@ from litellm.utils import (
# Adds the parent directory to the system path
def test_cloudflare_model_info_includes_rpm(local_model_cost_map: None) -> None:
assert litellm.get_model_info("cloudflare/@cf/meta/llama-3.1-8b-instruct-fp8")["rpm"] == 300
assert litellm.get_model_info("cloudflare/@cf/moonshotai/kimi-k2.6")["rpm"] == 20
assert litellm.get_model_info("cloudflare/@cf/openai/whisper-large-v3-turbo")["rpm"] == 720
def test_get_utc_datetime_returns_current_aware_utc_time() -> None:
before: Final = datetime.now(timezone.utc)
result: Final = litellm.utils.get_utc_datetime()
@ -810,6 +816,7 @@ def validate_model_cost_values(model_data, exceptions=None):
"input_cost_per_second",
"output_cost_per_second",
"output_cost_per_second_480p",
"output_cost_per_second_720p",
"output_cost_per_second_1080p",
"output_cost_per_second_4k",
"input_cost_per_query",
@ -1033,6 +1040,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
"output_cost_per_pixel": {"type": "number"},
"output_cost_per_second": {"type": "number"},
"output_cost_per_second_480p": {"type": "number"},
"output_cost_per_second_720p": {"type": "number"},
"output_cost_per_second_1080p": {"type": "number"},
"output_cost_per_second_4k": {"type": "number"},
"output_cost_per_token": {"type": "number"},
@ -1405,23 +1413,35 @@ def test_supports_tool_choice_simple_tests():
is True
)
assert (
litellm.utils.supports_tool_choice(model="us.amazon.nova-micro-v1:0") is False
)
assert (
litellm.utils.supports_tool_choice(model="bedrock/us.amazon.nova-micro-v1:0")
is False
)
assert (
litellm.utils.supports_tool_choice(
model="us.amazon.nova-micro-v1:0", custom_llm_provider="bedrock_converse"
)
is False
)
assert litellm.utils.supports_tool_choice(model="perplexity/sonar") is False
@pytest.mark.usefixtures("local_model_cost_map")
@pytest.mark.parametrize(
"model",
[
"amazon.nova-lite-v1:0",
"amazon.nova-micro-v1:0",
"amazon.nova-pro-v1:0",
"apac.amazon.nova-lite-v1:0",
"apac.amazon.nova-micro-v1:0",
"apac.amazon.nova-pro-v1:0",
"bedrock/us-gov-east-1/amazon.nova-pro-v1:0",
"bedrock/us-gov-west-1/amazon.nova-lite-v1:0",
"bedrock/us-gov-west-1/amazon.nova-micro-v1:0",
"bedrock/us-gov-west-1/amazon.nova-pro-v1:0",
"eu.amazon.nova-lite-v1:0",
"eu.amazon.nova-micro-v1:0",
"eu.amazon.nova-pro-v1:0",
"us.amazon.nova-lite-v1:0",
"us.amazon.nova-micro-v1:0",
"us.amazon.nova-pro-v1:0",
],
)
def test_amazon_nova_v1_understanding_models_support_tool_choice(model: str) -> None:
assert litellm.utils.supports_tool_choice(model=model) is True
def test_check_provider_match():
"""
Test the _check_provider_match function for various provider scenarios

View file

@ -532,6 +532,32 @@ class TestVideoGeneration:
assert abs(cost_for("runwayml/seedance2_5", "480p", 8.0) - 1.6) < 0.001
assert abs(cost_for("runwayml/gen4.5", None, 8.0) - 0.96) < 0.001
def test_completion_cost_xai_imagine_video_720p_tier_from_cost_map(self, monkeypatch):
"""720p xAI Imagine Video requests bill the published 720p rate, not the 480p base rate."""
from litellm.cost_calculator import completion_cost
local_map_path = os.path.join(
os.path.dirname(__file__), "..", "..", "model_prices_and_context_window.json"
)
with open(local_map_path, "r") as f:
monkeypatch.setattr(litellm, "model_cost", json.load(f))
def cost_for(model: str, resolution: str, duration: float) -> float:
mock_response = MagicMock()
mock_response.usage = {"duration_seconds": duration, "video_resolution": resolution}
type(mock_response)._hidden_params = {}
return completion_cost(
completion_response=mock_response,
model=model,
call_type="create_video",
custom_llm_provider="xai",
)
assert abs(cost_for("xai/grok-imagine-video", "720p", 10.0) - 0.7) < 0.001
assert abs(cost_for("xai/grok-imagine-video-1.5", "720p", 10.0) - 1.4) < 0.001
assert abs(cost_for("xai/grok-imagine-video-1.5", "480p", 10.0) - 0.8) < 0.001
assert abs(cost_for("xai/grok-imagine-video-1.5", "1080p", 10.0) - 2.5) < 0.001
def test_completion_cost_veo_31_tiers_pin_published_rates(self, monkeypatch):
"""The gemini and vertex_ai veo 3.1 entries bill Google's published per-second tier rates."""
from litellm.cost_calculator import completion_cost

View file

@ -29741,6 +29741,8 @@ export interface components {
output_cost_per_second_480p?: number | null;
/** Output Cost Per Second 4K */
output_cost_per_second_4k?: number | null;
/** Output Cost Per Second 720P */
output_cost_per_second_720p?: number | null;
/** Output Cost Per Token */
output_cost_per_token?: number | null;
/** Output Cost Per Token Above 128K Tokens */
@ -39932,6 +39934,8 @@ export interface components {
output_cost_per_second_480p?: number | null;
/** Output Cost Per Second 4K */
output_cost_per_second_4k?: number | null;
/** Output Cost Per Second 720P */
output_cost_per_second_720p?: number | null;
/** Output Cost Per Token */
output_cost_per_token?: number | null;
/** Output Cost Per Token Above 128K Tokens */

View file

@ -6,6 +6,7 @@ ai21.jamba-instruct-v1:0
twelvelabs.pegasus-1-2-v1:0
us.twelvelabs.pegasus-1-2-v1:0
eu.twelvelabs.pegasus-1-2-v1:0
global.twelvelabs.pegasus-1-2-v1:0
amazon.titan-text-express-v1
amazon.titan-text-lite-v1
amazon.titan-text-premier-v1:0