agentstack-cli 0.4.2rc9__tar.gz → 0.4.2rc10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/PKG-INFO +1 -1
  2. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/pyproject.toml +1 -1
  3. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/api.py +3 -3
  4. agentstack_cli-0.4.2rc10/src/agentstack_cli/auth_manager.py +241 -0
  5. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/agent.py +2 -1
  6. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/server.py +39 -4
  7. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/configuration.py +1 -1
  8. agentstack_cli-0.4.2rc10/src/agentstack_cli/data/helm-chart.tgz +0 -0
  9. agentstack_cli-0.4.2rc9/src/agentstack_cli/auth_manager.py +0 -126
  10. agentstack_cli-0.4.2rc9/src/agentstack_cli/data/helm-chart.tgz +0 -0
  11. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/README.md +0 -0
  12. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/__init__.py +0 -0
  13. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/async_typer.py +0 -0
  14. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/__init__.py +0 -0
  15. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/build.py +0 -0
  16. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/mcp.py +0 -0
  17. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/model.py +0 -0
  18. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/platform/__init__.py +0 -0
  19. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/platform/base_driver.py +0 -0
  20. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/platform/istio.py +0 -0
  21. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/platform/lima_driver.py +0 -0
  22. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/platform/wsl_driver.py +0 -0
  23. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/commands/self.py +0 -0
  24. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/console.py +0 -0
  25. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/data/.gitignore +0 -0
  26. {agentstack_cli-0.4.2rc9 → agentstack_cli-0.4.2rc10}/src/agentstack_cli/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agentstack-cli
3
- Version: 0.4.2rc9
3
+ Version: 0.4.2rc10
4
4
  Summary: Agent Stack CLI
5
5
  Author: IBM Corp.
6
6
  Requires-Dist: anyio~=4.10.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentstack-cli"
3
- version = "0.4.2-rc9"
3
+ version = "0.4.2-rc10"
4
4
  description = "Agent Stack CLI"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "IBM Corp." }]
@@ -44,7 +44,7 @@ async def api_request(
44
44
  timeout=60,
45
45
  headers=(
46
46
  {"Authorization": f"Bearer {token}"}
47
- if use_auth and (token := config.auth_manager.load_auth_token())
47
+ if use_auth and (token := await config.auth_manager.load_auth_token())
48
48
  else {}
49
49
  ),
50
50
  )
@@ -83,7 +83,7 @@ async def api_stream(
83
83
  timeout=timedelta(hours=1).total_seconds(),
84
84
  headers=(
85
85
  {"Authorization": f"Bearer {token}"}
86
- if use_auth and (token := config.auth_manager.load_auth_token())
86
+ if use_auth and (token := await config.auth_manager.load_auth_token())
87
87
  else {}
88
88
  ),
89
89
  ) as response,
@@ -108,7 +108,7 @@ async def a2a_client(agent_card: AgentCard, use_auth: bool = True) -> AsyncItera
108
108
  async with httpx.AsyncClient(
109
109
  headers=(
110
110
  {"Authorization": f"Bearer {token}"}
111
- if use_auth and (token := config.auth_manager.load_auth_token())
111
+ if use_auth and (token := await config.auth_manager.load_auth_token())
112
112
  else {}
113
113
  ),
114
114
  follow_redirects=True,
@@ -0,0 +1,241 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ import pathlib
4
+ import time
5
+ import typing
6
+ from collections import defaultdict
7
+ from typing import Any
8
+
9
+ import httpx
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class AuthToken(BaseModel):
14
+ access_token: str
15
+ token_type: str = "Bearer"
16
+ expires_in: int | None = None
17
+ expires_at: int | None = None
18
+ refresh_token: str | None = None
19
+ scope: str | None = None
20
+
21
+
22
+ class AuthServer(BaseModel):
23
+ client_id: str = "df82a687-d647-4247-838b-7080d7d83f6c" # Backwards compatibility default
24
+ client_secret: str | None = None
25
+ token: AuthToken | None = None
26
+ registration_token: str | None = None
27
+
28
+
29
+ class Server(BaseModel):
30
+ authorization_servers: dict[str, AuthServer] = Field(default_factory=dict)
31
+
32
+
33
+ class Auth(BaseModel):
34
+ version: typing.Literal[1] = 1
35
+ servers: defaultdict[str, typing.Annotated[Server, Field(default_factory=Server)]] = Field(
36
+ default_factory=lambda: defaultdict(Server)
37
+ )
38
+ active_server: str | None = None
39
+ active_auth_server: str | None = None
40
+
41
+
42
+ @typing.final
43
+ class AuthManager:
44
+ def __init__(self, config_path: pathlib.Path):
45
+ self._auth_path = config_path
46
+ self._auth = self._load()
47
+
48
+ def _load(self) -> Auth:
49
+ if not self._auth_path.exists():
50
+ return Auth()
51
+ return Auth.model_validate_json(self._auth_path.read_bytes())
52
+
53
+ def _save(self) -> None:
54
+ self._auth_path.write_text(self._auth.model_dump_json(indent=2))
55
+
56
+ def save_auth_token(
57
+ self,
58
+ server: str,
59
+ auth_server: str | None = None,
60
+ client_id: str | None = None,
61
+ client_secret: str | None = None,
62
+ token: dict[str, Any] | None = None,
63
+ registration_token: str | None = None,
64
+ ) -> None:
65
+ if auth_server is not None and client_id is not None and token is not None:
66
+ if token["access_token"]:
67
+ usetimestamp = int(time.time()) + int(token["expires_in"])
68
+ token["expires_at"] = usetimestamp
69
+ self._auth.servers[server].authorization_servers[auth_server] = AuthServer(
70
+ client_id=client_id,
71
+ client_secret=client_secret,
72
+ token=AuthToken(**token),
73
+ registration_token=registration_token,
74
+ )
75
+ else:
76
+ self._auth.servers[server] # touch
77
+ self._save()
78
+
79
+ async def exchange_refresh_token(self, auth_server: str, token: AuthToken) -> dict[str, Any] | None:
80
+ """
81
+ This method exchanges a refresh token for a new access token.
82
+ """
83
+ async with httpx.AsyncClient(headers={"Accept": "application/json"}) as client:
84
+ resp = None
85
+ try:
86
+ resp = await client.get(f"{auth_server}/.well-known/openid-configuration")
87
+ resp.raise_for_status()
88
+ oidc = resp.json()
89
+ except Exception as e:
90
+ if resp:
91
+ error_details = resp.json()
92
+ print(f"error: {error_details['error']} error description: {error_details['error_description']}")
93
+ raise RuntimeError(f"OIDC discovery failed: {e}") from e
94
+
95
+ token_endpoint = oidc["token_endpoint"]
96
+ try:
97
+ client_id = (
98
+ self._auth.servers[self._auth.active_server or ""].authorization_servers[auth_server].client_id
99
+ )
100
+ client_secret = (
101
+ self._auth.servers[self._auth.active_server or ""].authorization_servers[auth_server].client_secret
102
+ )
103
+ resp = await client.post(
104
+ f"{token_endpoint}",
105
+ data={
106
+ "grant_type": "refresh_token",
107
+ "refresh_token": token.refresh_token,
108
+ "scope": token.scope,
109
+ "client_id": client_id,
110
+ "client_secret": client_secret,
111
+ },
112
+ )
113
+ resp.raise_for_status()
114
+ new_token = resp.json()
115
+ except Exception as e:
116
+ if resp:
117
+ error_details = resp.json()
118
+ print(f"error: {error_details['error']} error description: {error_details['error_description']}")
119
+ raise RuntimeError(f"Failed to refresh token: {e}") from e
120
+ self.save_auth_token(
121
+ self._auth.active_server or "",
122
+ self._auth.active_auth_server or "",
123
+ self._auth.servers[self._auth.active_server or ""].authorization_servers[auth_server].client_id or "",
124
+ self._auth.servers[self._auth.active_server or ""].authorization_servers[auth_server].client_secret
125
+ or "",
126
+ token=new_token,
127
+ )
128
+ return new_token
129
+
130
+ async def load_auth_token(self) -> str | None:
131
+ active_res = self._auth.active_server
132
+ active_auth_server = self._auth.active_auth_server
133
+ if not active_res or not active_auth_server:
134
+ return None
135
+ server = self._auth.servers.get(active_res)
136
+ if not server:
137
+ return None
138
+
139
+ auth_server = server.authorization_servers.get(active_auth_server)
140
+ if not auth_server or not auth_server.token:
141
+ return None
142
+
143
+ if (auth_server.token.expires_at or 0) - 60 < time.time():
144
+ new_token = await self.exchange_refresh_token(active_auth_server, auth_server.token)
145
+ if new_token:
146
+ return new_token["access_token"]
147
+ return None
148
+
149
+ return auth_server.token.access_token
150
+
151
+ async def deregister_client(self, auth_server, client_id, registration_token) -> None:
152
+ async with httpx.AsyncClient(headers={"Accept": "application/json"}) as client:
153
+ resp = None
154
+ try:
155
+ resp = await client.get(f"{auth_server}/.well-known/openid-configuration")
156
+ resp.raise_for_status()
157
+ oidc = resp.json()
158
+ registration_endpoint = oidc["registration_endpoint"]
159
+ except Exception as e:
160
+ if resp:
161
+ error_details = resp.json()
162
+ print(f"error: {error_details['error']} error description: {error_details['error_description']}")
163
+ raise RuntimeError(f"OIDC discovery failed: {e}") from e
164
+
165
+ try:
166
+ if client_id is not None and client_id != "" and registration_token is not None:
167
+ headers = {"authorization": f"bearer {registration_token}"}
168
+ resp = await client.delete(f"{registration_endpoint}/{client_id}", headers=headers)
169
+ resp.raise_for_status()
170
+
171
+ except Exception as e:
172
+ if resp:
173
+ error_details = resp.json()
174
+ print(f"error: {error_details['error']} error description: {error_details['error_description']}")
175
+ raise RuntimeError(f"Dynamic client de-registration failed. {e}") from e
176
+
177
+ async def clear_auth_token(self, all: bool = False) -> None:
178
+ if all:
179
+ for server in self._auth.servers:
180
+ for auth_server in self._auth.servers[server].authorization_servers:
181
+ await self.deregister_client(
182
+ auth_server,
183
+ self._auth.servers[server].authorization_servers[auth_server].client_id,
184
+ self._auth.servers[server].authorization_servers[auth_server].registration_token,
185
+ )
186
+
187
+ self._auth.servers = defaultdict(Server)
188
+ else:
189
+ if self._auth.active_server and self._auth.active_auth_server:
190
+ if (
191
+ self._auth.servers[self._auth.active_server]
192
+ .authorization_servers[self._auth.active_auth_server]
193
+ .client_id
194
+ ):
195
+ await self.deregister_client(
196
+ self._auth.active_auth_server,
197
+ self._auth.servers[self._auth.active_server]
198
+ .authorization_servers[self._auth.active_auth_server]
199
+ .client_id,
200
+ self._auth.servers[self._auth.active_server]
201
+ .authorization_servers[self._auth.active_auth_server]
202
+ .registration_token,
203
+ )
204
+ del self._auth.servers[self._auth.active_server].authorization_servers[self._auth.active_auth_server]
205
+ if self._auth.active_server and not self._auth.servers[self._auth.active_server].authorization_servers:
206
+ del self._auth.servers[self._auth.active_server]
207
+ self._auth.active_server = None
208
+ self._auth.active_auth_server = None
209
+ self._save()
210
+
211
+ def get_server(self, server: str) -> Server | None:
212
+ return self._auth.servers.get(server)
213
+
214
+ @property
215
+ def servers(self) -> list[str]:
216
+ return list(self._auth.servers.keys())
217
+
218
+ @property
219
+ def active_server(self) -> str | None:
220
+ return self._auth.active_server
221
+
222
+ @active_server.setter
223
+ def active_server(self, server: str | None) -> None:
224
+ if server is not None and server not in self._auth.servers:
225
+ raise ValueError(f"Server {server} not found")
226
+ self._auth.active_server = server
227
+ self._save()
228
+
229
+ @property
230
+ def active_auth_server(self) -> str | None:
231
+ return self._auth.active_auth_server
232
+
233
+ @active_auth_server.setter
234
+ def active_auth_server(self, auth_server: str | None) -> None:
235
+ if auth_server is not None and (
236
+ self._auth.active_server not in self._auth.servers
237
+ or auth_server not in self._auth.servers[self._auth.active_server].authorization_servers
238
+ ):
239
+ raise ValueError(f"Auth server {auth_server} not found in active server")
240
+ self._auth.active_auth_server = auth_server
241
+ self._save()
@@ -535,7 +535,8 @@ async def _run_agent(
535
535
  error = ""
536
536
  if message and message.parts and isinstance(message.parts[0].root, TextPart):
537
537
  error = message.parts[0].root.text
538
- console.print(f"[red]Task {status}[/red]: {error}")
538
+ console.print(f"\n:boom: [red][bold]Task {status.value}[/bold][/red]")
539
+ console.print(Markdown(error))
539
540
  return
540
541
  case Task(id=task_id), TaskStatusUpdateEvent(
541
542
  status=TaskStatus(state=TaskState.auth_required, message=message)
@@ -5,6 +5,7 @@ import asyncio
5
5
  import logging
6
6
  import sys
7
7
  import typing
8
+ import uuid
8
9
  import webbrowser
9
10
  from urllib.parse import urlencode
10
11
 
@@ -68,6 +69,10 @@ async def _wait_for_auth_code(port: int = 9001) -> str:
68
69
  return code
69
70
 
70
71
 
72
+ def get_unique_app_name() -> str:
73
+ return f"Agent Stack CLI {uuid.uuid4()}"
74
+
75
+
71
76
  @app.command("login | change | select | default | switch")
72
77
  async def server_login(server: typing.Annotated[str | None, typer.Argument()] = None):
73
78
  """Login to a server or switch between logged in servers."""
@@ -137,6 +142,7 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
137
142
 
138
143
  client_id = config.client_id
139
144
  client_secret = config.client_secret
145
+ registration_token = None
140
146
 
141
147
  if auth_servers:
142
148
  if len(auth_servers) == 1:
@@ -161,17 +167,35 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
161
167
  registration_endpoint = oidc["registration_endpoint"]
162
168
  if not client_id and registration_endpoint:
163
169
  async with httpx.AsyncClient() as client:
170
+ resp = None
164
171
  try:
172
+ app_name = get_unique_app_name()
165
173
  resp = await client.post(
166
174
  registration_endpoint,
167
- json={"client_name": "Agent Stack CLI", "redirect_uris": [REDIRECT_URI]},
175
+ json={
176
+ "client_name": app_name,
177
+ "grant_types": ["authorization_code", "refresh_token"],
178
+ "enforce_pkce": True,
179
+ "all_users_entitled": True,
180
+ "redirect_uris": [REDIRECT_URI],
181
+ },
168
182
  )
169
183
  resp.raise_for_status()
170
184
  data = resp.json()
171
185
  client_id = data["client_id"]
172
186
  client_secret = data["client_secret"]
173
- except Exception:
174
- console.warning("Dynamic client registration failed. Proceed with manual input.")
187
+ registration_token = data["registration_access_token"]
188
+ except Exception as e:
189
+ if resp:
190
+ try:
191
+ error_details = resp.json()
192
+ console.warning(
193
+ f"error: {error_details['error']} error description: {error_details['error_description']}"
194
+ )
195
+
196
+ except Exception:
197
+ console.info("no parsable json response.")
198
+ console.warning(f" Dynamic client registration failed. Proceed with manual input. {e!s}")
175
199
 
176
200
  if not client_id:
177
201
  client_id = await inquirer.text( # type: ignore
@@ -208,6 +232,7 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
208
232
 
209
233
  code = await _wait_for_auth_code()
210
234
  async with httpx.AsyncClient() as client:
235
+ token_resp = None
211
236
  try:
212
237
  token_resp = await client.post(
213
238
  oidc["token_endpoint"],
@@ -223,6 +248,15 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
223
248
  token_resp.raise_for_status()
224
249
  token = token_resp.json()
225
250
  except Exception as e:
251
+ if resp:
252
+ try:
253
+ error_details = resp.json()
254
+ console.warning(
255
+ f"error: {error_details['error']} error description: {error_details['error_description']}"
256
+ )
257
+ except Exception:
258
+ console.info("no parsable json response.")
259
+
226
260
  raise RuntimeError(f"Token request failed: {e}") from e
227
261
 
228
262
  if not token:
@@ -234,6 +268,7 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
234
268
  client_id=client_id,
235
269
  client_secret=client_secret,
236
270
  token=token,
271
+ registration_token=registration_token,
237
272
  )
238
273
 
239
274
  config.auth_manager.active_server = server
@@ -248,7 +283,7 @@ async def server_logout(
248
283
  typer.Option(),
249
284
  ] = False,
250
285
  ):
251
- config.auth_manager.clear_auth_token(all=all)
286
+ await config.auth_manager.clear_auth_token(all=all)
252
287
  console.success("You have been logged out.")
253
288
 
254
289
 
@@ -65,7 +65,7 @@ class Configuration(pydantic_settings.BaseSettings):
65
65
  sys.exit(1)
66
66
  async with use_platform_client(
67
67
  auth=("admin", self.admin_password.get_secret_value()) if self.admin_password else None,
68
- auth_token=self.auth_manager.load_auth_token(),
68
+ auth_token=await self.auth_manager.load_auth_token(),
69
69
  base_url=self.auth_manager.active_server + "/",
70
70
  ) as client:
71
71
  yield client
@@ -1,126 +0,0 @@
1
- # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
- # SPDX-License-Identifier: Apache-2.0
3
-
4
- import pathlib
5
- import typing
6
- from collections import defaultdict
7
- from typing import Any
8
-
9
- from pydantic import BaseModel, Field
10
-
11
-
12
- class AuthToken(BaseModel):
13
- access_token: str
14
- token_type: str = "Bearer"
15
- expires_in: int | None = None
16
- refresh_token: str | None = None
17
- scope: str | None = None
18
-
19
-
20
- class AuthServer(BaseModel):
21
- client_id: str = "df82a687-d647-4247-838b-7080d7d83f6c" # Backwards compatibility default
22
- client_secret: str | None = None
23
- token: AuthToken | None = None
24
-
25
-
26
- class Server(BaseModel):
27
- authorization_servers: dict[str, AuthServer] = Field(default_factory=dict)
28
-
29
-
30
- class Auth(BaseModel):
31
- version: typing.Literal[1] = 1
32
- servers: defaultdict[str, typing.Annotated[Server, Field(default_factory=Server)]] = Field(
33
- default_factory=lambda: defaultdict(Server)
34
- )
35
- active_server: str | None = None
36
- active_auth_server: str | None = None
37
-
38
-
39
- @typing.final
40
- class AuthManager:
41
- def __init__(self, config_path: pathlib.Path):
42
- self._auth_path = config_path
43
- self._auth = self._load()
44
-
45
- def _load(self) -> Auth:
46
- if not self._auth_path.exists():
47
- return Auth()
48
- return Auth.model_validate_json(self._auth_path.read_bytes())
49
-
50
- def _save(self) -> None:
51
- self._auth_path.write_text(self._auth.model_dump_json(indent=2))
52
-
53
- def save_auth_token(
54
- self,
55
- server: str,
56
- auth_server: str | None = None,
57
- client_id: str | None = None,
58
- client_secret: str | None = None,
59
- token: dict[str, Any] | None = None,
60
- ) -> None:
61
- if auth_server is not None and client_id is not None and token is not None:
62
- self._auth.servers[server].authorization_servers[auth_server] = AuthServer(
63
- client_id=client_id, client_secret=client_secret, token=AuthToken(**token)
64
- )
65
- else:
66
- self._auth.servers[server] # touch
67
- self._save()
68
-
69
- def load_auth_token(self) -> str | None:
70
- active_res = self._auth.active_server
71
- active_auth_server = self._auth.active_auth_server
72
- if not active_res or not active_auth_server:
73
- return None
74
- server = self._auth.servers.get(active_res)
75
- if not server:
76
- return None
77
-
78
- auth_server = server.authorization_servers.get(active_auth_server)
79
- if not auth_server or not auth_server.token:
80
- return None
81
-
82
- return auth_server.token.access_token
83
-
84
- def clear_auth_token(self, all: bool = False) -> None:
85
- if all:
86
- self._auth.servers = defaultdict(Server)
87
- else:
88
- if self._auth.active_server and self._auth.active_auth_server:
89
- del self._auth.servers[self._auth.active_server].authorization_servers[self._auth.active_auth_server]
90
- if self._auth.active_server and not self._auth.servers[self._auth.active_server].authorization_servers:
91
- del self._auth.servers[self._auth.active_server]
92
- self._auth.active_server = None
93
- self._auth.active_auth_server = None
94
- self._save()
95
-
96
- def get_server(self, server: str) -> Server | None:
97
- return self._auth.servers.get(server)
98
-
99
- @property
100
- def servers(self) -> list[str]:
101
- return list(self._auth.servers.keys())
102
-
103
- @property
104
- def active_server(self) -> str | None:
105
- return self._auth.active_server
106
-
107
- @active_server.setter
108
- def active_server(self, server: str | None) -> None:
109
- if server is not None and server not in self._auth.servers:
110
- raise ValueError(f"Server {server} not found")
111
- self._auth.active_server = server
112
- self._save()
113
-
114
- @property
115
- def active_auth_server(self) -> str | None:
116
- return self._auth.active_auth_server
117
-
118
- @active_auth_server.setter
119
- def active_auth_server(self, auth_server: str | None) -> None:
120
- if auth_server is not None and (
121
- self._auth.active_server not in self._auth.servers
122
- or auth_server not in self._auth.servers[self._auth.active_server].authorization_servers
123
- ):
124
- raise ValueError(f"Auth server {auth_server} not found in active server")
125
- self._auth.active_auth_server = auth_server
126
- self._save()