افزودن ارائه‌دهندهٔ agy (Google Antigravity) با لاگین headless و پول چرخشی اکانت

- Adapter اکانت‌محور با چرخش round-robin و بنچ‌شدن خودکار روی ۴۲۹/۴۰۱
- ورود ادمین از طریق URL بازگشتی گوگل (PKCE) و ذخیرهٔ رمزنگاری‌شدهٔ refresh token
- صفحهٔ مدیریت اکانت‌های agy در پنل ادمین به همراه تست و فعال/غیرفعال‌سازی
- مهاجرت Alembic b1c2d3e4f5a6 برای ستون‌های اکانت agy
- seed همیشه کاتالوگ agy را می‌سازد و فقط با AI_DEFAULT_PROVIDER=agy مسیریابی می‌شود
This commit is contained in:
mjalmoudy94
2026-10-13 08:20:27 +03:30
parent 54074f10c3
commit 3d1e98f4fb
25 changed files with 3073 additions and 10 deletions
@@ -0,0 +1,57 @@
"""add agy account columns to ai_model_credentials
Turns ``ai_model_credentials`` into a store that can hold *accounts* for the
agy (Google Antigravity) provider, not just API keys: ``email`` / ``project_id``
carry the account identity, ``status`` + ``cooldown_until`` drive the rotation
pool (skip a rate-limited account until its reset time), and
``last_error`` / ``last_used_at`` support the admin console.
Every new NOT NULL column carries a ``server_default`` so existing rows
backfill without a table rewrite and stay insertable from plain SQL.
Revision ID: b1c2d3e4f5a6
Revises: f5ba57af2906
Create Date: 2026-10-13 02:10:00.000000
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = 'b1c2d3e4f5a6'
down_revision: str | None = 'f5ba57af2906'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
#: ``(name, type, server_default)``. ``None`` default means nullable, no default.
_NEW_COLUMNS: tuple[tuple[str, sa.types.TypeEngine, str | None], ...] = (
("email", sa.String(length=320), "''"),
("project_id", sa.String(length=160), "''"),
("status", sa.String(length=16), "'active'"),
("cooldown_until", sa.DateTime(timezone=True), None),
("last_error", sa.String(length=512), "''"),
("last_used_at", sa.DateTime(timezone=True), None),
)
def upgrade() -> None:
for name, column_type, default in _NEW_COLUMNS:
op.add_column(
"ai_model_credentials",
sa.Column(
name,
column_type,
nullable=default is None,
server_default=None if default is None else sa.text(default),
),
)
def downgrade() -> None:
for name, _type, _default in _NEW_COLUMNS:
op.drop_column("ai_model_credentials", name)