mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-06 17:57:43 +02:00
[eric] sub bug fix
This commit is contained in:
@@ -295,6 +295,18 @@ async def subscriptions_connect(body: dict):
|
||||
if not is_running():
|
||||
raise HTTPException(status_code=503, detail="9Router not available. Please install Node.js.")
|
||||
|
||||
# If reconnecting a primary lane (e.g. gemini-cli), drop its cascade
|
||||
# siblings first. The registry prefers antigravity over gemini-cli
|
||||
# when both are present, so a stale antigravity token would keep
|
||||
# 400ing even after gemini-cli refreshes. Wiping the sibling forces
|
||||
# the registry onto the freshly reconnected lane.
|
||||
cascade = _PROVIDER_CASCADE_REMOVES.get(provider, [])
|
||||
if cascade:
|
||||
try:
|
||||
await _delete_provider_connections(cascade)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
result = await start_oauth(provider)
|
||||
|
||||
@@ -680,21 +692,61 @@ async def list_models():
|
||||
return {"models": result, "notes": notes}
|
||||
|
||||
|
||||
# Google's two OAuth lanes (gemini-cli and antigravity) share user-facing
|
||||
# meaning (both = "Google subscription") but 9Router treats them as
|
||||
# separate connections with independent token lifecycles. The registry
|
||||
# prefers `ag/` over `gc/` whenever AG is active because AG bypasses the
|
||||
# thoughtSignature validator that breaks multi-step tool turns. That
|
||||
# preference becomes a footgun when AG's token expires silently: the
|
||||
# user reconnects "Google", only gemini-cli refreshes, and every request
|
||||
# still routes through the stale AG token -> 400 Invalid argument.
|
||||
#
|
||||
# Cascade is one-directional. gemini-cli is the primary lane the UI
|
||||
# exposes; operations on it sweep antigravity too. Direct operations on
|
||||
# antigravity (e.g. an explicit AG opt-in/out path) MUST NOT cascade
|
||||
# back to gemini-cli or we'd nuke the user's main Google connection.
|
||||
_PROVIDER_CASCADE_REMOVES: dict[str, list[str]] = {
|
||||
"gemini-cli": ["antigravity"],
|
||||
}
|
||||
|
||||
|
||||
async def _delete_provider_connections(providers: list[str]) -> int:
|
||||
"""Delete all 9Router connections whose provider is in the given list.
|
||||
Returns the count actually removed. Silent if 9Router is unreachable."""
|
||||
import httpx
|
||||
from backend.apps.nine_router import NINE_ROUTER_API, get_providers
|
||||
try:
|
||||
connections = await get_providers()
|
||||
except Exception:
|
||||
return 0
|
||||
targets = [c for c in connections if c.get("provider") in providers and c.get("id")]
|
||||
removed = 0
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
for c in targets:
|
||||
try:
|
||||
await client.delete(f"{NINE_ROUTER_API}/providers/{c['id']}")
|
||||
removed += 1
|
||||
except Exception:
|
||||
pass
|
||||
return removed
|
||||
|
||||
|
||||
@agents.router.post("/subscriptions/disconnect")
|
||||
async def subscriptions_disconnect(body: dict):
|
||||
"""Disconnect a subscription provider via 9Router."""
|
||||
import httpx
|
||||
"""Disconnect a subscription provider via 9Router.
|
||||
|
||||
For Google's paired lanes (gemini-cli + antigravity), wipe BOTH so a
|
||||
subsequent reconnect lands on a clean slate instead of resurrecting
|
||||
a stale sibling.
|
||||
"""
|
||||
provider = body.get("provider", "")
|
||||
if not provider:
|
||||
raise HTTPException(status_code=400, detail="provider required")
|
||||
|
||||
try:
|
||||
from backend.apps.nine_router import NINE_ROUTER_API, get_providers
|
||||
connections = await get_providers()
|
||||
conn = next((c for c in connections if c.get("provider") == provider), None)
|
||||
if conn and conn.get("id"):
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
await client.delete(f"{NINE_ROUTER_API}/providers/{conn['id']}")
|
||||
to_remove = [provider, *_PROVIDER_CASCADE_REMOVES.get(provider, [])]
|
||||
removed = await _delete_provider_connections(to_remove)
|
||||
if removed:
|
||||
from backend.apps.service.client import sync as _sync
|
||||
from backend.apps.settings.settings import load_settings
|
||||
_sync(load_settings().model_dump())
|
||||
|
||||
@@ -416,14 +416,19 @@ def resolve_model_id_for_sdk(short_name: str, settings: AppSettings) -> str:
|
||||
return entry.get("model_id", short_name)
|
||||
if getattr(settings, "anthropic_api_key", None):
|
||||
return entry.get("model_id", short_name)
|
||||
# Gemini lane order: AI Studio apikey → Antigravity OAuth → Gemini CLI.
|
||||
# Gemini lane order: AI Studio apikey, Antigravity OAuth, Gemini CLI.
|
||||
# AG bypasses the thoughtSignature validator that breaks multi-step tool
|
||||
# turns on gc/. Without it, every Gemini turn 400s after the first tool
|
||||
# call with "Thought signature is not valid".
|
||||
_ANTIGRAVITY_MAP = {
|
||||
# gemini-3-pro-preview disabled — AG returns 404 even with active conn.
|
||||
# gemini-3-pro-preview disabled: AG returns 404 even with active conn.
|
||||
# gemini-3.1-pro-preview disabled: AG's `gemini-3.1-pro-high` variant
|
||||
# 400s every request with "invalid argument" (the `-high` thinking-
|
||||
# budget alias on AG requires a thinking_config the CLI doesn't
|
||||
# emit). Falls through to gc/gemini-3.1-pro-preview, which works
|
||||
# for non-tool turns; multi-step tool turns still hit the
|
||||
# thoughtSignature validator but that's a separate fight.
|
||||
"gemini-3-flash-preview": "gemini-3-flash",
|
||||
"gemini-3.1-pro-preview": "gemini-3.1-pro-high",
|
||||
"gemini-3.1-flash-lite-preview": "gemini-3-flash",
|
||||
}
|
||||
if entry.get("api") == "gemini-cli":
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openswarm",
|
||||
"version": "1.0.32",
|
||||
"version": "1.1.32",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openswarm",
|
||||
"version": "1.0.32",
|
||||
"version": "1.1.32",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"electron-updater": "^6.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openswarm",
|
||||
"version": "1.0.32",
|
||||
"version": "1.1.32",
|
||||
"description": "OpenSwarm — AI Agent Orchestrator",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user