2026-07-04 19:16:32 +00:00
# retoor <retoor@molodetz.nl>
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 "
2026-07-09 02:52:54 +02:00
"chat-completions endpoint; the embeddings and images endpoints are derived from it."
2026-07-04 19:16:32 +00:00
),
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 "
2026-07-09 02:52:54 +02:00
"(chat/embed/image), optional vision model, context window, and per-model pricing economy "
"(including any tiered/off-peak pricing configured on it)."
2026-07-04 19:16:32 +00:00
),
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 "
2026-07-09 02:52:54 +02:00
"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."
2026-07-04 19:16:32 +00:00
),
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 ),
2026-07-09 02:52:54 +02:00
body ( "kind" , "Route kind: 'chat', 'embed', or 'image'." ),
2026-07-04 19:16:32 +00:00
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" ),
2026-07-09 02:52:54 +02:00
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" ),
2026-07-04 19:16:32 +00:00
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 ()),
),
2026-07-21 09:48:37 +02:00
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." ),
),
),
2026-07-27 11:17:48 +02:00
Action (
name = "gateway_quota_reset" ,
method = "POST" ,
path = "/admin/gateway/quota-resets" ,
summary = "Reset the rolling-24h AI gateway spend so a capped caller can call again (admin only)" ,
description = (
"Clears the counted spend for a scope without deleting any usage history, so the "
"cost analytics on /admin/ai-usage stay intact. Scope it exactly like a quota rule: "
"owner_kind (internal/key/user/admin/anonymous), a specific owner_id (user uid), and "
"app_reference (the X-App-Reference header). Leaving all three blank resets every "
"caller. A reset only clears spend recorded BEFORE it - new calls start counting "
"again immediately against the same limit. Use this when an app is stuck on "
"'AI gateway daily quota exceeded' and you want it running again without raising "
"its cap."
),
handler = "http" ,
requires_admin = True ,
params = (
body ( "owner_kind" , "Role to scope by: internal, key, user, admin, or anonymous. Blank = every role." ),
body ( "owner_id" , "Specific user uid to scope by. Blank = every caller." ),
body ( "app_reference" , "App label to scope by (the X-App-Reference header). Blank = every app." ),
confirm (),
),
),
2026-07-21 09:48:37 +02:00
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 ()),
),
2026-07-04 19:16:32 +00:00
)