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.
Files changed (156) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. {glaip_sdk-0.6.11.dist-info → glaip_sdk-0.6.14.dist-info}/METADATA +31 -37
  3. glaip_sdk-0.6.14.dist-info/RECORD +12 -0
  4. {glaip_sdk-0.6.11.dist-info → glaip_sdk-0.6.14.dist-info}/WHEEL +2 -1
  5. glaip_sdk-0.6.14.dist-info/entry_points.txt +2 -0
  6. glaip_sdk-0.6.14.dist-info/top_level.txt +1 -0
  7. glaip_sdk/agents/__init__.py +0 -27
  8. glaip_sdk/agents/base.py +0 -1191
  9. glaip_sdk/cli/__init__.py +0 -9
  10. glaip_sdk/cli/account_store.py +0 -540
  11. glaip_sdk/cli/agent_config.py +0 -78
  12. glaip_sdk/cli/auth.py +0 -699
  13. glaip_sdk/cli/commands/__init__.py +0 -5
  14. glaip_sdk/cli/commands/accounts.py +0 -746
  15. glaip_sdk/cli/commands/agents.py +0 -1509
  16. glaip_sdk/cli/commands/common_config.py +0 -101
  17. glaip_sdk/cli/commands/configure.py +0 -896
  18. glaip_sdk/cli/commands/mcps.py +0 -1356
  19. glaip_sdk/cli/commands/models.py +0 -69
  20. glaip_sdk/cli/commands/tools.py +0 -576
  21. glaip_sdk/cli/commands/transcripts.py +0 -755
  22. glaip_sdk/cli/commands/update.py +0 -61
  23. glaip_sdk/cli/config.py +0 -95
  24. glaip_sdk/cli/constants.py +0 -38
  25. glaip_sdk/cli/context.py +0 -150
  26. glaip_sdk/cli/core/__init__.py +0 -79
  27. glaip_sdk/cli/core/context.py +0 -124
  28. glaip_sdk/cli/core/output.py +0 -846
  29. glaip_sdk/cli/core/prompting.py +0 -649
  30. glaip_sdk/cli/core/rendering.py +0 -187
  31. glaip_sdk/cli/display.py +0 -355
  32. glaip_sdk/cli/hints.py +0 -57
  33. glaip_sdk/cli/io.py +0 -112
  34. glaip_sdk/cli/main.py +0 -604
  35. glaip_sdk/cli/masking.py +0 -136
  36. glaip_sdk/cli/mcp_validators.py +0 -287
  37. glaip_sdk/cli/pager.py +0 -266
  38. glaip_sdk/cli/parsers/__init__.py +0 -7
  39. glaip_sdk/cli/parsers/json_input.py +0 -177
  40. glaip_sdk/cli/resolution.py +0 -67
  41. glaip_sdk/cli/rich_helpers.py +0 -27
  42. glaip_sdk/cli/slash/__init__.py +0 -15
  43. glaip_sdk/cli/slash/accounts_controller.py +0 -578
  44. glaip_sdk/cli/slash/accounts_shared.py +0 -75
  45. glaip_sdk/cli/slash/agent_session.py +0 -285
  46. glaip_sdk/cli/slash/prompt.py +0 -256
  47. glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
  48. glaip_sdk/cli/slash/session.py +0 -1708
  49. glaip_sdk/cli/slash/tui/__init__.py +0 -9
  50. glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
  51. glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
  52. glaip_sdk/cli/slash/tui/loading.py +0 -58
  53. glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
  54. glaip_sdk/cli/transcript/__init__.py +0 -31
  55. glaip_sdk/cli/transcript/cache.py +0 -536
  56. glaip_sdk/cli/transcript/capture.py +0 -329
  57. glaip_sdk/cli/transcript/export.py +0 -38
  58. glaip_sdk/cli/transcript/history.py +0 -815
  59. glaip_sdk/cli/transcript/launcher.py +0 -77
  60. glaip_sdk/cli/transcript/viewer.py +0 -374
  61. glaip_sdk/cli/update_notifier.py +0 -290
  62. glaip_sdk/cli/utils.py +0 -263
  63. glaip_sdk/cli/validators.py +0 -238
  64. glaip_sdk/client/__init__.py +0 -11
  65. glaip_sdk/client/_agent_payloads.py +0 -520
  66. glaip_sdk/client/agent_runs.py +0 -147
  67. glaip_sdk/client/agents.py +0 -1335
  68. glaip_sdk/client/base.py +0 -502
  69. glaip_sdk/client/main.py +0 -249
  70. glaip_sdk/client/mcps.py +0 -370
  71. glaip_sdk/client/run_rendering.py +0 -700
  72. glaip_sdk/client/shared.py +0 -21
  73. glaip_sdk/client/tools.py +0 -661
  74. glaip_sdk/client/validators.py +0 -198
  75. glaip_sdk/config/constants.py +0 -52
  76. glaip_sdk/mcps/__init__.py +0 -21
  77. glaip_sdk/mcps/base.py +0 -345
  78. glaip_sdk/models/__init__.py +0 -90
  79. glaip_sdk/models/agent.py +0 -47
  80. glaip_sdk/models/agent_runs.py +0 -116
  81. glaip_sdk/models/common.py +0 -42
  82. glaip_sdk/models/mcp.py +0 -33
  83. glaip_sdk/models/tool.py +0 -33
  84. glaip_sdk/payload_schemas/__init__.py +0 -7
  85. glaip_sdk/payload_schemas/agent.py +0 -85
  86. glaip_sdk/registry/__init__.py +0 -55
  87. glaip_sdk/registry/agent.py +0 -164
  88. glaip_sdk/registry/base.py +0 -139
  89. glaip_sdk/registry/mcp.py +0 -253
  90. glaip_sdk/registry/tool.py +0 -232
  91. glaip_sdk/runner/__init__.py +0 -59
  92. glaip_sdk/runner/base.py +0 -84
  93. glaip_sdk/runner/deps.py +0 -115
  94. glaip_sdk/runner/langgraph.py +0 -782
  95. glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
  99. glaip_sdk/runner/tool_adapter/__init__.py +0 -18
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
  102. glaip_sdk/tools/__init__.py +0 -22
  103. glaip_sdk/tools/base.py +0 -435
  104. glaip_sdk/utils/__init__.py +0 -86
  105. glaip_sdk/utils/a2a/__init__.py +0 -34
  106. glaip_sdk/utils/a2a/event_processor.py +0 -188
  107. glaip_sdk/utils/agent_config.py +0 -194
  108. glaip_sdk/utils/bundler.py +0 -267
  109. glaip_sdk/utils/client.py +0 -111
  110. glaip_sdk/utils/client_utils.py +0 -486
  111. glaip_sdk/utils/datetime_helpers.py +0 -58
  112. glaip_sdk/utils/discovery.py +0 -78
  113. glaip_sdk/utils/display.py +0 -135
  114. glaip_sdk/utils/export.py +0 -143
  115. glaip_sdk/utils/general.py +0 -61
  116. glaip_sdk/utils/import_export.py +0 -168
  117. glaip_sdk/utils/import_resolver.py +0 -492
  118. glaip_sdk/utils/instructions.py +0 -101
  119. glaip_sdk/utils/rendering/__init__.py +0 -115
  120. glaip_sdk/utils/rendering/formatting.py +0 -264
  121. glaip_sdk/utils/rendering/layout/__init__.py +0 -64
  122. glaip_sdk/utils/rendering/layout/panels.py +0 -156
  123. glaip_sdk/utils/rendering/layout/progress.py +0 -202
  124. glaip_sdk/utils/rendering/layout/summary.py +0 -74
  125. glaip_sdk/utils/rendering/layout/transcript.py +0 -606
  126. glaip_sdk/utils/rendering/models.py +0 -85
  127. glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
  128. glaip_sdk/utils/rendering/renderer/base.py +0 -1024
  129. glaip_sdk/utils/rendering/renderer/config.py +0 -27
  130. glaip_sdk/utils/rendering/renderer/console.py +0 -55
  131. glaip_sdk/utils/rendering/renderer/debug.py +0 -178
  132. glaip_sdk/utils/rendering/renderer/factory.py +0 -138
  133. glaip_sdk/utils/rendering/renderer/stream.py +0 -202
  134. glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
  135. glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
  136. glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
  137. glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
  138. glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
  139. glaip_sdk/utils/rendering/state.py +0 -204
  140. glaip_sdk/utils/rendering/step_tree_state.py +0 -100
  141. glaip_sdk/utils/rendering/steps/__init__.py +0 -34
  142. glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
  143. glaip_sdk/utils/rendering/steps/format.py +0 -176
  144. glaip_sdk/utils/rendering/steps/manager.py +0 -387
  145. glaip_sdk/utils/rendering/timing.py +0 -36
  146. glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
  147. glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
  148. glaip_sdk/utils/resource_refs.py +0 -195
  149. glaip_sdk/utils/run_renderer.py +0 -41
  150. glaip_sdk/utils/runtime_config.py +0 -425
  151. glaip_sdk/utils/serialization.py +0 -424
  152. glaip_sdk/utils/sync.py +0 -142
  153. glaip_sdk/utils/tool_detection.py +0 -33
  154. glaip_sdk/utils/validation.py +0 -264
  155. glaip_sdk-0.6.11.dist-info/RECORD +0 -159
  156. glaip_sdk-0.6.11.dist-info/entry_points.txt +0 -3
glaip_sdk/cli/__init__.py DELETED
@@ -1,9 +0,0 @@
1
- """CLI package for AIP SDK.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- from glaip_sdk.cli.main import main
8
-
9
- __all__ = ["main"]
@@ -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
@@ -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
- )