# retoor from __future__ import annotations from ..spec import Action, Param from ._shared import body, confirm, path GATEWAY_ACTIONS: tuple[Action, ...] = ( Action( name="gateway_providers", method="GET", path="/admin/gateway/providers", summary="List OpenAI gateway providers (admin only)", description=( "Returns JSON: the named upstream providers used by model routes, plus the implicit " "'default' provider read from the gateway service config." ), handler="http", requires_admin=True, read_only=True, ), Action( name="gateway_provider_set", method="POST", path="/admin/gateway/providers", summary="Create or update a gateway provider (admin only)", description=( "Adds or updates a named upstream provider. base_url is the OpenAI-compatible " "chat-completions endpoint; the embeddings and images endpoints are derived from it." ), handler="http", requires_admin=True, params=( body("name", "Provider name (letters, numbers, hyphen, underscore).", required=True), body("base_url", "Chat-completions endpoint URL."), body("api_key", "Upstream API key."), Param( name="is_active", location="body", description="Whether the provider is active ('1' or '0').", required=False, type="boolean", ), ), ), Action( name="gateway_provider_delete", method="DELETE", path="/admin/gateway/providers/{name}", summary="Delete a gateway provider (admin only, confirmation required)", handler="http", requires_admin=True, params=(path("name", "Provider name."), confirm()), ), Action( name="gateway_models", method="GET", path="/admin/gateway/models", summary="List OpenAI gateway model routes (admin only)", description=( "Returns JSON: every source-model route with its provider, target model, kind " "(chat/embed/image), optional vision model, context window, and per-model pricing economy " "(including any tiered/off-peak pricing configured on it)." ), handler="http", requires_admin=True, read_only=True, ), Action( name="gateway_model_set", method="POST", path="/admin/gateway/models", summary="Create or update a gateway model route (admin only)", description=( "Maps a requested source_model onto a provider + target_model, each with its own " "pricing. kind is 'chat', 'embed', or 'image'. A vision_model adds image-to-text augmentation " "for chat routes. Prices are USD per 1,000,000 tokens for chat/embed/vision; image routes " "use price_input_per_m as a flat USD per generated image. Optionally, a route can also " "charge a different (tier-2) rate once the request's input tokens exceed " "context_tier_threshold_tokens, and/or apply a percentage discount during a fixed " "UTC off-peak window - leave the tier2/off-peak fields unset to keep the flat rates " "above at all times. off_peak_start_minute and off_peak_end_minute must be set " "together (both or neither) or the call is rejected." ), handler="http", requires_admin=True, params=( body("source_model", "Model name clients request.", required=True), body("provider", "Provider name, or blank for the default upstream."), body("target_model", "Model name sent upstream.", required=True), body("kind", "Route kind: 'chat', 'embed', or 'image'."), body("vision_provider", "Provider for image description, or blank for the route provider."), body("vision_model", "Vision model name (blank disables the merge)."), Param(name="context_window", location="body", description="Max context tokens (0 = unknown).", required=False, type="integer"), Param(name="price_cache_hit_per_m", location="body", description="USD per 1M cache-hit input tokens.", required=False, type="number"), Param(name="price_cache_miss_per_m", location="body", description="USD per 1M cache-miss input tokens.", required=False, type="number"), Param(name="price_output_per_m", location="body", description="USD per 1M output tokens.", required=False, type="number"), Param(name="price_input_per_m", location="body", description="USD per 1M input tokens (embed/vision), or flat USD per image for image routes.", required=False, type="number"), Param(name="context_tier_threshold_tokens", location="body", description="Input tokens above which tier-2 rates apply (0 disables tiering).", required=False, type="integer"), Param(name="price_cache_hit_per_m_tier2", location="body", description="Tier-2 USD per 1M cache-hit input tokens (unset = keep tier-1 rate above threshold).", required=False, type="number"), Param(name="price_cache_miss_per_m_tier2", location="body", description="Tier-2 USD per 1M cache-miss input tokens (unset = keep tier-1 rate above threshold).", required=False, type="number"), Param(name="price_output_per_m_tier2", location="body", description="Tier-2 USD per 1M output tokens (unset = keep tier-1 rate above threshold).", required=False, type="number"), Param(name="price_input_per_m_tier2", location="body", description="Tier-2 USD per 1M input tokens, embed/vision (unset = keep tier-1 rate above threshold).", required=False, type="number"), Param(name="off_peak_start_minute", location="body", description="Off-peak window start, UTC minutes since midnight (0-1439). Must be set together with off_peak_end_minute.", required=False, type="integer"), Param(name="off_peak_end_minute", location="body", description="Off-peak window end, UTC minutes since midnight (0-1439). A value less than the start wraps past midnight.", required=False, type="integer"), Param(name="off_peak_discount_pct", location="body", description="Percentage discount (0-100) applied to the active tier's rates during the off-peak window.", required=False, type="number"), Param(name="is_active", location="body", description="Whether the route is active ('1' or '0').", required=False, type="boolean"), ), ), Action( name="gateway_model_delete", method="DELETE", path="/admin/gateway/models/{source_model}", summary="Delete a gateway model route (admin only, confirmation required)", handler="http", requires_admin=True, params=(path("source_model", "Source model name."), confirm()), ), Action( name="gateway_quota_rules", method="GET", path="/admin/gateway/quota-rules", summary="List AI gateway quota rules and the current global defaults (admin only)", description=( "Returns JSON: every quota rule (each scoped by any combination of role/owner_kind, " "a specific user uid, and an app_reference label) with its 24h limit, current 24h spend " "against that exact scope, active flag, and label, plus the global per-role default caps " "used when no rule matches a request." ), handler="http", requires_admin=True, read_only=True, ), Action( name="gateway_quota_rule_set", method="POST", path="/admin/gateway/quota-rules", summary="Create or update an AI gateway quota rule (admin only)", description=( "Caps rolling-24h USD spend on /openai/v1/*. Scope by any combination of owner_kind " "(internal/key/user/admin/anonymous), a specific owner_id (user uid), and app_reference " "(the X-App-Reference header apps send). At least one of the three must be set - an " "unscoped cap belongs in the global default fields on the gateway service config instead. " "Leaving a dimension blank makes it a wildcard: an app_reference-only rule pools spend " "across every caller using that app; an owner_kind-only rule pools spend across every " "caller of that role. Setting owner_id pins the rule to one specific caller. When several " "rules match one request, the MOST SPECIFIC one wins (most non-blank dimensions); ties " "break toward the smaller limit. limit_usd of 0 means unlimited for that rule. Pass uid " "to update an existing rule instead of creating a new one." ), handler="http", requires_admin=True, params=( body("uid", "Existing rule uid to update; omit to create a new rule."), body("owner_kind", "Role to scope by: internal, key, user, admin, or anonymous. Blank = any role."), body("owner_id", "Specific user uid to scope by. Blank = any caller of the matched role."), body("app_reference", "App label to scope by (the X-App-Reference header). Blank = any app."), Param(name="limit_usd", location="body", description="Rolling 24h USD cap for this rule. 0 = unlimited.", required=True, type="number"), Param(name="is_active", location="body", description="Whether the rule is enforced ('1' or '0'). Defaults to active.", required=False, type="boolean"), body("label", "Optional admin-facing note describing what this rule is for."), ), ), Action( name="gateway_quota_rule_delete", method="DELETE", path="/admin/gateway/quota-rules/{uid}", summary="Delete an AI gateway quota rule (admin only, confirmation required)", handler="http", requires_admin=True, params=(path("uid", "Quota rule uid."), confirm()), ), )