API / kiro-gateway /kiro /cache.py
sshinmen's picture
Clean deploy to HF Space
bf9e111
Raw
History Blame Contribute Delete
5.95 kB
# -*- coding: utf-8 -*-
# Kiro Gateway
# https://github.com/jwadow/kiro-gateway
# Copyright (C) 2025 Jwadow
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
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, # Store internal ID for reference
"_is_hidden": True, # Mark as hidden model
}
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