diff --git a/drizzle-sqlite/0003_medical_rattler.sql b/drizzle-sqlite/0003_medical_rattler.sql
new file mode 100644
index 00000000..e8b705e1
--- /dev/null
+++ b/drizzle-sqlite/0003_medical_rattler.sql
@@ -0,0 +1 @@
+ALTER TABLE `api_keys` ADD `spending_rules` text;--> statement-breakpoint
diff --git a/drizzle-sqlite/meta/0003_snapshot.json b/drizzle-sqlite/meta/0003_snapshot.json
new file mode 100644
index 00000000..50e7c557
--- /dev/null
+++ b/drizzle-sqlite/meta/0003_snapshot.json
@@ -0,0 +1,1702 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "707bc16f-cdab-4049-bdeb-6db22f625cfd",
+ "prevId": "90b78acf-8072-4f96-bb8b-e3493fa4e278",
+ "tables": {
+ "api_key_upstreams": {
+ "name": "api_key_upstreams",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "api_key_id": {
+ "name": "api_key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "api_key_upstreams_api_key_id_idx": {
+ "name": "api_key_upstreams_api_key_id_idx",
+ "columns": ["api_key_id"],
+ "isUnique": false
+ },
+ "api_key_upstreams_upstream_id_idx": {
+ "name": "api_key_upstreams_upstream_id_idx",
+ "columns": ["upstream_id"],
+ "isUnique": false
+ },
+ "uq_api_key_upstream": {
+ "name": "uq_api_key_upstream",
+ "columns": ["api_key_id", "upstream_id"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "api_key_upstreams_api_key_id_api_keys_id_fk": {
+ "name": "api_key_upstreams_api_key_id_api_keys_id_fk",
+ "tableFrom": "api_key_upstreams",
+ "tableTo": "api_keys",
+ "columnsFrom": ["api_key_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "api_key_upstreams_upstream_id_upstreams_id_fk": {
+ "name": "api_key_upstreams_upstream_id_upstreams_id_fk",
+ "tableFrom": "api_key_upstreams",
+ "tableTo": "upstreams",
+ "columnsFrom": ["upstream_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "api_keys": {
+ "name": "api_keys",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key_hash": {
+ "name": "key_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key_value_encrypted": {
+ "name": "key_value_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_prefix": {
+ "name": "key_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_mode": {
+ "name": "access_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'unrestricted'"
+ },
+ "spending_rules": {
+ "name": "spending_rules",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "api_keys_key_hash_unique": {
+ "name": "api_keys_key_hash_unique",
+ "columns": ["key_hash"],
+ "isUnique": true
+ },
+ "api_keys_key_hash_idx": {
+ "name": "api_keys_key_hash_idx",
+ "columns": ["key_hash"],
+ "isUnique": false
+ },
+ "api_keys_is_active_idx": {
+ "name": "api_keys_is_active_idx",
+ "columns": ["is_active"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "billing_manual_price_overrides": {
+ "name": "billing_manual_price_overrides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "input_price_per_million": {
+ "name": "input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "output_price_per_million": {
+ "name": "output_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cache_read_input_price_per_million": {
+ "name": "cache_read_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cache_write_input_price_per_million": {
+ "name": "cache_write_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "billing_manual_price_overrides_model_unique": {
+ "name": "billing_manual_price_overrides_model_unique",
+ "columns": ["model"],
+ "isUnique": true
+ },
+ "billing_manual_price_overrides_model_idx": {
+ "name": "billing_manual_price_overrides_model_idx",
+ "columns": ["model"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "billing_model_prices": {
+ "name": "billing_model_prices",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "input_price_per_million": {
+ "name": "input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "output_price_per_million": {
+ "name": "output_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cache_read_input_price_per_million": {
+ "name": "cache_read_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cache_write_input_price_per_million": {
+ "name": "cache_write_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "max_input_tokens": {
+ "name": "max_input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "max_output_tokens": {
+ "name": "max_output_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "synced_at": {
+ "name": "synced_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "billing_model_prices_model_idx": {
+ "name": "billing_model_prices_model_idx",
+ "columns": ["model"],
+ "isUnique": false
+ },
+ "billing_model_prices_source_idx": {
+ "name": "billing_model_prices_source_idx",
+ "columns": ["source"],
+ "isUnique": false
+ },
+ "uq_billing_model_prices_model_source": {
+ "name": "uq_billing_model_prices_model_source",
+ "columns": ["model", "source"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "billing_price_sync_history": {
+ "name": "billing_price_sync_history",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "success_count": {
+ "name": "success_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "failure_count": {
+ "name": "failure_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "failure_reason": {
+ "name": "failure_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "billing_price_sync_history_created_at_idx": {
+ "name": "billing_price_sync_history_created_at_idx",
+ "columns": ["created_at"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "billing_tier_rules": {
+ "name": "billing_tier_rules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "threshold_input_tokens": {
+ "name": "threshold_input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "display_label": {
+ "name": "display_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "input_price_per_million": {
+ "name": "input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "output_price_per_million": {
+ "name": "output_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cache_read_input_price_per_million": {
+ "name": "cache_read_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cache_write_input_price_per_million": {
+ "name": "cache_write_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "billing_tier_rules_model_idx": {
+ "name": "billing_tier_rules_model_idx",
+ "columns": ["model"],
+ "isUnique": false
+ },
+ "billing_tier_rules_source_idx": {
+ "name": "billing_tier_rules_source_idx",
+ "columns": ["source"],
+ "isUnique": false
+ },
+ "uq_billing_tier_rules_model_source_threshold": {
+ "name": "uq_billing_tier_rules_model_source_threshold",
+ "columns": ["model", "source", "threshold_input_tokens"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "circuit_breaker_states": {
+ "name": "circuit_breaker_states",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'closed'"
+ },
+ "failure_count": {
+ "name": "failure_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "success_count": {
+ "name": "success_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_failure_at": {
+ "name": "last_failure_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "opened_at": {
+ "name": "opened_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_probe_at": {
+ "name": "last_probe_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "circuit_breaker_states_upstream_id_unique": {
+ "name": "circuit_breaker_states_upstream_id_unique",
+ "columns": ["upstream_id"],
+ "isUnique": true
+ },
+ "circuit_breaker_states_upstream_id_idx": {
+ "name": "circuit_breaker_states_upstream_id_idx",
+ "columns": ["upstream_id"],
+ "isUnique": false
+ },
+ "circuit_breaker_states_state_idx": {
+ "name": "circuit_breaker_states_state_idx",
+ "columns": ["state"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "circuit_breaker_states_upstream_id_upstreams_id_fk": {
+ "name": "circuit_breaker_states_upstream_id_upstreams_id_fk",
+ "tableFrom": "circuit_breaker_states",
+ "tableTo": "upstreams",
+ "columnsFrom": ["upstream_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "compensation_rules": {
+ "name": "compensation_rules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_builtin": {
+ "name": "is_builtin",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "capabilities": {
+ "name": "capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target_header": {
+ "name": "target_header",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sources": {
+ "name": "sources",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'missing_only'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "compensation_rules_name_unique": {
+ "name": "compensation_rules_name_unique",
+ "columns": ["name"],
+ "isUnique": true
+ },
+ "compensation_rules_enabled_idx": {
+ "name": "compensation_rules_enabled_idx",
+ "columns": ["enabled"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "request_billing_snapshots": {
+ "name": "request_billing_snapshots",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "request_log_id": {
+ "name": "request_log_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "api_key_id": {
+ "name": "api_key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "billing_status": {
+ "name": "billing_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "unbillable_reason": {
+ "name": "unbillable_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "price_source": {
+ "name": "price_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_input_price_per_million": {
+ "name": "base_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_output_price_per_million": {
+ "name": "base_output_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_cache_read_input_price_per_million": {
+ "name": "base_cache_read_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_cache_write_input_price_per_million": {
+ "name": "base_cache_write_input_price_per_million",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "matched_rule_type": {
+ "name": "matched_rule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "matched_rule_display_label": {
+ "name": "matched_rule_display_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "applied_tier_threshold": {
+ "name": "applied_tier_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model_max_input_tokens": {
+ "name": "model_max_input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model_max_output_tokens": {
+ "name": "model_max_output_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "input_multiplier": {
+ "name": "input_multiplier",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "output_multiplier": {
+ "name": "output_multiplier",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "prompt_tokens": {
+ "name": "prompt_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "completion_tokens": {
+ "name": "completion_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "total_tokens": {
+ "name": "total_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cache_read_tokens": {
+ "name": "cache_read_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cache_write_tokens": {
+ "name": "cache_write_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cache_read_cost": {
+ "name": "cache_read_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cache_write_cost": {
+ "name": "cache_write_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "final_cost": {
+ "name": "final_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "currency": {
+ "name": "currency",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'USD'"
+ },
+ "billed_at": {
+ "name": "billed_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "request_billing_snapshots_request_log_id_unique": {
+ "name": "request_billing_snapshots_request_log_id_unique",
+ "columns": ["request_log_id"],
+ "isUnique": true
+ },
+ "request_billing_snapshots_request_log_id_idx": {
+ "name": "request_billing_snapshots_request_log_id_idx",
+ "columns": ["request_log_id"],
+ "isUnique": false
+ },
+ "request_billing_snapshots_billing_status_idx": {
+ "name": "request_billing_snapshots_billing_status_idx",
+ "columns": ["billing_status"],
+ "isUnique": false
+ },
+ "request_billing_snapshots_model_idx": {
+ "name": "request_billing_snapshots_model_idx",
+ "columns": ["model"],
+ "isUnique": false
+ },
+ "request_billing_snapshots_created_at_idx": {
+ "name": "request_billing_snapshots_created_at_idx",
+ "columns": ["created_at"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "request_billing_snapshots_request_log_id_request_logs_id_fk": {
+ "name": "request_billing_snapshots_request_log_id_request_logs_id_fk",
+ "tableFrom": "request_billing_snapshots",
+ "tableTo": "request_logs",
+ "columnsFrom": ["request_log_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "request_billing_snapshots_api_key_id_api_keys_id_fk": {
+ "name": "request_billing_snapshots_api_key_id_api_keys_id_fk",
+ "tableFrom": "request_billing_snapshots",
+ "tableTo": "api_keys",
+ "columnsFrom": ["api_key_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "request_billing_snapshots_upstream_id_upstreams_id_fk": {
+ "name": "request_billing_snapshots_upstream_id_upstreams_id_fk",
+ "tableFrom": "request_billing_snapshots",
+ "tableTo": "upstreams",
+ "columnsFrom": ["upstream_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "request_logs": {
+ "name": "request_logs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "api_key_id": {
+ "name": "api_key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reasoning_effort": {
+ "name": "reasoning_effort",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "prompt_tokens": {
+ "name": "prompt_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "completion_tokens": {
+ "name": "completion_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "total_tokens": {
+ "name": "total_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cached_tokens": {
+ "name": "cached_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "reasoning_tokens": {
+ "name": "reasoning_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cache_creation_tokens": {
+ "name": "cache_creation_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cache_creation_5m_tokens": {
+ "name": "cache_creation_5m_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cache_creation_1h_tokens": {
+ "name": "cache_creation_1h_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cache_read_tokens": {
+ "name": "cache_read_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "status_code": {
+ "name": "status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "routing_duration_ms": {
+ "name": "routing_duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "routing_type": {
+ "name": "routing_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lb_strategy": {
+ "name": "lb_strategy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "priority_tier": {
+ "name": "priority_tier",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "failover_attempts": {
+ "name": "failover_attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "failover_history": {
+ "name": "failover_history",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "routing_decision": {
+ "name": "routing_decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "thinking_config": {
+ "name": "thinking_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "affinity_hit": {
+ "name": "affinity_hit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "affinity_migrated": {
+ "name": "affinity_migrated",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "ttft_ms": {
+ "name": "ttft_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_stream": {
+ "name": "is_stream",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "session_id_compensated": {
+ "name": "session_id_compensated",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "header_diff": {
+ "name": "header_diff",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "request_logs_api_key_id_idx": {
+ "name": "request_logs_api_key_id_idx",
+ "columns": ["api_key_id"],
+ "isUnique": false
+ },
+ "request_logs_upstream_id_idx": {
+ "name": "request_logs_upstream_id_idx",
+ "columns": ["upstream_id"],
+ "isUnique": false
+ },
+ "request_logs_created_at_idx": {
+ "name": "request_logs_created_at_idx",
+ "columns": ["created_at"],
+ "isUnique": false
+ },
+ "request_logs_routing_type_idx": {
+ "name": "request_logs_routing_type_idx",
+ "columns": ["routing_type"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "request_logs_api_key_id_api_keys_id_fk": {
+ "name": "request_logs_api_key_id_api_keys_id_fk",
+ "tableFrom": "request_logs",
+ "tableTo": "api_keys",
+ "columnsFrom": ["api_key_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "request_logs_upstream_id_upstreams_id_fk": {
+ "name": "request_logs_upstream_id_upstreams_id_fk",
+ "tableFrom": "request_logs",
+ "tableTo": "upstreams",
+ "columnsFrom": ["upstream_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "upstream_health": {
+ "name": "upstream_health",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_healthy": {
+ "name": "is_healthy",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_check_at": {
+ "name": "last_check_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_success_at": {
+ "name": "last_success_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "failure_count": {
+ "name": "failure_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "upstream_health_upstream_id_unique": {
+ "name": "upstream_health_upstream_id_unique",
+ "columns": ["upstream_id"],
+ "isUnique": true
+ },
+ "upstream_health_upstream_id_idx": {
+ "name": "upstream_health_upstream_id_idx",
+ "columns": ["upstream_id"],
+ "isUnique": false
+ },
+ "upstream_health_is_healthy_idx": {
+ "name": "upstream_health_is_healthy_idx",
+ "columns": ["is_healthy"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "upstream_health_upstream_id_upstreams_id_fk": {
+ "name": "upstream_health_upstream_id_upstreams_id_fk",
+ "tableFrom": "upstream_health",
+ "tableTo": "upstreams",
+ "columnsFrom": ["upstream_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "upstreams": {
+ "name": "upstreams",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "base_url": {
+ "name": "base_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "official_website_url": {
+ "name": "official_website_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "api_key_encrypted": {
+ "name": "api_key_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "timeout": {
+ "name": "timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 60
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "max_concurrency": {
+ "name": "max_concurrency",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight": {
+ "name": "weight",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "route_capabilities": {
+ "name": "route_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "allowed_models": {
+ "name": "allowed_models",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model_redirects": {
+ "name": "model_redirects",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "affinity_migration": {
+ "name": "affinity_migration",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "billing_input_multiplier": {
+ "name": "billing_input_multiplier",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "billing_output_multiplier": {
+ "name": "billing_output_multiplier",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "spending_rules": {
+ "name": "spending_rules",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "upstreams_name_unique": {
+ "name": "upstreams_name_unique",
+ "columns": ["name"],
+ "isUnique": true
+ },
+ "upstreams_name_idx": {
+ "name": "upstreams_name_idx",
+ "columns": ["name"],
+ "isUnique": false
+ },
+ "upstreams_is_active_idx": {
+ "name": "upstreams_is_active_idx",
+ "columns": ["is_active"],
+ "isUnique": false
+ },
+ "upstreams_priority_idx": {
+ "name": "upstreams_priority_idx",
+ "columns": ["priority"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
diff --git a/drizzle-sqlite/meta/_journal.json b/drizzle-sqlite/meta/_journal.json
index 507bcd92..8520de9b 100644
--- a/drizzle-sqlite/meta/_journal.json
+++ b/drizzle-sqlite/meta/_journal.json
@@ -22,6 +22,13 @@
"when": 1773927435295,
"tag": "0002_api_keys_access_mode",
"breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "6",
+ "when": 1773979738418,
+ "tag": "0003_medical_rattler",
+ "breakpoints": true
}
]
}
diff --git a/drizzle/0024_tiny_brother_voodoo.sql b/drizzle/0024_tiny_brother_voodoo.sql
new file mode 100644
index 00000000..bde6f9e9
--- /dev/null
+++ b/drizzle/0024_tiny_brother_voodoo.sql
@@ -0,0 +1 @@
+ALTER TABLE "api_keys" ADD COLUMN "spending_rules" json;
diff --git a/drizzle/meta/0024_snapshot.json b/drizzle/meta/0024_snapshot.json
new file mode 100644
index 00000000..e95a0bc5
--- /dev/null
+++ b/drizzle/meta/0024_snapshot.json
@@ -0,0 +1,1906 @@
+{
+ "id": "074f3ade-567b-4ff8-8320-05c83a4dcfcf",
+ "prevId": "2d41ed45-fa93-458b-99b2-eae9b5d57820",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.api_key_upstreams": {
+ "name": "api_key_upstreams",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "api_key_id": {
+ "name": "api_key_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "api_key_upstreams_api_key_id_idx": {
+ "name": "api_key_upstreams_api_key_id_idx",
+ "columns": [
+ {
+ "expression": "api_key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "api_key_upstreams_upstream_id_idx": {
+ "name": "api_key_upstreams_upstream_id_idx",
+ "columns": [
+ {
+ "expression": "upstream_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "api_key_upstreams_api_key_id_api_keys_id_fk": {
+ "name": "api_key_upstreams_api_key_id_api_keys_id_fk",
+ "tableFrom": "api_key_upstreams",
+ "tableTo": "api_keys",
+ "columnsFrom": [
+ "api_key_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "api_key_upstreams_upstream_id_upstreams_id_fk": {
+ "name": "api_key_upstreams_upstream_id_upstreams_id_fk",
+ "tableFrom": "api_key_upstreams",
+ "tableTo": "upstreams",
+ "columnsFrom": [
+ "upstream_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "uq_api_key_upstream": {
+ "name": "uq_api_key_upstream",
+ "nullsNotDistinct": false,
+ "columns": [
+ "api_key_id",
+ "upstream_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.api_keys": {
+ "name": "api_keys",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "key_hash": {
+ "name": "key_hash",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_value_encrypted": {
+ "name": "key_value_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key_prefix": {
+ "name": "key_prefix",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_mode": {
+ "name": "access_mode",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unrestricted'"
+ },
+ "spending_rules": {
+ "name": "spending_rules",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "api_keys_key_hash_idx": {
+ "name": "api_keys_key_hash_idx",
+ "columns": [
+ {
+ "expression": "key_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "api_keys_is_active_idx": {
+ "name": "api_keys_is_active_idx",
+ "columns": [
+ {
+ "expression": "is_active",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "api_keys_key_hash_unique": {
+ "name": "api_keys_key_hash_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "key_hash"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.billing_manual_price_overrides": {
+ "name": "billing_manual_price_overrides",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "input_price_per_million": {
+ "name": "input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "output_price_per_million": {
+ "name": "output_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cache_read_input_price_per_million": {
+ "name": "cache_read_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cache_write_input_price_per_million": {
+ "name": "cache_write_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "billing_manual_price_overrides_model_idx": {
+ "name": "billing_manual_price_overrides_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "billing_manual_price_overrides_model_unique": {
+ "name": "billing_manual_price_overrides_model_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "model"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.billing_model_prices": {
+ "name": "billing_model_prices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "input_price_per_million": {
+ "name": "input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "output_price_per_million": {
+ "name": "output_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cache_read_input_price_per_million": {
+ "name": "cache_read_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cache_write_input_price_per_million": {
+ "name": "cache_write_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_input_tokens": {
+ "name": "max_input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_output_tokens": {
+ "name": "max_output_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "synced_at": {
+ "name": "synced_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "billing_model_prices_model_idx": {
+ "name": "billing_model_prices_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "billing_model_prices_source_idx": {
+ "name": "billing_model_prices_source_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "uq_billing_model_prices_model_source": {
+ "name": "uq_billing_model_prices_model_source",
+ "nullsNotDistinct": false,
+ "columns": [
+ "model",
+ "source"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.billing_price_sync_history": {
+ "name": "billing_price_sync_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "success_count": {
+ "name": "success_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "failure_count": {
+ "name": "failure_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "failure_reason": {
+ "name": "failure_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "billing_price_sync_history_created_at_idx": {
+ "name": "billing_price_sync_history_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.billing_tier_rules": {
+ "name": "billing_tier_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "threshold_input_tokens": {
+ "name": "threshold_input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_label": {
+ "name": "display_label",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "input_price_per_million": {
+ "name": "input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "output_price_per_million": {
+ "name": "output_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cache_read_input_price_per_million": {
+ "name": "cache_read_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cache_write_input_price_per_million": {
+ "name": "cache_write_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "billing_tier_rules_model_idx": {
+ "name": "billing_tier_rules_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "billing_tier_rules_source_idx": {
+ "name": "billing_tier_rules_source_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "uq_billing_tier_rules_model_source_threshold": {
+ "name": "uq_billing_tier_rules_model_source_threshold",
+ "nullsNotDistinct": false,
+ "columns": [
+ "model",
+ "source",
+ "threshold_input_tokens"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.circuit_breaker_states": {
+ "name": "circuit_breaker_states",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'closed'"
+ },
+ "failure_count": {
+ "name": "failure_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "success_count": {
+ "name": "success_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_failure_at": {
+ "name": "last_failure_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "opened_at": {
+ "name": "opened_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_probe_at": {
+ "name": "last_probe_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "config": {
+ "name": "config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "circuit_breaker_states_upstream_id_idx": {
+ "name": "circuit_breaker_states_upstream_id_idx",
+ "columns": [
+ {
+ "expression": "upstream_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "circuit_breaker_states_state_idx": {
+ "name": "circuit_breaker_states_state_idx",
+ "columns": [
+ {
+ "expression": "state",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "circuit_breaker_states_upstream_id_upstreams_id_fk": {
+ "name": "circuit_breaker_states_upstream_id_upstreams_id_fk",
+ "tableFrom": "circuit_breaker_states",
+ "tableTo": "upstreams",
+ "columnsFrom": [
+ "upstream_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "circuit_breaker_states_upstream_id_unique": {
+ "name": "circuit_breaker_states_upstream_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "upstream_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.compensation_rules": {
+ "name": "compensation_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_builtin": {
+ "name": "is_builtin",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "capabilities": {
+ "name": "capabilities",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_header": {
+ "name": "target_header",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sources": {
+ "name": "sources",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'missing_only'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "compensation_rules_enabled_idx": {
+ "name": "compensation_rules_enabled_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "compensation_rules_name_unique": {
+ "name": "compensation_rules_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.request_billing_snapshots": {
+ "name": "request_billing_snapshots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "request_log_id": {
+ "name": "request_log_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "api_key_id": {
+ "name": "api_key_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_status": {
+ "name": "billing_status",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "unbillable_reason": {
+ "name": "unbillable_reason",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "price_source": {
+ "name": "price_source",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_input_price_per_million": {
+ "name": "base_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_output_price_per_million": {
+ "name": "base_output_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_cache_read_input_price_per_million": {
+ "name": "base_cache_read_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_cache_write_input_price_per_million": {
+ "name": "base_cache_write_input_price_per_million",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "matched_rule_type": {
+ "name": "matched_rule_type",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "matched_rule_display_label": {
+ "name": "matched_rule_display_label",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applied_tier_threshold": {
+ "name": "applied_tier_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model_max_input_tokens": {
+ "name": "model_max_input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model_max_output_tokens": {
+ "name": "model_max_output_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "input_multiplier": {
+ "name": "input_multiplier",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "output_multiplier": {
+ "name": "output_multiplier",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prompt_tokens": {
+ "name": "prompt_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "completion_tokens": {
+ "name": "completion_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_tokens": {
+ "name": "total_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_read_tokens": {
+ "name": "cache_read_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_write_tokens": {
+ "name": "cache_write_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_read_cost": {
+ "name": "cache_read_cost",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cache_write_cost": {
+ "name": "cache_write_cost",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "final_cost": {
+ "name": "final_cost",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "currency": {
+ "name": "currency",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'USD'"
+ },
+ "billed_at": {
+ "name": "billed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "request_billing_snapshots_request_log_id_idx": {
+ "name": "request_billing_snapshots_request_log_id_idx",
+ "columns": [
+ {
+ "expression": "request_log_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "request_billing_snapshots_billing_status_idx": {
+ "name": "request_billing_snapshots_billing_status_idx",
+ "columns": [
+ {
+ "expression": "billing_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "request_billing_snapshots_model_idx": {
+ "name": "request_billing_snapshots_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "request_billing_snapshots_created_at_idx": {
+ "name": "request_billing_snapshots_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "request_billing_snapshots_request_log_id_request_logs_id_fk": {
+ "name": "request_billing_snapshots_request_log_id_request_logs_id_fk",
+ "tableFrom": "request_billing_snapshots",
+ "tableTo": "request_logs",
+ "columnsFrom": [
+ "request_log_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "request_billing_snapshots_api_key_id_api_keys_id_fk": {
+ "name": "request_billing_snapshots_api_key_id_api_keys_id_fk",
+ "tableFrom": "request_billing_snapshots",
+ "tableTo": "api_keys",
+ "columnsFrom": [
+ "api_key_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "request_billing_snapshots_upstream_id_upstreams_id_fk": {
+ "name": "request_billing_snapshots_upstream_id_upstreams_id_fk",
+ "tableFrom": "request_billing_snapshots",
+ "tableTo": "upstreams",
+ "columnsFrom": [
+ "upstream_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "request_billing_snapshots_request_log_id_unique": {
+ "name": "request_billing_snapshots_request_log_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "request_log_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.request_logs": {
+ "name": "request_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "api_key_id": {
+ "name": "api_key_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "method": {
+ "name": "method",
+ "type": "varchar(10)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reasoning_effort": {
+ "name": "reasoning_effort",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prompt_tokens": {
+ "name": "prompt_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "completion_tokens": {
+ "name": "completion_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_tokens": {
+ "name": "total_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cached_tokens": {
+ "name": "cached_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "reasoning_tokens": {
+ "name": "reasoning_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_creation_tokens": {
+ "name": "cache_creation_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_creation_5m_tokens": {
+ "name": "cache_creation_5m_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_creation_1h_tokens": {
+ "name": "cache_creation_1h_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_read_tokens": {
+ "name": "cache_read_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "status_code": {
+ "name": "status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "routing_duration_ms": {
+ "name": "routing_duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "routing_type": {
+ "name": "routing_type",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lb_strategy": {
+ "name": "lb_strategy",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "priority_tier": {
+ "name": "priority_tier",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "failover_attempts": {
+ "name": "failover_attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "failover_history": {
+ "name": "failover_history",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "routing_decision": {
+ "name": "routing_decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thinking_config": {
+ "name": "thinking_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "affinity_hit": {
+ "name": "affinity_hit",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "affinity_migrated": {
+ "name": "affinity_migrated",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "ttft_ms": {
+ "name": "ttft_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_stream": {
+ "name": "is_stream",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "session_id_compensated": {
+ "name": "session_id_compensated",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "header_diff": {
+ "name": "header_diff",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "request_logs_api_key_id_idx": {
+ "name": "request_logs_api_key_id_idx",
+ "columns": [
+ {
+ "expression": "api_key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "request_logs_upstream_id_idx": {
+ "name": "request_logs_upstream_id_idx",
+ "columns": [
+ {
+ "expression": "upstream_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "request_logs_created_at_idx": {
+ "name": "request_logs_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "request_logs_routing_type_idx": {
+ "name": "request_logs_routing_type_idx",
+ "columns": [
+ {
+ "expression": "routing_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "request_logs_api_key_id_api_keys_id_fk": {
+ "name": "request_logs_api_key_id_api_keys_id_fk",
+ "tableFrom": "request_logs",
+ "tableTo": "api_keys",
+ "columnsFrom": [
+ "api_key_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "request_logs_upstream_id_upstreams_id_fk": {
+ "name": "request_logs_upstream_id_upstreams_id_fk",
+ "tableFrom": "request_logs",
+ "tableTo": "upstreams",
+ "columnsFrom": [
+ "upstream_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.upstream_health": {
+ "name": "upstream_health",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "upstream_id": {
+ "name": "upstream_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_healthy": {
+ "name": "is_healthy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "last_check_at": {
+ "name": "last_check_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_success_at": {
+ "name": "last_success_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "failure_count": {
+ "name": "failure_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "upstream_health_upstream_id_idx": {
+ "name": "upstream_health_upstream_id_idx",
+ "columns": [
+ {
+ "expression": "upstream_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "upstream_health_is_healthy_idx": {
+ "name": "upstream_health_is_healthy_idx",
+ "columns": [
+ {
+ "expression": "is_healthy",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "upstream_health_upstream_id_upstreams_id_fk": {
+ "name": "upstream_health_upstream_id_upstreams_id_fk",
+ "tableFrom": "upstream_health",
+ "tableTo": "upstreams",
+ "columnsFrom": [
+ "upstream_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "upstream_health_upstream_id_unique": {
+ "name": "upstream_health_upstream_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "upstream_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.upstreams": {
+ "name": "upstreams",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "base_url": {
+ "name": "base_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "official_website_url": {
+ "name": "official_website_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "api_key_encrypted": {
+ "name": "api_key_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "timeout": {
+ "name": "timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 60
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "max_concurrency": {
+ "name": "max_concurrency",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weight": {
+ "name": "weight",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "route_capabilities": {
+ "name": "route_capabilities",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_models": {
+ "name": "allowed_models",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model_redirects": {
+ "name": "model_redirects",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "affinity_migration": {
+ "name": "affinity_migration",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_input_multiplier": {
+ "name": "billing_input_multiplier",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "billing_output_multiplier": {
+ "name": "billing_output_multiplier",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "spending_rules": {
+ "name": "spending_rules",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "upstreams_name_idx": {
+ "name": "upstreams_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "upstreams_is_active_idx": {
+ "name": "upstreams_is_active_idx",
+ "columns": [
+ {
+ "expression": "is_active",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "upstreams_priority_idx": {
+ "name": "upstreams_priority_idx",
+ "columns": [
+ {
+ "expression": "priority",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "upstreams_name_unique": {
+ "name": "upstreams_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index e7e51156..211f7d43 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -169,6 +169,13 @@
"when": 1773927435294,
"tag": "0023_api_keys_access_mode",
"breakpoints": true
+ },
+ {
+ "idx": 24,
+ "version": "7",
+ "when": 1773979771393,
+ "tag": "0024_tiny_brother_voodoo",
+ "breakpoints": true
}
]
}
diff --git a/openspec/changes/add-api-key-spending-quota/.openspec.yaml b/openspec/changes/add-api-key-spending-quota/.openspec.yaml
new file mode 100644
index 00000000..4e618347
--- /dev/null
+++ b/openspec/changes/add-api-key-spending-quota/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-03-19
diff --git a/openspec/changes/add-api-key-spending-quota/design.md b/openspec/changes/add-api-key-spending-quota/design.md
new file mode 100644
index 00000000..585a3f85
--- /dev/null
+++ b/openspec/changes/add-api-key-spending-quota/design.md
@@ -0,0 +1,241 @@
+## Context
+
+当前项目已经具备以下相关能力:
+
+- `api_keys` 与 `api_key_upstreams` 支撑 API Key 的创建、编辑、启停和上游授权。
+- `/api/proxy/v1/[...path]` 在请求入口完成 API Key 校验、上游选择、请求转发、请求日志写入和计费快照持久化。
+- `request_billing_snapshots` 已经按 `api_key_id` 保存 `finalCost`、`billingStatus`、`billedAt` 等字段,能够作为金额限额的事实来源。
+- 上游侧已经存在 `upstream.spendingRules` 与 `upstream-quota-tracker`,支持 `daily`、`monthly`、`rolling` 三种周期和多规则 AND 语义。
+
+本次变更需要在不改变上游限额行为的前提下,为 API Key 增加金额限额能力,并且补齐密钥管理页的规则级额度状态展示。用户已经确认本次范围为:
+
+- 数据模型直接挂在 `api_keys.spending_rules`
+- 仅限制金额
+- 支持多条规则同时生效,语义与上游一致
+- 基于已计费金额做硬拒绝,接受并发下的小幅超额
+- 被额度拒绝的请求需要落请求日志,并计入密钥请求次数
+- `unbilled` 请求允许通过且不计入额度
+- 额度状态只在密钥管理页展示,且需要展示到规则级
+
+涉及模块横跨数据库 Schema、Admin API、代理入口、请求日志、计费快照消费聚合、前端表单与列表展示,属于典型的跨模块变更。
+
+### 请求与额度关系草图
+
+```text
+┌──────────────┐
+│ Client │
+└──────┬───────┘
+ │
+ ▼
+┌──────────────────────────────┐
+│ Proxy Route │
+│ verify key │
+│ check key spending quota │
+└──────┬───────────────────────┘
+ │ pass reject
+ │ │
+ ▼ ▼
+┌──────────────────────┐ ┌──────────────────────┐
+│ route + forward │ │ write rejection log │
+│ write request log │ │ return 429 │
+│ persist billing row │ └──────────────────────┘
+└──────┬───────────────┘
+ │
+ ▼
+┌──────────────────────────────┐
+│ API Key Quota Tracker │
+│ increment billed cost │
+│ periodic DB reconciliation │
+└──────────────────────────────┘
+```
+
+### 密钥管理页展示草图
+
+```text
+┌──────────────────────────────────────────────────────────────┐
+│ Key Name Status Access Spending Rules │
+├──────────────────────────────────────────────────────────────┤
+│ Production Key Active restricted │
+│ Daily [#######---] $72 / $100 resets in 6h │
+│ Rolling 6h [##########] $26 / $20 exceeded, recover...│
+├──────────────────────────────────────────────────────────────┤
+│ Sandbox Key Active unrestricted │
+│ No spending rules configured │
+└──────────────────────────────────────────────────────────────┘
+```
+
+视觉层级说明:
+
+- 第一层是密钥基础信息,保持当前表格结构不变。
+- 第二层是规则级状态块,每条规则独立展示已用金额、上限、占比、超额状态和恢复信息。
+- 超额规则优先使用危险态文本和边框强调,但不自动把密钥切成停用态,避免和 `is_active` 语义混淆。
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- 为 API Key 增加与上游一致的金额限额规则结构与校验逻辑。
+- 在代理入口实现 API Key 限额硬拒绝,并在超额后稳定阻断后续请求。
+- 基于 `request_billing_snapshots` 的已计费金额维护密钥额度状态。
+- 在密钥管理页展示每条规则的实时状态,包括 fixed window 重置时间和 rolling 规则预计恢复时间。
+- 为被额度拒绝请求提供可审计的请求日志记录,并让密钥维度统计能够包含这些拒绝请求。
+
+**Non-Goals:**
+
+- 不实现 Token、请求次数或按模型维度的密钥限额。
+- 不实现管理员手动清零、手动恢复额度或额度重置按钮。
+- 不保证多实例部署下的强一致额度判断。
+- 不在 Dashboard 总览页新增额度卡片或跨页面可视化。
+- 不解决“单次请求绝不允许超过上限”的预占用问题。
+
+## Decisions
+
+### 1. 将 `spending_rules` 直接存入 `api_keys`
+
+决策:
+
+- 在 `api_keys` 表中新增 `spending_rules` JSON 字段,结构与上游 `spendingRules` 保持同构:
+ - `period_type: "daily" | "monthly" | "rolling"`
+ - `limit: number`
+ - `period_hours?: number`
+
+原因:
+
+- 当前需求只覆盖金额限额,直接复用现有上游规则结构最简洁。
+- API Key 的创建和编辑已经集中在同一个表单与同一组 Admin API 中,挂在 `api_keys` 上能避免引入额外 CRUD。
+- 该结构已经在上游配额中被验证过,命名和行为都已有一致参照。
+
+备选方案:
+
+- 独立 `api_key_spending_rules` 表。该方案扩展性更强,但会让第一版的迁移、查询和表单交互复杂度明显上升,因此本次不采用。
+
+### 2. 引入独立的 `api-key-quota-tracker`,而不是直接复用上游 tracker
+
+决策:
+
+- 新建 API Key 额度追踪服务,复用上游 tracker 的周期语义、DB 校准和滚动恢复算法,但实体主键改为 `api_key_id`,返回结构改为密钥管理页所需的规则级状态。
+
+原因:
+
+- 上游 tracker 的业务语义是“路由过滤候选上游”,而 API Key tracker 的语义是“在入口拒绝请求并生成密钥状态展示”。
+- 两者虽然共享金额限额算法,但日志语义、调用方、返回结构和 UI 消费位置不同,直接复用会让服务职责混杂。
+- 独立服务便于后续在不影响上游路由逻辑的前提下演进密钥级规则展示和统计口径。
+
+备选方案:
+
+- 让现有 `upstream-quota-tracker` 接受泛型主体类型。该方案抽象层次更高,但会在第一版引入不必要的广域重构。
+
+### 3. 基于已计费金额执行硬拒绝,接受小幅超额
+
+决策:
+
+- 代理入口在 API Key 鉴权通过后、上游选路前检查当前额度状态。
+- 若任一规则已达到或超过限额,则直接返回 `429`。
+- 实际消费累加以 `billingStatus = billed` 的快照为准,不做预占用。
+
+原因:
+
+- 系统当前只能在请求完成并拿到 usage 与价格后得到精确金额,无法在请求开始前知道最终成本。
+- 采用已计费金额作为唯一事实来源,能让拒绝逻辑与账单口径保持一致。
+- 这与用户确认的“接受小幅超额”一致,避免引入复杂的预估和回滚机制。
+
+备选方案:
+
+- 请求开始前基于 `max_tokens` 预留预算。该方案更严格,但会把模型差异、流式中断、缓存计费和回滚全部引入第一版。
+
+### 4. 被额度拒绝的请求要写日志,但不归属上游
+
+决策:
+
+- 对额度拒绝请求写入 `request_logs`:
+ - `apiKeyId` 有值
+ - `upstreamId = null`
+ - `statusCode = 429`
+ - `errorMessage` 写入额度拒绝原因
+- 这类日志计入密钥请求次数,但不计入上游请求次数。
+
+原因:
+
+- 用户明确要求需要审计能力,并希望密钥层能看到被拒绝尝试。
+- `upstreamId = null` 能天然避免把这类请求算入上游 leaderboard 和上游请求统计。
+- 保留统一日志入口,便于后续排查“为什么这个密钥不可用”。
+
+备选方案:
+
+- 完全不记录日志。这样会损失排查与审计价值,本次不采用。
+
+### 5. `unbilled` 请求允许通过,且不计入额度
+
+决策:
+
+- 只有 `request_billing_snapshots.billingStatus = "billed"` 的请求会累加到密钥额度。
+- 价格缺失等导致的 `unbilled` 请求允许执行,不额外触发密钥额度拦截。
+
+原因:
+
+- 当前金额限额的目标是约束“已确认可计费金额”,而不是所有调用次数。
+- 与上游限额现有语义保持一致,降低认知差异。
+
+备选方案:
+
+- 只要配置了限额,遇到无法计费请求就直接拒绝。该方案更保守,但会让价格尚未维护的模型在有限额密钥下完全不可用。
+
+### 6. 密钥管理页复用现有列表 API,直接返回规则配置与规则级状态
+
+决策:
+
+- 扩展 `GET /api/admin/keys` 返回:
+ - `spending_rules`
+ - `spending_rule_statuses`
+ - `is_quota_exceeded`
+- 创建、编辑 API 同样接收和返回 `spending_rules`。
+
+原因:
+
+- 第一版只在密钥管理页展示额度状态,列表接口已经是该页的唯一数据源,直接扩展可避免新建只服务单页的接口。
+- 规则状态需要和规则配置一起展示,聚合在同一响应里能简化前端状态管理。
+
+备选方案:
+
+- 新建 `/api/admin/keys/quota`。这样可以拆分职责,但会增加请求次数和前端合并逻辑,本次不采用。
+
+### 7. rolling 规则的预计恢复时间通过 DB 回算获得
+
+决策:
+
+- 当 rolling 规则超额时,基于当前窗口内该密钥的 billed 快照时间序列,计算“累计金额重新低于限制值”的最早时间,作为 `estimated_recovery_at`。
+
+原因:
+
+- 仅返回 `null` 无法满足密钥管理页规则级展示需求。
+- 现有上游配额已经有相同的语义预期,可以沿用同类算法。
+
+备选方案:
+
+- rolling 规则不显示预计恢复时间。该方案实现简单,但不满足已确认的展示要求。
+
+## Risks / Trade-offs
+
+- [并发下可能小幅超额] → 通过设计文档和规格明确“按已计费金额硬拒绝”的边界,并在日志中保留拒绝与超额现象,避免误解为强一致预算锁。
+- [多实例下内存 tracker 不一致] → 第一版以单实例为主要目标,DB 校准仍保证最终一致;后续如扩为多实例,可再引入共享缓存或基于数据库的集中状态。
+- [`unbilled` 请求可能形成预算绕过口子] → 在规格中明确该行为是已接受的产品语义,并通过后续价格配置完善降低口子规模。
+- [列表接口返回的状态计算变重] → 仅对当前分页内密钥计算规则级状态,并复用内存 tracker + DB 校准,而不是每次请求都对全表做聚合。
+- [日志口径变化影响统计理解] → 通过 `upstreamId = null` 将额度拒绝请求与真实上游调用隔离,并在相关统计转换中明确密钥与上游的不同口径。
+
+## Migration Plan
+
+1. 为 PostgreSQL 与 SQLite 的 `api_keys` 表增加 `spending_rules` 字段,并生成数据库迁移。
+2. 扩展 API Key 的类型、校验和 Admin API,使创建、编辑、列表链路能够读写并返回规则配置。
+3. 引入 API Key 额度追踪服务,在进程启动后完成初始化和数据库校准。
+4. 在代理入口接入额度预检查,并为额度拒绝请求补齐日志写入。
+5. 扩展密钥管理页的表单和列表展示,确认规则级状态与时间信息能够正确渲染。
+6. 通过服务层、路由层和前端测试覆盖配置、拒绝、恢复、日志与显示口径。
+
+回滚策略:
+
+- 若上线后需要回退功能,可先移除代理入口额度检查与前端展示,再回滚 API 层对 `spending_rules` 的读写。
+- 已落库的 `spending_rules` 数据可以保留为空闲字段,不会影响旧逻辑继续运行。
+
+## Open Questions
+
+- 当前没有阻塞实现的开放问题;后续若需要支持多实例强一致预算、手动额度重置或非金额型限额,应作为独立变更处理。
diff --git a/openspec/changes/add-api-key-spending-quota/proposal.md b/openspec/changes/add-api-key-spending-quota/proposal.md
new file mode 100644
index 00000000..086e9c11
--- /dev/null
+++ b/openspec/changes/add-api-key-spending-quota/proposal.md
@@ -0,0 +1,43 @@
+## Why
+
+当前系统已经具备 API Key 管理、请求日志、计费快照和上游消费限额能力,但缺少针对单个 API Key 的消费约束。只依赖上游限额无法阻止单个密钥过度消耗预算,也无法在密钥管理页直观解释某个密钥为什么被拒绝、何时恢复可用,因此需要补齐密钥级金额限额能力。
+
+## What Changes
+
+- 为 API Key 新增 `spending_rules` 配置,支持零条或多条金额限额规则。
+- 支持与上游限额一致的周期语义:`daily`、`monthly`、`rolling`,其中 `rolling` 需要 `period_hours`。
+- 在代理请求入口增加 API Key 消费限额检查;当任一规则超额时,后续请求立即以硬拒绝方式返回。
+- 被 API Key 限额拒绝的请求仍写入请求日志,并计入密钥请求次数,但不计入上游请求次数。
+- `unbilled` 请求允许通过,且不计入 API Key 消费限额。
+- 在密钥管理页展示每条规则的已用金额、限额金额、占比、超额状态,以及 fixed window 的重置时间或 rolling window 的预计恢复时间。
+- 管理员调整限额规则后立即生效;若新限额已低于当前已用金额,对应密钥立即进入临时超额状态,待窗口恢复后自动可用。
+
+## Capabilities
+
+### New Capabilities
+- `api-key-spending-quota`: API Key 金额限额的配置、运行时拦截、消费追踪、管理台状态展示与拒绝日志语义。
+
+### Modified Capabilities
+- None.
+
+## Impact
+
+- Affected code:
+ - `src/lib/db/schema-pg.ts`
+ - `src/lib/db/schema-sqlite.ts`
+ - `src/lib/services/key-manager.ts`
+ - `src/app/api/admin/keys/route.ts`
+ - `src/app/api/admin/keys/[id]/route.ts`
+ - `src/app/api/proxy/v1/[...path]/route.ts`
+ - `src/lib/services/request-logger.ts`
+ - `src/lib/services/stats-service.ts`
+ - `src/components/admin/create-key-dialog.tsx`
+ - `src/components/admin/edit-key-dialog.tsx`
+ - `src/components/admin/keys-table.tsx`
+ - `src/hooks/use-api-keys.ts`
+ - `src/types/api.ts`
+- Affected systems:
+ - API Key 管理 Admin API
+ - 代理入口与请求日志链路
+ - 密钥管理页额度状态展示
+ - 基于请求日志与计费快照的密钥统计口径
diff --git a/openspec/changes/add-api-key-spending-quota/specs/api-key-spending-quota/spec.md b/openspec/changes/add-api-key-spending-quota/specs/api-key-spending-quota/spec.md
new file mode 100644
index 00000000..190f615f
--- /dev/null
+++ b/openspec/changes/add-api-key-spending-quota/specs/api-key-spending-quota/spec.md
@@ -0,0 +1,147 @@
+## ADDED Requirements
+
+### Requirement: API Key 消费限额配置
+系统 SHALL 允许管理员为每个 API Key 配置零条或多条消费限额规则,并将规则持久化到 `api_keys.spending_rules`。每条规则包含限额金额(USD)和周期类型(每天、每月、滚动 N 小时)。多条规则之间为 AND 语义:任一规则超额即视为该 API Key 超额。未配置任何规则的 API Key 不受消费限额约束。
+
+#### Scenario: 创建密钥时配置单条每日限额规则
+- **WHEN** 管理员创建 API Key 并设置 `spending_rules = [{ period_type: "daily", limit: 50 }]`
+- **THEN** 系统持久化该规则
+- **AND** 该密钥在当日已计费金额达到 $50 后进入临时超额状态
+
+#### Scenario: 创建密钥时配置多条叠加规则
+- **WHEN** 管理员创建 API Key 并设置 `spending_rules = [{ period_type: "daily", limit: 100 }, { period_type: "rolling", limit: 30, period_hours: 6 }]`
+- **THEN** 系统持久化全部规则
+- **AND** 该密钥在任一规则达到上限后进入临时超额状态
+
+#### Scenario: 不配置限额规则
+- **WHEN** 管理员创建或编辑 API Key 时不设置任何 `spending_rules`(空数组或 null)
+- **THEN** 该 API Key 不受消费限额约束
+- **AND** 行为与限额功能不存在时保持一致
+
+#### Scenario: 编辑时调低限额立即生效
+- **WHEN** 管理员将某个 API Key 的限额调整为低于当前已计费金额
+- **THEN** 新规则在保存后立即生效
+- **AND** 该 API Key 在后续请求中立即按超额状态处理
+
+#### Scenario: 移除全部规则后恢复额度约束豁免
+- **WHEN** 管理员编辑 API Key 并移除全部 `spending_rules`
+- **THEN** 该 API Key 的消费限额约束立即解除
+- **AND** 不需要管理员额外执行手动重置
+
+#### Scenario: rolling 规则缺少 period_hours
+- **WHEN** 管理员提交 `period_type = "rolling"` 且未提供 `period_hours`
+- **THEN** 系统 MUST 返回参数校验错误
+
+#### Scenario: 规则限额金额非法
+- **WHEN** 管理员提交的某条规则 `limit` 小于或等于 0
+- **THEN** 系统 MUST 返回参数校验错误
+
+### Requirement: 代理入口消费限额硬拒绝
+系统 SHALL 在 API Key 鉴权通过后、选择上游之前检查该密钥的消费限额状态。当任一规则在当前周期内的已计费金额达到或超过上限时,系统 MUST 立即拒绝后续请求,而不是继续进入上游路由或转发流程。
+
+#### Scenario: 未超额的密钥允许继续请求
+- **WHEN** API Key 已配置限额规则且所有规则当前已计费金额均低于上限
+- **THEN** 系统允许请求继续进入后续路由与转发流程
+
+#### Scenario: 任一规则超额时拒绝请求
+- **WHEN** API Key 的任一消费限额规则已达到或超过当前周期上限
+- **THEN** 系统 MUST 以拒绝响应终止请求
+- **AND** 系统 MUST 不再为该请求选择或调用任何上游
+
+#### Scenario: 超额状态是临时的
+- **WHEN** API Key 因固定窗口或滚动窗口限额而超额
+- **THEN** 该 API Key 仍保持 `is_active = true`
+- **AND** 仅在额度恢复到限制以下后自动恢复可用
+
+#### Scenario: 固定窗口恢复后重新可用
+- **WHEN** daily 或 monthly 规则进入新的周期且当前周期实际已计费金额低于上限
+- **THEN** 该 API Key 在下一次额度状态计算后恢复为可用
+
+#### Scenario: 滚动窗口恢复后重新可用
+- **WHEN** rolling 规则窗口外的旧消费滑出后,当前窗口内已计费金额重新低于上限
+- **THEN** 该 API Key 在下一次额度状态计算后恢复为可用
+
+### Requirement: 已计费金额追踪与校准
+系统 SHALL 仅基于 `request_billing_snapshots` 中 `billingStatus = "billed"` 的记录追踪 API Key 的消费限额状态,并通过内存累加与数据库校准保证额度状态与已计费金额保持一致。
+
+#### Scenario: billed 请求计入额度
+- **WHEN** 某次代理请求完成计费并生成 `billingStatus = "billed"` 的快照
+- **THEN** 系统 MUST 将该请求的 `finalCost` 累加到对应 API Key 的所有适用规则中
+
+#### Scenario: unbilled 请求不计入额度
+- **WHEN** 某次代理请求因价格缺失或其他原因生成 `billingStatus = "unbilled"` 的快照
+- **THEN** 系统 MUST 不将该请求计入 API Key 消费限额
+- **AND** 该请求本身仍允许执行
+
+#### Scenario: 启动后全量校准额度
+- **WHEN** API Key 额度追踪器首次初始化
+- **THEN** 系统 MUST 从 `request_billing_snapshots` 聚合所有配置了限额规则的 API Key 当前周期已计费金额
+
+#### Scenario: 定期数据库校准
+- **WHEN** 额度追踪器的校准周期触发
+- **THEN** 系统 MUST 用数据库聚合结果覆盖内存中的当前消费值
+- **AND** rolling 窗口需要通过校准移除已滑出窗口的旧消费
+
+#### Scenario: fixed window 使用 UTC 周期边界
+- **WHEN** 系统计算 daily 或 monthly 规则的当前周期
+- **THEN** 系统 MUST 使用 UTC 日期和 UTC 月初作为周期边界
+
+### Requirement: 限额拒绝请求日志与统计口径
+系统 SHALL 为因 API Key 消费限额被拒绝的请求保留请求日志,并将其计入密钥请求次数;但这类请求 MUST 不计入上游请求次数。
+
+#### Scenario: 额度拒绝请求写入日志
+- **WHEN** 某次请求因 API Key 消费限额超额而被拒绝
+- **THEN** 系统 MUST 写入一条请求日志
+- **AND** 该日志 MUST 关联对应 `api_key_id`
+- **AND** 该日志 MUST 不关联任何 `upstream_id`
+- **AND** 该日志 MUST 标识为额度拒绝错误
+
+#### Scenario: 密钥请求统计包含额度拒绝
+- **WHEN** 系统按 API Key 聚合请求次数
+- **THEN** 因消费限额被拒绝的请求 MUST 计入对应密钥的请求次数
+
+#### Scenario: 上游请求统计不包含额度拒绝
+- **WHEN** 系统按上游聚合请求次数
+- **THEN** 因消费限额被拒绝的请求 MUST 不计入任何上游的请求次数
+
+### Requirement: 密钥管理页规则级额度状态展示
+系统 SHALL 在密钥管理页展示每个 API Key 的每条消费限额规则状态,包括当前已用金额、限额金额、占比、是否超额,以及 fixed window 的重置时间或 rolling window 的预计恢复时间。
+
+#### Scenario: 列表展示多条规则状态
+- **WHEN** 某个 API Key 配置了多条 `spending_rules`
+- **THEN** 密钥管理页 MUST 为每条规则分别展示一条状态信息
+- **AND** 每条状态信息 MUST 包含周期类型、已用金额、限额金额和占比
+
+#### Scenario: 固定窗口展示重置时间
+- **WHEN** 某条规则的 `period_type` 为 `daily` 或 `monthly`
+- **THEN** 密钥管理页 MUST 展示该规则的下一个周期重置时间
+
+#### Scenario: rolling 规则展示预计恢复时间
+- **WHEN** 某条规则的 `period_type` 为 `rolling`
+- **THEN** 密钥管理页 MUST 展示该规则的预计恢复时间
+
+#### Scenario: 超额规则高亮显示
+- **WHEN** 某条规则当前已达到或超过限额
+- **THEN** 密钥管理页 MUST 对该规则显示明确的超额状态提示
+
+#### Scenario: 无规则密钥不展示额度状态块
+- **WHEN** 某个 API Key 未配置 `spending_rules`
+- **THEN** 密钥管理页 MUST 不渲染规则级额度状态信息
+
+### Requirement: API Key 管理接口返回规则与状态
+系统 SHALL 通过现有 API Key Admin API 返回限额规则配置与规则级额度状态,以支撑创建、编辑和密钥管理页展示。
+
+#### Scenario: 创建接口接收 spending_rules
+- **WHEN** 管理员调用创建 API Key 接口并提交 `spending_rules`
+- **THEN** 系统 MUST 校验并持久化规则配置
+- **AND** 创建响应 MUST 返回该密钥的规则配置
+
+#### Scenario: 更新接口接收 spending_rules
+- **WHEN** 管理员调用更新 API Key 接口并提交新的 `spending_rules`
+- **THEN** 系统 MUST 校验并更新规则配置
+- **AND** 更新响应 MUST 返回更新后的规则配置
+
+#### Scenario: 列表接口返回规则级额度状态
+- **WHEN** 管理员调用 API Key 列表接口
+- **THEN** 系统 MUST 为每个密钥返回其 `spending_rules`
+- **AND** 对于已配置规则的密钥,系统 MUST 返回每条规则的额度状态、超额标识和对应时间信息
diff --git a/openspec/changes/add-api-key-spending-quota/tasks.md b/openspec/changes/add-api-key-spending-quota/tasks.md
new file mode 100644
index 00000000..fd6b5983
--- /dev/null
+++ b/openspec/changes/add-api-key-spending-quota/tasks.md
@@ -0,0 +1,31 @@
+## 1. Schema 与类型扩展
+
+- [x] 1.1 为 PostgreSQL 与 SQLite 的 `api_keys` 表新增 `spending_rules` 字段,并生成对应迁移
+- [x] 1.2 在 `src/types/api.ts`、服务层输入输出类型与 API transformer 中补齐 `spending_rules`、规则级状态与超额标识字段
+- [x] 1.3 扩展 API Key 创建与更新请求的服务层参数定义,确保规则结构与上游限额规则的校验口径一致
+
+## 2. API Key 额度追踪与拒绝链路
+
+- [x] 2.1 新增 API Key 额度追踪服务,支持 fixed window、rolling window、规则级状态、预计恢复时间、启动初始化与定期数据库校准
+- [x] 2.2 在 API Key 管理服务与 Admin API 中接入 `spending_rules` 的创建、编辑、列表返回与立即生效逻辑
+- [x] 2.3 在 `/api/proxy/v1/[...path]` 中接入 API Key 额度预检查,并在超额时返回 `429` 且阻止上游路由与转发
+- [x] 2.4 为额度拒绝请求补齐请求日志写入语义,确保其计入密钥请求次数且不归属任何上游
+- [x] 2.5 在 billed 快照落库后接入 API Key 额度增量累加,并保持 `unbilled` 请求不计入额度
+
+## 3. 密钥管理页配置与状态展示
+
+- [x] 3.1 在创建与编辑密钥对话框中增加动态 `spending_rules` 配置区域,支持多规则添加、删除与 rolling 参数联动
+- [x] 3.2 在密钥列表表格中增加规则级额度状态展示,显示已用金额、限额金额、占比、超额提示、重置时间或预计恢复时间
+- [x] 3.3 更新 `use-api-keys` 及相关页面状态处理,确保新增规则配置和规则级状态能够正确回显与刷新
+
+## 4. 统计口径与接口一致性
+
+- [x] 4.1 调整密钥维度统计与转换逻辑,使额度拒绝请求计入密钥请求次数
+- [x] 4.2 校准上游维度统计口径,确保额度拒绝请求不计入任何上游请求次数
+- [x] 4.3 检查并补齐 API 响应中的错误码、错误消息与规则状态字段,保证前后端语义一致
+
+## 5. 测试与验收
+
+- [x] 5.1 为 API Key 管理服务、额度追踪服务与代理拒绝链路补齐单元测试,覆盖多规则、立即生效、rolling 恢复与 `unbilled` 豁免
+- [x] 5.2 为 Admin API 路由与密钥管理页组件补齐测试,覆盖规则配置、列表状态展示和额度拒绝日志口径
+- [x] 5.3 运行与本次变更相关的测试和质量检查,确认 proposal 对应实现具备可提交状态
diff --git a/openspec/config.yaml b/openspec/config.yaml
index f3d0e5f0..af9c4d42 100644
--- a/openspec/config.yaml
+++ b/openspec/config.yaml
@@ -26,7 +26,7 @@ rules:
- 涉及前端变更时,需包含关键场景的布局示意图和视觉层级说明
tasks:
- task.md设计:按照线性流程分解任务,每个任务都要有明确的输入和输出,确保阶段性的任务是可提交的,每个阶段的任务都要有明确的目标和验收标准
- - 执行流程:每完成一个任务就立即勾选任务完成,每完成一个阶段的任务就就行一次阶段性的提交,每次提交都需要通过质量门禁
+ - 执行流程:每完成一个任务就立即勾选任务完成,进行任务时需要自主规划提交节点并主动完成提交,每次提交都需要通过质量门禁
context: |
语言:中文(简体)
diff --git a/src/app/[locale]/(dashboard)/keys/page.tsx b/src/app/[locale]/(dashboard)/keys/page.tsx
index 7da04aab..5e933185 100644
--- a/src/app/[locale]/(dashboard)/keys/page.tsx
+++ b/src/app/[locale]/(dashboard)/keys/page.tsx
@@ -73,33 +73,20 @@ export default function KeysPage() {
<>
{t("managementDesc")}
-+ {t("spendingRules")} +
++ {t("spendingRulesDesc")} +
++ {t("spendingRulesEmpty")} +
+ ) : ( +