glaip-sdk 0.6.11__py3-none-any.whl → 0.6.14__py3-none-any.whl
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.
- glaip_sdk/__init__.py +42 -5
- {glaip_sdk-0.6.11.dist-info → glaip_sdk-0.6.14.dist-info}/METADATA +31 -37
- glaip_sdk-0.6.14.dist-info/RECORD +12 -0
- {glaip_sdk-0.6.11.dist-info → glaip_sdk-0.6.14.dist-info}/WHEEL +2 -1
- glaip_sdk-0.6.14.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.6.14.dist-info/top_level.txt +1 -0
- glaip_sdk/agents/__init__.py +0 -27
- glaip_sdk/agents/base.py +0 -1191
- glaip_sdk/cli/__init__.py +0 -9
- glaip_sdk/cli/account_store.py +0 -540
- glaip_sdk/cli/agent_config.py +0 -78
- glaip_sdk/cli/auth.py +0 -699
- glaip_sdk/cli/commands/__init__.py +0 -5
- glaip_sdk/cli/commands/accounts.py +0 -746
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/common_config.py +0 -101
- glaip_sdk/cli/commands/configure.py +0 -896
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/models.py +0 -69
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/commands/transcripts.py +0 -755
- glaip_sdk/cli/commands/update.py +0 -61
- glaip_sdk/cli/config.py +0 -95
- glaip_sdk/cli/constants.py +0 -38
- glaip_sdk/cli/context.py +0 -150
- glaip_sdk/cli/core/__init__.py +0 -79
- glaip_sdk/cli/core/context.py +0 -124
- glaip_sdk/cli/core/output.py +0 -846
- glaip_sdk/cli/core/prompting.py +0 -649
- glaip_sdk/cli/core/rendering.py +0 -187
- glaip_sdk/cli/display.py +0 -355
- glaip_sdk/cli/hints.py +0 -57
- glaip_sdk/cli/io.py +0 -112
- glaip_sdk/cli/main.py +0 -604
- glaip_sdk/cli/masking.py +0 -136
- glaip_sdk/cli/mcp_validators.py +0 -287
- glaip_sdk/cli/pager.py +0 -266
- glaip_sdk/cli/parsers/__init__.py +0 -7
- glaip_sdk/cli/parsers/json_input.py +0 -177
- glaip_sdk/cli/resolution.py +0 -67
- glaip_sdk/cli/rich_helpers.py +0 -27
- glaip_sdk/cli/slash/__init__.py +0 -15
- glaip_sdk/cli/slash/accounts_controller.py +0 -578
- glaip_sdk/cli/slash/accounts_shared.py +0 -75
- glaip_sdk/cli/slash/agent_session.py +0 -285
- glaip_sdk/cli/slash/prompt.py +0 -256
- glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
- glaip_sdk/cli/slash/session.py +0 -1708
- glaip_sdk/cli/slash/tui/__init__.py +0 -9
- glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
- glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
- glaip_sdk/cli/slash/tui/loading.py +0 -58
- glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
- glaip_sdk/cli/transcript/__init__.py +0 -31
- glaip_sdk/cli/transcript/cache.py +0 -536
- glaip_sdk/cli/transcript/capture.py +0 -329
- glaip_sdk/cli/transcript/export.py +0 -38
- glaip_sdk/cli/transcript/history.py +0 -815
- glaip_sdk/cli/transcript/launcher.py +0 -77
- glaip_sdk/cli/transcript/viewer.py +0 -374
- glaip_sdk/cli/update_notifier.py +0 -290
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk/cli/validators.py +0 -238
- glaip_sdk/client/__init__.py +0 -11
- glaip_sdk/client/_agent_payloads.py +0 -520
- glaip_sdk/client/agent_runs.py +0 -147
- glaip_sdk/client/agents.py +0 -1335
- glaip_sdk/client/base.py +0 -502
- glaip_sdk/client/main.py +0 -249
- glaip_sdk/client/mcps.py +0 -370
- glaip_sdk/client/run_rendering.py +0 -700
- glaip_sdk/client/shared.py +0 -21
- glaip_sdk/client/tools.py +0 -661
- glaip_sdk/client/validators.py +0 -198
- glaip_sdk/config/constants.py +0 -52
- glaip_sdk/mcps/__init__.py +0 -21
- glaip_sdk/mcps/base.py +0 -345
- glaip_sdk/models/__init__.py +0 -90
- glaip_sdk/models/agent.py +0 -47
- glaip_sdk/models/agent_runs.py +0 -116
- glaip_sdk/models/common.py +0 -42
- glaip_sdk/models/mcp.py +0 -33
- glaip_sdk/models/tool.py +0 -33
- glaip_sdk/payload_schemas/__init__.py +0 -7
- glaip_sdk/payload_schemas/agent.py +0 -85
- glaip_sdk/registry/__init__.py +0 -55
- glaip_sdk/registry/agent.py +0 -164
- glaip_sdk/registry/base.py +0 -139
- glaip_sdk/registry/mcp.py +0 -253
- glaip_sdk/registry/tool.py +0 -232
- glaip_sdk/runner/__init__.py +0 -59
- glaip_sdk/runner/base.py +0 -84
- glaip_sdk/runner/deps.py +0 -115
- glaip_sdk/runner/langgraph.py +0 -782
- glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
- glaip_sdk/runner/tool_adapter/__init__.py +0 -18
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
- glaip_sdk/tools/__init__.py +0 -22
- glaip_sdk/tools/base.py +0 -435
- glaip_sdk/utils/__init__.py +0 -86
- glaip_sdk/utils/a2a/__init__.py +0 -34
- glaip_sdk/utils/a2a/event_processor.py +0 -188
- glaip_sdk/utils/agent_config.py +0 -194
- glaip_sdk/utils/bundler.py +0 -267
- glaip_sdk/utils/client.py +0 -111
- glaip_sdk/utils/client_utils.py +0 -486
- glaip_sdk/utils/datetime_helpers.py +0 -58
- glaip_sdk/utils/discovery.py +0 -78
- glaip_sdk/utils/display.py +0 -135
- glaip_sdk/utils/export.py +0 -143
- glaip_sdk/utils/general.py +0 -61
- glaip_sdk/utils/import_export.py +0 -168
- glaip_sdk/utils/import_resolver.py +0 -492
- glaip_sdk/utils/instructions.py +0 -101
- glaip_sdk/utils/rendering/__init__.py +0 -115
- glaip_sdk/utils/rendering/formatting.py +0 -264
- glaip_sdk/utils/rendering/layout/__init__.py +0 -64
- glaip_sdk/utils/rendering/layout/panels.py +0 -156
- glaip_sdk/utils/rendering/layout/progress.py +0 -202
- glaip_sdk/utils/rendering/layout/summary.py +0 -74
- glaip_sdk/utils/rendering/layout/transcript.py +0 -606
- glaip_sdk/utils/rendering/models.py +0 -85
- glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
- glaip_sdk/utils/rendering/renderer/base.py +0 -1024
- glaip_sdk/utils/rendering/renderer/config.py +0 -27
- glaip_sdk/utils/rendering/renderer/console.py +0 -55
- glaip_sdk/utils/rendering/renderer/debug.py +0 -178
- glaip_sdk/utils/rendering/renderer/factory.py +0 -138
- glaip_sdk/utils/rendering/renderer/stream.py +0 -202
- glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
- glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
- glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
- glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
- glaip_sdk/utils/rendering/state.py +0 -204
- glaip_sdk/utils/rendering/step_tree_state.py +0 -100
- glaip_sdk/utils/rendering/steps/__init__.py +0 -34
- glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
- glaip_sdk/utils/rendering/steps/format.py +0 -176
- glaip_sdk/utils/rendering/steps/manager.py +0 -387
- glaip_sdk/utils/rendering/timing.py +0 -36
- glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
- glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
- glaip_sdk/utils/resource_refs.py +0 -195
- glaip_sdk/utils/run_renderer.py +0 -41
- glaip_sdk/utils/runtime_config.py +0 -425
- glaip_sdk/utils/serialization.py +0 -424
- glaip_sdk/utils/sync.py +0 -142
- glaip_sdk/utils/tool_detection.py +0 -33
- glaip_sdk/utils/validation.py +0 -264
- glaip_sdk-0.6.11.dist-info/RECORD +0 -159
- glaip_sdk-0.6.11.dist-info/entry_points.txt +0 -3
glaip_sdk/cli/__init__.py
DELETED
glaip_sdk/cli/account_store.py
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
"""Account store for managing multiple credential profiles.
|
|
2
|
-
|
|
3
|
-
This module provides the AccountStore class for managing multiple account profiles
|
|
4
|
-
with API URL and API key pairs, supporting migration from legacy single-profile configs.
|
|
5
|
-
|
|
6
|
-
Authors:
|
|
7
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import os
|
|
12
|
-
import re
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
import fcntl # type: ignore
|
|
18
|
-
except ImportError: # pragma: no cover - platform-specific
|
|
19
|
-
fcntl = None # type: ignore[assignment]
|
|
20
|
-
|
|
21
|
-
import yaml
|
|
22
|
-
|
|
23
|
-
from glaip_sdk.cli.config import CONFIG_FILE
|
|
24
|
-
|
|
25
|
-
# POSIX-only locking; Windows falls back to no-op so CLI can still import/run.
|
|
26
|
-
LOCKING_SUPPORTED: bool = fcntl is not None
|
|
27
|
-
|
|
28
|
-
# Account name validation: alphanumeric plus "-" or "_", 1-32 chars
|
|
29
|
-
ACCOUNT_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_-]{1,32}$")
|
|
30
|
-
CONFIG_VERSION = 2
|
|
31
|
-
# Toggle to stop mirroring top-level api_url/api_key after deprecation window
|
|
32
|
-
MIRROR_TOP_LEVEL_CREDS = True
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class AccountStoreError(Exception):
|
|
36
|
-
"""Base exception for account store operations."""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class InvalidAccountNameError(AccountStoreError):
|
|
40
|
-
"""Raised when an account name doesn't match validation rules."""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class AccountNotFoundError(AccountStoreError):
|
|
44
|
-
"""Raised when a requested account doesn't exist."""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class AccountStore:
|
|
48
|
-
"""Manages multiple account profiles in versioned config.yaml.
|
|
49
|
-
|
|
50
|
-
Supports migration from legacy single-profile configs and provides
|
|
51
|
-
thread-safe operations with file locking.
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
def __init__(self, config_file: Path | None = None):
|
|
55
|
-
"""Initialize the account store.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
config_file: Optional path to config file (for testing).
|
|
59
|
-
Defaults to ~/.aip/config.yaml.
|
|
60
|
-
"""
|
|
61
|
-
self.config_file = config_file or CONFIG_FILE
|
|
62
|
-
self.config_dir = self.config_file.parent
|
|
63
|
-
self.lock_file = self.config_file.with_name(f"{self.config_file.name}.lock")
|
|
64
|
-
|
|
65
|
-
def _ensure_config_dir(self) -> None:
|
|
66
|
-
"""Ensure the config directory exists."""
|
|
67
|
-
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
-
|
|
69
|
-
def _acquire_lock(self, file_handle: Any) -> None:
|
|
70
|
-
"""Acquire an exclusive lock on the config file.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
file_handle: File handle to lock.
|
|
74
|
-
|
|
75
|
-
Raises:
|
|
76
|
-
AccountStoreError: If lock cannot be acquired.
|
|
77
|
-
"""
|
|
78
|
-
if not LOCKING_SUPPORTED:
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
try:
|
|
82
|
-
fcntl.flock(file_handle.fileno(), fcntl.LOCK_EX)
|
|
83
|
-
except (OSError, AttributeError) as e:
|
|
84
|
-
raise AccountStoreError(f"Failed to acquire lock on config file: {e}") from e
|
|
85
|
-
|
|
86
|
-
def _release_lock(self, file_handle: Any) -> None:
|
|
87
|
-
"""Release the lock on the config file.
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
file_handle: File handle to unlock.
|
|
91
|
-
"""
|
|
92
|
-
if not LOCKING_SUPPORTED:
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
try:
|
|
96
|
-
fcntl.flock(file_handle.fileno(), fcntl.LOCK_UN)
|
|
97
|
-
except (OSError, AttributeError):
|
|
98
|
-
# Lock release failures are non-fatal
|
|
99
|
-
pass
|
|
100
|
-
|
|
101
|
-
def _load_raw_config(self) -> dict[str, Any]:
|
|
102
|
-
"""Load raw config file without migration or validation."""
|
|
103
|
-
if not self.config_file.exists():
|
|
104
|
-
return {}
|
|
105
|
-
|
|
106
|
-
self._ensure_config_dir()
|
|
107
|
-
lock_handle = None
|
|
108
|
-
lock_acquired = False
|
|
109
|
-
try:
|
|
110
|
-
lock_handle = open(self.lock_file, "a+", encoding="utf-8")
|
|
111
|
-
self._acquire_lock(lock_handle)
|
|
112
|
-
lock_acquired = True
|
|
113
|
-
|
|
114
|
-
with open(self.config_file, encoding="utf-8") as f:
|
|
115
|
-
return yaml.safe_load(f) or {}
|
|
116
|
-
except yaml.YAMLError as e:
|
|
117
|
-
raise AccountStoreError(f"Failed to parse config file: {e}") from e
|
|
118
|
-
finally:
|
|
119
|
-
if lock_handle:
|
|
120
|
-
if lock_acquired:
|
|
121
|
-
self._release_lock(lock_handle)
|
|
122
|
-
lock_handle.close()
|
|
123
|
-
|
|
124
|
-
def _save_config(self, config: dict[str, Any]) -> None:
|
|
125
|
-
"""Atomically save config file with proper permissions.
|
|
126
|
-
|
|
127
|
-
Also mirrors active profile credentials to top-level api_url/api_key
|
|
128
|
-
for backward compatibility with older CLIs during deprecation window.
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
config: Configuration dictionary to save.
|
|
132
|
-
"""
|
|
133
|
-
self._ensure_config_dir()
|
|
134
|
-
|
|
135
|
-
# Mirror active profile to top-level for backward compatibility
|
|
136
|
-
if MIRROR_TOP_LEVEL_CREDS:
|
|
137
|
-
active_account = config.get("active_account")
|
|
138
|
-
accounts = config.get("accounts", {})
|
|
139
|
-
if active_account and active_account in accounts:
|
|
140
|
-
account = accounts[active_account]
|
|
141
|
-
config["api_url"] = account.get("api_url", "")
|
|
142
|
-
config["api_key"] = account.get("api_key", "")
|
|
143
|
-
else:
|
|
144
|
-
# Clear top-level creds if no active account
|
|
145
|
-
config.pop("api_url", None)
|
|
146
|
-
config.pop("api_key", None)
|
|
147
|
-
|
|
148
|
-
# Atomic write: write to temp file, then replace with lock held
|
|
149
|
-
tmp_path = self.config_file.with_name(f"{self.config_file.name}.tmp")
|
|
150
|
-
lock_handle = None
|
|
151
|
-
lock_acquired = False
|
|
152
|
-
try:
|
|
153
|
-
lock_handle = open(self.lock_file, "a+", encoding="utf-8")
|
|
154
|
-
self._acquire_lock(lock_handle)
|
|
155
|
-
lock_acquired = True
|
|
156
|
-
|
|
157
|
-
with open(tmp_path, "w", encoding="utf-8") as f:
|
|
158
|
-
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
159
|
-
tmp_path.replace(self.config_file)
|
|
160
|
-
|
|
161
|
-
# Set secure file permissions
|
|
162
|
-
try:
|
|
163
|
-
os.chmod(self.config_file, 0o600)
|
|
164
|
-
except OSError: # pragma: no cover - permission errors are expected in some environments
|
|
165
|
-
pass
|
|
166
|
-
except Exception as e:
|
|
167
|
-
# Clean up temp file on error
|
|
168
|
-
if tmp_path.exists():
|
|
169
|
-
tmp_path.unlink()
|
|
170
|
-
raise AccountStoreError(f"Failed to save config file: {e}") from e
|
|
171
|
-
finally:
|
|
172
|
-
if lock_handle:
|
|
173
|
-
if lock_acquired:
|
|
174
|
-
self._release_lock(lock_handle)
|
|
175
|
-
lock_handle.close()
|
|
176
|
-
|
|
177
|
-
def _needs_migration(self, config: dict[str, Any]) -> bool:
|
|
178
|
-
"""Check if config needs migration from legacy format.
|
|
179
|
-
|
|
180
|
-
Args:
|
|
181
|
-
config: Raw config dictionary.
|
|
182
|
-
|
|
183
|
-
Returns:
|
|
184
|
-
True if migration is needed.
|
|
185
|
-
"""
|
|
186
|
-
return "version" not in config
|
|
187
|
-
|
|
188
|
-
def _load_auth_json_credentials(self, api_url: str | None, api_key: str | None) -> tuple[str | None, str | None]:
|
|
189
|
-
"""Load credentials from auth.json if missing.
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
api_url: Existing API URL or None.
|
|
193
|
-
api_key: Existing API key or None.
|
|
194
|
-
|
|
195
|
-
Returns:
|
|
196
|
-
Tuple of (api_url, api_key) with values from auth.json if missing.
|
|
197
|
-
"""
|
|
198
|
-
auth_json_path = self.config_dir / "auth.json"
|
|
199
|
-
if (not api_url or not api_key) and auth_json_path.exists():
|
|
200
|
-
try:
|
|
201
|
-
with open(auth_json_path, encoding="utf-8") as f:
|
|
202
|
-
auth_data = json.load(f)
|
|
203
|
-
if not api_url:
|
|
204
|
-
api_url = auth_data.get("api_url") or api_url
|
|
205
|
-
if not api_key:
|
|
206
|
-
api_key = auth_data.get("api_key") or api_key
|
|
207
|
-
except (json.JSONDecodeError, OSError):
|
|
208
|
-
# Ignore errors reading auth.json
|
|
209
|
-
pass
|
|
210
|
-
return api_url, api_key
|
|
211
|
-
|
|
212
|
-
def _create_default_account(self, api_url: str | None, api_key: str | None) -> dict[str, dict[str, str]]:
|
|
213
|
-
"""Create default account from legacy credentials.
|
|
214
|
-
|
|
215
|
-
Args:
|
|
216
|
-
api_url: API URL or None.
|
|
217
|
-
api_key: API key or None.
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
220
|
-
Dictionary with "default" account if both credentials exist and are non-empty, empty dict otherwise.
|
|
221
|
-
"""
|
|
222
|
-
accounts = {}
|
|
223
|
-
# Only create default account if both URL and key are present and non-empty
|
|
224
|
-
if api_url and api_key and api_url.strip() and api_key.strip():
|
|
225
|
-
accounts["default"] = {
|
|
226
|
-
"api_url": api_url.strip(),
|
|
227
|
-
"api_key": api_key.strip(),
|
|
228
|
-
}
|
|
229
|
-
return accounts
|
|
230
|
-
|
|
231
|
-
def _preserve_legacy_keys(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
232
|
-
"""Preserve legacy top-level keys for backward compatibility.
|
|
233
|
-
|
|
234
|
-
Args:
|
|
235
|
-
config: Legacy config dictionary.
|
|
236
|
-
|
|
237
|
-
Returns:
|
|
238
|
-
Dictionary with preserved keys.
|
|
239
|
-
"""
|
|
240
|
-
preserved = {}
|
|
241
|
-
for key in ["timeout", "history_default_limit"]:
|
|
242
|
-
if key in config:
|
|
243
|
-
preserved[key] = config[key]
|
|
244
|
-
return preserved
|
|
245
|
-
|
|
246
|
-
def _migrate_legacy_config(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
247
|
-
"""Migrate legacy config to versioned structure.
|
|
248
|
-
|
|
249
|
-
Args:
|
|
250
|
-
config: Legacy config dictionary.
|
|
251
|
-
|
|
252
|
-
Returns:
|
|
253
|
-
Migrated config dictionary.
|
|
254
|
-
"""
|
|
255
|
-
migrated = {
|
|
256
|
-
"version": CONFIG_VERSION,
|
|
257
|
-
"active_account": "default",
|
|
258
|
-
"accounts": {},
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
# Preserve existing accounts if they exist (shouldn't happen in true migration, but defensive)
|
|
262
|
-
existing_accounts = config.get("accounts", {})
|
|
263
|
-
if existing_accounts:
|
|
264
|
-
migrated["accounts"] = existing_accounts.copy()
|
|
265
|
-
existing_active = config.get("active_account")
|
|
266
|
-
if existing_active and existing_active in existing_accounts:
|
|
267
|
-
migrated["active_account"] = existing_active
|
|
268
|
-
elif "default" in existing_accounts:
|
|
269
|
-
migrated["active_account"] = "default"
|
|
270
|
-
else:
|
|
271
|
-
migrated["active_account"] = sorted(existing_accounts.keys())[0]
|
|
272
|
-
else:
|
|
273
|
-
# Extract legacy api_url and api_key only if no accounts exist
|
|
274
|
-
api_url = config.get("api_url")
|
|
275
|
-
api_key = config.get("api_key")
|
|
276
|
-
|
|
277
|
-
# Check for auth.json from secure login MVP (only during migration)
|
|
278
|
-
api_url, api_key = self._load_auth_json_credentials(api_url, api_key)
|
|
279
|
-
|
|
280
|
-
# Create default account if we have valid credentials
|
|
281
|
-
migrated["accounts"] = self._create_default_account(api_url, api_key)
|
|
282
|
-
# Only set active_account to default if we actually created a default account
|
|
283
|
-
if not migrated["accounts"]:
|
|
284
|
-
migrated.pop("active_account", None)
|
|
285
|
-
|
|
286
|
-
# Preserve other top-level keys for backward compatibility
|
|
287
|
-
migrated.update(self._preserve_legacy_keys(config))
|
|
288
|
-
|
|
289
|
-
return migrated
|
|
290
|
-
|
|
291
|
-
def _ensure_migrated(self) -> None:
|
|
292
|
-
"""Ensure config is migrated to versioned structure.
|
|
293
|
-
|
|
294
|
-
This should be called before any account operations to ensure
|
|
295
|
-
the config file is in the correct format.
|
|
296
|
-
"""
|
|
297
|
-
config = self._load_raw_config()
|
|
298
|
-
|
|
299
|
-
if self._needs_migration(config):
|
|
300
|
-
migrated = self._migrate_legacy_config(config)
|
|
301
|
-
try:
|
|
302
|
-
self._save_config(migrated)
|
|
303
|
-
except AccountStoreError:
|
|
304
|
-
# Gracefully skip migration when persistence is blocked (e.g., mocked I/O in tests)
|
|
305
|
-
return
|
|
306
|
-
|
|
307
|
-
def load_config(self) -> dict[str, Any]:
|
|
308
|
-
"""Load config with automatic migration.
|
|
309
|
-
|
|
310
|
-
Returns:
|
|
311
|
-
Versioned config dictionary.
|
|
312
|
-
"""
|
|
313
|
-
self._ensure_migrated()
|
|
314
|
-
return self._load_raw_config()
|
|
315
|
-
|
|
316
|
-
def validate_account_name(self, name: str) -> None:
|
|
317
|
-
"""Validate an account name.
|
|
318
|
-
|
|
319
|
-
Args:
|
|
320
|
-
name: Account name to validate.
|
|
321
|
-
|
|
322
|
-
Raises:
|
|
323
|
-
InvalidAccountNameError: If name is invalid.
|
|
324
|
-
"""
|
|
325
|
-
if not name:
|
|
326
|
-
raise InvalidAccountNameError("Account name cannot be empty")
|
|
327
|
-
if not ACCOUNT_NAME_PATTERN.match(name):
|
|
328
|
-
raise InvalidAccountNameError(
|
|
329
|
-
f"Invalid account name '{name}'. Must be 1-32 characters, alphanumeric, dash, or underscore."
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
def list_accounts(self) -> dict[str, dict[str, str]]:
|
|
333
|
-
"""List all account profiles.
|
|
334
|
-
|
|
335
|
-
Returns:
|
|
336
|
-
Dictionary mapping account names to their profiles.
|
|
337
|
-
"""
|
|
338
|
-
config = self.load_config()
|
|
339
|
-
return config.get("accounts", {}).copy()
|
|
340
|
-
|
|
341
|
-
def get_account(self, name: str) -> dict[str, str] | None:
|
|
342
|
-
"""Get a specific account profile.
|
|
343
|
-
|
|
344
|
-
Args:
|
|
345
|
-
name: Account name.
|
|
346
|
-
|
|
347
|
-
Returns:
|
|
348
|
-
Account profile dictionary with api_url and api_key, or None if not found.
|
|
349
|
-
"""
|
|
350
|
-
accounts = self.list_accounts()
|
|
351
|
-
return accounts.get(name)
|
|
352
|
-
|
|
353
|
-
def get_active_account(self) -> str | None:
|
|
354
|
-
"""Get the name of the active account.
|
|
355
|
-
|
|
356
|
-
Returns:
|
|
357
|
-
Active account name, or None if not set.
|
|
358
|
-
"""
|
|
359
|
-
config = self.load_config()
|
|
360
|
-
return config.get("active_account")
|
|
361
|
-
|
|
362
|
-
def set_active_account(self, name: str) -> None:
|
|
363
|
-
"""Set the active account.
|
|
364
|
-
|
|
365
|
-
Args:
|
|
366
|
-
name: Account name to activate.
|
|
367
|
-
|
|
368
|
-
Raises:
|
|
369
|
-
AccountNotFoundError: If account doesn't exist.
|
|
370
|
-
"""
|
|
371
|
-
self.validate_account_name(name)
|
|
372
|
-
|
|
373
|
-
config = self.load_config()
|
|
374
|
-
accounts = config.get("accounts", {})
|
|
375
|
-
|
|
376
|
-
if name not in accounts:
|
|
377
|
-
raise AccountNotFoundError(f"Account '{name}' not found")
|
|
378
|
-
|
|
379
|
-
config["active_account"] = name
|
|
380
|
-
self._save_config(config)
|
|
381
|
-
|
|
382
|
-
def add_account(
|
|
383
|
-
self,
|
|
384
|
-
name: str,
|
|
385
|
-
api_url: str,
|
|
386
|
-
api_key: str,
|
|
387
|
-
*,
|
|
388
|
-
overwrite: bool = False,
|
|
389
|
-
) -> None:
|
|
390
|
-
"""Add or update an account profile.
|
|
391
|
-
|
|
392
|
-
Args:
|
|
393
|
-
name: Account name.
|
|
394
|
-
api_url: API URL for this account.
|
|
395
|
-
api_key: API key for this account.
|
|
396
|
-
overwrite: If True, overwrite existing account without prompting.
|
|
397
|
-
|
|
398
|
-
Raises:
|
|
399
|
-
InvalidAccountNameError: If name is invalid.
|
|
400
|
-
AccountStoreError: If account exists and overwrite is False.
|
|
401
|
-
"""
|
|
402
|
-
self.validate_account_name(name)
|
|
403
|
-
|
|
404
|
-
config = self.load_config()
|
|
405
|
-
accounts = config.setdefault("accounts", {})
|
|
406
|
-
|
|
407
|
-
if name in accounts and not overwrite:
|
|
408
|
-
raise AccountStoreError(f"Account '{name}' already exists. Use --yes to overwrite.")
|
|
409
|
-
|
|
410
|
-
accounts[name] = {
|
|
411
|
-
"api_url": api_url,
|
|
412
|
-
"api_key": api_key,
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
# If this is the first account, make it active
|
|
416
|
-
if not config.get("active_account") and len(accounts) == 1:
|
|
417
|
-
config["active_account"] = name
|
|
418
|
-
|
|
419
|
-
self._save_config(config)
|
|
420
|
-
|
|
421
|
-
def remove_account(self, name: str) -> None:
|
|
422
|
-
"""Remove an account profile.
|
|
423
|
-
|
|
424
|
-
Args:
|
|
425
|
-
name: Account name to remove.
|
|
426
|
-
|
|
427
|
-
Raises:
|
|
428
|
-
AccountNotFoundError: If account doesn't exist.
|
|
429
|
-
AccountStoreError: If trying to remove the last account.
|
|
430
|
-
"""
|
|
431
|
-
config = self.load_config()
|
|
432
|
-
accounts = config.get("accounts", {})
|
|
433
|
-
|
|
434
|
-
if name not in accounts:
|
|
435
|
-
raise AccountNotFoundError(f"Account '{name}' not found")
|
|
436
|
-
|
|
437
|
-
if len(accounts) <= 1:
|
|
438
|
-
raise AccountStoreError("Cannot remove the last remaining account")
|
|
439
|
-
|
|
440
|
-
del accounts[name]
|
|
441
|
-
|
|
442
|
-
# If we removed the active account, switch to another account
|
|
443
|
-
active_account = config.get("active_account")
|
|
444
|
-
if active_account == name:
|
|
445
|
-
# Prefer "default" if it exists, otherwise use first alphabetical account
|
|
446
|
-
if "default" in accounts:
|
|
447
|
-
config["active_account"] = "default"
|
|
448
|
-
elif accounts:
|
|
449
|
-
# Sort accounts alphabetically and pick the first one
|
|
450
|
-
sorted_names = sorted(accounts.keys())
|
|
451
|
-
config["active_account"] = sorted_names[0]
|
|
452
|
-
else:
|
|
453
|
-
# No accounts remaining (shouldn't happen due to check above)
|
|
454
|
-
config.pop("active_account", None)
|
|
455
|
-
|
|
456
|
-
self._save_config(config)
|
|
457
|
-
|
|
458
|
-
def get_credentials(
|
|
459
|
-
self,
|
|
460
|
-
account_name: str | None = None,
|
|
461
|
-
) -> tuple[str | None, str | None]:
|
|
462
|
-
"""Get credentials for an account.
|
|
463
|
-
|
|
464
|
-
Args:
|
|
465
|
-
account_name: Account name, or None to use active account.
|
|
466
|
-
|
|
467
|
-
Returns:
|
|
468
|
-
Tuple of (api_url, api_key), or (None, None) if not found.
|
|
469
|
-
"""
|
|
470
|
-
config = self.load_config()
|
|
471
|
-
|
|
472
|
-
# Determine which account to use
|
|
473
|
-
if account_name:
|
|
474
|
-
target_account = account_name
|
|
475
|
-
else:
|
|
476
|
-
target_account = config.get("active_account")
|
|
477
|
-
|
|
478
|
-
if not target_account:
|
|
479
|
-
return None, None
|
|
480
|
-
|
|
481
|
-
accounts = config.get("accounts", {})
|
|
482
|
-
account = accounts.get(target_account)
|
|
483
|
-
|
|
484
|
-
if not account:
|
|
485
|
-
return None, None
|
|
486
|
-
|
|
487
|
-
return account.get("api_url"), account.get("api_key")
|
|
488
|
-
|
|
489
|
-
def rename_account(self, current_name: str, new_name: str, *, overwrite: bool = False) -> None:
|
|
490
|
-
"""Rename an existing account profile.
|
|
491
|
-
|
|
492
|
-
Args:
|
|
493
|
-
current_name: The existing account name.
|
|
494
|
-
new_name: The desired new account name.
|
|
495
|
-
overwrite: Whether to overwrite an existing target account.
|
|
496
|
-
|
|
497
|
-
Raises:
|
|
498
|
-
InvalidAccountNameError: If either name is invalid.
|
|
499
|
-
AccountNotFoundError: If the source account does not exist.
|
|
500
|
-
AccountStoreError: If the target exists and overwrite is False.
|
|
501
|
-
"""
|
|
502
|
-
self.validate_account_name(current_name)
|
|
503
|
-
self.validate_account_name(new_name)
|
|
504
|
-
|
|
505
|
-
if current_name == new_name:
|
|
506
|
-
# No-op rename; keep behavior predictable without mutating config
|
|
507
|
-
return
|
|
508
|
-
|
|
509
|
-
config = self.load_config()
|
|
510
|
-
accounts = config.get("accounts", {})
|
|
511
|
-
|
|
512
|
-
if current_name not in accounts:
|
|
513
|
-
raise AccountNotFoundError(f"Account '{current_name}' not found")
|
|
514
|
-
|
|
515
|
-
if new_name in accounts and not overwrite:
|
|
516
|
-
raise AccountStoreError(f"Account '{new_name}' already exists. Use --yes to overwrite.")
|
|
517
|
-
|
|
518
|
-
accounts[new_name] = accounts[current_name]
|
|
519
|
-
del accounts[current_name]
|
|
520
|
-
|
|
521
|
-
if config.get("active_account") == current_name:
|
|
522
|
-
config["active_account"] = new_name
|
|
523
|
-
|
|
524
|
-
self._save_config(config)
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
# Global instance for convenience
|
|
528
|
-
_account_store = AccountStore()
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
def get_account_store() -> AccountStore:
|
|
532
|
-
"""Get the global account store instance."""
|
|
533
|
-
from glaip_sdk.cli import config as config_module # noqa: PLC0415
|
|
534
|
-
|
|
535
|
-
global _account_store
|
|
536
|
-
|
|
537
|
-
if _account_store is None or _account_store.config_file != config_module.CONFIG_FILE:
|
|
538
|
-
_account_store = AccountStore(config_module.CONFIG_FILE)
|
|
539
|
-
|
|
540
|
-
return _account_store
|
glaip_sdk/cli/agent_config.py
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"""CLI-specific agent configuration utilities.
|
|
2
|
-
|
|
3
|
-
This module provides CLI-only affordances for agent configuration,
|
|
4
|
-
such as merging CLI flags over imported data.
|
|
5
|
-
|
|
6
|
-
Authors:
|
|
7
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from glaip_sdk.utils.agent_config import (
|
|
13
|
-
resolve_language_model_selection,
|
|
14
|
-
sanitize_agent_config,
|
|
15
|
-
)
|
|
16
|
-
from glaip_sdk.utils.import_export import merge_import_with_cli_args
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def merge_agent_config_with_cli_args(import_data: dict[str, Any], cli_args: dict[str, Any]) -> dict[str, Any]:
|
|
20
|
-
"""Merge imported agent data with CLI arguments, preferring CLI args.
|
|
21
|
-
|
|
22
|
-
This is a CLI-specific wrapper that handles agent-specific merging logic.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
import_data: Data loaded from import file
|
|
26
|
-
cli_args: Arguments passed via CLI
|
|
27
|
-
|
|
28
|
-
Returns:
|
|
29
|
-
Merged data dictionary
|
|
30
|
-
|
|
31
|
-
Notes:
|
|
32
|
-
- CLI arguments take precedence over imported data
|
|
33
|
-
- Handles agent-specific fields like tools and agents arrays
|
|
34
|
-
- Preserves agent configuration structure
|
|
35
|
-
"""
|
|
36
|
-
return merge_import_with_cli_args(import_data, cli_args, array_fields=["tools", "agents"])
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def resolve_agent_language_model_selection(
|
|
40
|
-
merged_data: dict[str, Any], cli_model: str | None
|
|
41
|
-
) -> tuple[dict[str, Any], bool]:
|
|
42
|
-
"""Resolve language model selection for agent creation/update.
|
|
43
|
-
|
|
44
|
-
This is a CLI-specific wrapper around the core LM selection logic.
|
|
45
|
-
|
|
46
|
-
Args:
|
|
47
|
-
merged_data: Merged import data and CLI args
|
|
48
|
-
cli_model: Model specified via CLI --model flag
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
Tuple of (lm_selection_dict, should_strip_lm_identity)
|
|
52
|
-
"""
|
|
53
|
-
return resolve_language_model_selection(merged_data, cli_model)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def sanitize_agent_config_for_cli(
|
|
57
|
-
agent_config: dict | None,
|
|
58
|
-
*,
|
|
59
|
-
strip_credentials: bool = True,
|
|
60
|
-
strip_lm_identity: bool = False,
|
|
61
|
-
) -> dict:
|
|
62
|
-
"""Sanitize agent_config for CLI operations.
|
|
63
|
-
|
|
64
|
-
This is a CLI-specific wrapper around the core sanitization logic.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
agent_config: The agent configuration to sanitize
|
|
68
|
-
strip_credentials: Always drop lm_credentials (default: True)
|
|
69
|
-
strip_lm_identity: Also drop lm_provider/lm_name/lm_base_url when True
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
Sanitized agent configuration
|
|
73
|
-
"""
|
|
74
|
-
return sanitize_agent_config(
|
|
75
|
-
agent_config,
|
|
76
|
-
strip_credentials=strip_credentials,
|
|
77
|
-
strip_lm_identity=strip_lm_identity,
|
|
78
|
-
)
|