| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """ |
| Model metadata cache for Kiro Gateway. |
| |
| Thread-safe storage for available model information |
| with TTL and lazy loading support. |
| """ |
|
|
| import asyncio |
| import time |
| from typing import Any, Dict, List, Optional |
|
|
| from loguru import logger |
|
|
| from kiro.config import MODEL_CACHE_TTL, DEFAULT_MAX_INPUT_TOKENS |
|
|
|
|
| class ModelInfoCache: |
| """ |
| Thread-safe cache for storing model metadata. |
| |
| Uses Lazy Loading for population - data is loaded |
| only on first access or when cache is stale. |
| |
| Attributes: |
| cache_ttl: Cache time-to-live in seconds |
| |
| Example: |
| >>> cache = ModelInfoCache() |
| >>> await cache.update([{"modelId": "claude-sonnet-4", "tokenLimits": {...}}]) |
| >>> info = cache.get("claude-sonnet-4") |
| >>> max_tokens = cache.get_max_input_tokens("claude-sonnet-4") |
| """ |
| |
| def __init__(self, cache_ttl: int = MODEL_CACHE_TTL): |
| """ |
| Initializes the model cache. |
| |
| Args: |
| cache_ttl: Cache time-to-live in seconds (default from config) |
| """ |
| self._cache: Dict[str, Dict[str, Any]] = {} |
| self._lock = asyncio.Lock() |
| self._last_update: Optional[float] = None |
| self._cache_ttl = cache_ttl |
| |
| async def update(self, models_data: List[Dict[str, Any]]) -> None: |
| """ |
| Updates the model cache. |
| |
| Thread-safely replaces cache contents with new data. |
| |
| Args: |
| models_data: List of dictionaries with model information. |
| Each dictionary must contain the "modelId" key. |
| """ |
| async with self._lock: |
| logger.info(f"Updating model cache. Found {len(models_data)} models.") |
| self._cache = {model["modelId"]: model for model in models_data} |
| self._last_update = time.time() |
| |
| def get(self, model_id: str) -> Optional[Dict[str, Any]]: |
| """ |
| Returns model information. |
| |
| Args: |
| model_id: Model ID |
| |
| Returns: |
| Dictionary with model information or None if model not found |
| """ |
| return self._cache.get(model_id) |
| |
| def is_valid_model(self, model_id: str) -> bool: |
| """ |
| Check if model exists in dynamic cache. |
| |
| Used by ModelResolver to verify if a model is available. |
| |
| Args: |
| model_id: Model ID to check |
| |
| Returns: |
| True if model exists in cache, False otherwise |
| """ |
| return model_id in self._cache |
| |
| def add_hidden_model(self, display_name: str, internal_id: str) -> None: |
| """ |
| Add a hidden model to the cache. |
| |
| Hidden models are not returned by Kiro /ListAvailableModels API |
| but are still functional. They are added to the cache so they |
| appear in our /v1/models endpoint. |
| |
| Args: |
| display_name: Model name to display (e.g., "claude-3.7-sonnet") |
| internal_id: Internal Kiro ID (e.g., "CLAUDE_3_7_SONNET_20250219_V1_0") |
| """ |
| if display_name not in self._cache: |
| self._cache[display_name] = { |
| "modelId": display_name, |
| "modelName": display_name, |
| "description": f"Hidden model (internal: {internal_id})", |
| "tokenLimits": {"maxInputTokens": DEFAULT_MAX_INPUT_TOKENS}, |
| "_internal_id": internal_id, |
| "_is_hidden": True, |
| } |
| logger.debug(f"Added hidden model: {display_name} → {internal_id}") |
| |
| def get_max_input_tokens(self, model_id: str) -> int: |
| """ |
| Returns maxInputTokens for the model. |
| |
| Args: |
| model_id: Model ID |
| |
| Returns: |
| Maximum number of input tokens or DEFAULT_MAX_INPUT_TOKENS |
| """ |
| model = self._cache.get(model_id) |
| if model and model.get("tokenLimits"): |
| return model["tokenLimits"].get("maxInputTokens") or DEFAULT_MAX_INPUT_TOKENS |
| return DEFAULT_MAX_INPUT_TOKENS |
| |
| def is_empty(self) -> bool: |
| """ |
| Checks if the cache is empty. |
| |
| Returns: |
| True if cache is empty |
| """ |
| return not self._cache |
| |
| def is_stale(self) -> bool: |
| """ |
| Checks if the cache is stale. |
| |
| Returns: |
| True if cache is stale (more than cache_ttl seconds have passed) |
| or if cache was never updated |
| """ |
| if not self._last_update: |
| return True |
| return time.time() - self._last_update > self._cache_ttl |
| |
| def get_all_model_ids(self) -> List[str]: |
| """ |
| Returns a list of all model IDs in the cache. |
| |
| Returns: |
| List of model IDs |
| """ |
| return list(self._cache.keys()) |
| |
| @property |
| def size(self) -> int: |
| """Number of models in the cache.""" |
| return len(self._cache) |
| |
| @property |
| def last_update_time(self) -> Optional[float]: |
| """Last update time (timestamp) or None.""" |
| return self._last_update |