glaip-sdk 0.1.0__py3-none-any.whl → 0.6.10__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 +5 -2
- glaip_sdk/_version.py +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1191 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +265 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +251 -173
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +735 -143
- glaip_sdk/cli/commands/mcps.py +266 -134
- glaip_sdk/cli/commands/models.py +13 -9
- glaip_sdk/cli/commands/tools.py +67 -88
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/commands/update.py +3 -8
- glaip_sdk/cli/config.py +49 -7
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +846 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +45 -32
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +232 -143
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +12 -19
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +3 -9
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +578 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +807 -225
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -499
- glaip_sdk/cli/update_notifier.py +177 -24
- glaip_sdk/cli/utils.py +242 -1308
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +53 -37
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +320 -92
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/main.py +19 -10
- glaip_sdk/client/mcps.py +123 -15
- glaip_sdk/client/run_rendering.py +136 -101
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +163 -34
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +232 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +706 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +58 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +7 -35
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +3 -6
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -49
- glaip_sdk/utils/rendering/renderer/base.py +258 -1577
- glaip_sdk/utils/rendering/renderer/config.py +1 -5
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +10 -51
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/step_tree_state.py +1 -3
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +76 -517
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +29 -26
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +20 -28
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
- glaip_sdk-0.6.10.dist-info/RECORD +159 -0
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.1.0.dist-info/RECORD +0 -82
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/auth.py
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
"""Authentication export helpers for MCP CLI commands.
|
|
1
|
+
"""Authentication export helpers for MCP CLI commands and credential resolution.
|
|
2
2
|
|
|
3
3
|
This module provides utilities for preparing authentication data for export,
|
|
4
4
|
including interactive secret capture and placeholder generation.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
environment variables.
|
|
6
|
+
It also provides credential resolution for the AIP CLI, supporting multiple
|
|
7
|
+
account profiles and environment variable overrides.
|
|
9
8
|
|
|
10
9
|
Authors:
|
|
11
10
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
12
11
|
"""
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
import os
|
|
14
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
17
|
import click
|
|
18
18
|
from rich.console import Console
|
|
19
19
|
|
|
20
20
|
from glaip_sdk.branding import HINT_PREFIX_STYLE, WARNING_STYLE
|
|
21
|
-
from glaip_sdk.cli.
|
|
21
|
+
from glaip_sdk.cli.account_store import AccountNotFoundError, AccountStoreError, get_account_store
|
|
22
|
+
from glaip_sdk.cli.hints import format_command_hint
|
|
23
|
+
from glaip_sdk.cli.utils import command_hint
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
def prepare_authentication_export(
|
|
@@ -65,9 +67,7 @@ def prepare_authentication_export(
|
|
|
65
67
|
|
|
66
68
|
# Handle bearer-token authentication
|
|
67
69
|
if auth_type == "bearer-token":
|
|
68
|
-
return _prepare_bearer_token_auth(
|
|
69
|
-
auth, prompt_for_secrets, placeholder, console
|
|
70
|
-
)
|
|
70
|
+
return _prepare_bearer_token_auth(auth, prompt_for_secrets, placeholder, console)
|
|
71
71
|
|
|
72
72
|
# Handle api-key authentication
|
|
73
73
|
if auth_type == "api-key":
|
|
@@ -75,9 +75,7 @@ def prepare_authentication_export(
|
|
|
75
75
|
|
|
76
76
|
# Handle custom-header authentication
|
|
77
77
|
if auth_type == "custom-header":
|
|
78
|
-
return _prepare_custom_header_auth(
|
|
79
|
-
auth, prompt_for_secrets, placeholder, console
|
|
80
|
-
)
|
|
78
|
+
return _prepare_custom_header_auth(auth, prompt_for_secrets, placeholder, console)
|
|
81
79
|
|
|
82
80
|
# Unknown auth type - return as-is but strip helper metadata
|
|
83
81
|
result = auth.copy()
|
|
@@ -85,9 +83,7 @@ def prepare_authentication_export(
|
|
|
85
83
|
return result
|
|
86
84
|
|
|
87
85
|
|
|
88
|
-
def _get_token_value(
|
|
89
|
-
prompt_for_secrets: bool, placeholder: str, console: Console
|
|
90
|
-
) -> str:
|
|
86
|
+
def _get_token_value(prompt_for_secrets: bool, placeholder: str, console: Console) -> str:
|
|
91
87
|
"""Get bearer token value either by prompting or using a placeholder.
|
|
92
88
|
|
|
93
89
|
Args:
|
|
@@ -109,13 +105,28 @@ def _get_token_value(
|
|
|
109
105
|
)
|
|
110
106
|
|
|
111
107
|
if not click.get_text_stream("stdin").isatty():
|
|
112
|
-
console.print(
|
|
113
|
-
f"[{WARNING_STYLE}]⚠️ Non-interactive mode: "
|
|
114
|
-
"using placeholder for bearer token[/]"
|
|
115
|
-
)
|
|
108
|
+
console.print(f"[{WARNING_STYLE}]⚠️ Non-interactive mode: using placeholder for bearer token[/]")
|
|
116
109
|
return placeholder
|
|
117
110
|
|
|
118
111
|
|
|
112
|
+
def _normalize_header_keys(
|
|
113
|
+
header_keys: Iterable[str] | str | None,
|
|
114
|
+
*,
|
|
115
|
+
default: Iterable[str] | None = None,
|
|
116
|
+
) -> list[str]:
|
|
117
|
+
"""Normalize header_keys to a list, handling strings and None safely."""
|
|
118
|
+
if header_keys is None:
|
|
119
|
+
return list(default or [])
|
|
120
|
+
if isinstance(header_keys, str):
|
|
121
|
+
return [header_keys] if header_keys else list(default or [])
|
|
122
|
+
try:
|
|
123
|
+
return list(header_keys)
|
|
124
|
+
except TypeError:
|
|
125
|
+
raise click.ClickException(
|
|
126
|
+
f"Invalid header_keys type: expected string or iterable, got {type(header_keys).__name__}"
|
|
127
|
+
) from None
|
|
128
|
+
|
|
129
|
+
|
|
119
130
|
def _build_bearer_headers(auth: dict[str, Any], token_value: str) -> dict[str, str]:
|
|
120
131
|
"""Build headers for bearer token authentication.
|
|
121
132
|
|
|
@@ -127,7 +138,10 @@ def _build_bearer_headers(auth: dict[str, Any], token_value: str) -> dict[str, s
|
|
|
127
138
|
A dictionary of HTTP headers including the Authorization header when
|
|
128
139
|
applicable.
|
|
129
140
|
"""
|
|
130
|
-
|
|
141
|
+
default_header_keys = ["Authorization"]
|
|
142
|
+
has_header_keys = "header_keys" in auth
|
|
143
|
+
header_keys_raw = auth.get("header_keys") if has_header_keys else default_header_keys
|
|
144
|
+
header_keys = _normalize_header_keys(header_keys_raw, default=None if has_header_keys else default_header_keys)
|
|
131
145
|
headers = {}
|
|
132
146
|
for key in header_keys:
|
|
133
147
|
# Prepend "Bearer " if this is Authorization header
|
|
@@ -186,8 +200,8 @@ def _extract_api_key_name(auth: dict[str, Any]) -> str | None:
|
|
|
186
200
|
"""
|
|
187
201
|
key_name = auth.get("key")
|
|
188
202
|
if not key_name and "header_keys" in auth:
|
|
189
|
-
header_keys = auth["header_keys"]
|
|
190
|
-
if
|
|
203
|
+
header_keys = _normalize_header_keys(auth["header_keys"])
|
|
204
|
+
if header_keys:
|
|
191
205
|
key_name = header_keys[0]
|
|
192
206
|
return key_name
|
|
193
207
|
|
|
@@ -220,16 +234,11 @@ def _get_api_key_value(
|
|
|
220
234
|
)
|
|
221
235
|
|
|
222
236
|
if not click.get_text_stream("stdin").isatty():
|
|
223
|
-
console.print(
|
|
224
|
-
f"[{WARNING_STYLE}]⚠️ Non-interactive mode: "
|
|
225
|
-
f"using placeholder for API key '{key_name}'[/]"
|
|
226
|
-
)
|
|
237
|
+
console.print(f"[{WARNING_STYLE}]⚠️ Non-interactive mode: using placeholder for API key '{key_name}'[/]")
|
|
227
238
|
return placeholder
|
|
228
239
|
|
|
229
240
|
|
|
230
|
-
def _build_api_key_headers(
|
|
231
|
-
auth: dict[str, Any], key_name: str | None, key_value: str
|
|
232
|
-
) -> dict[str, str]:
|
|
241
|
+
def _build_api_key_headers(auth: dict[str, Any], key_name: str | None, key_value: str) -> dict[str, str]:
|
|
233
242
|
"""Build headers for API key authentication.
|
|
234
243
|
|
|
235
244
|
Args:
|
|
@@ -240,8 +249,12 @@ def _build_api_key_headers(
|
|
|
240
249
|
Returns:
|
|
241
250
|
A dictionary of HTTP headers for API key authentication.
|
|
242
251
|
"""
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
default_header_keys = [key_name] if key_name else []
|
|
253
|
+
has_header_keys = "header_keys" in auth
|
|
254
|
+
header_keys_raw = auth.get("header_keys") if has_header_keys else default_header_keys
|
|
255
|
+
header_keys_list = _normalize_header_keys(header_keys_raw, default=None if has_header_keys else default_header_keys)
|
|
256
|
+
filtered_keys = [k for k in header_keys_list if k]
|
|
257
|
+
return dict.fromkeys(filtered_keys, key_value)
|
|
245
258
|
|
|
246
259
|
|
|
247
260
|
def _prepare_api_key_auth(
|
|
@@ -270,9 +283,7 @@ def _prepare_api_key_auth(
|
|
|
270
283
|
|
|
271
284
|
# Capture or use placeholder for value
|
|
272
285
|
if not has_valid_value:
|
|
273
|
-
key_value = _get_api_key_value(
|
|
274
|
-
key_name, prompt_for_secrets, placeholder, console
|
|
275
|
-
)
|
|
286
|
+
key_value = _get_api_key_value(key_name, prompt_for_secrets, placeholder, console)
|
|
276
287
|
|
|
277
288
|
# Check if original had headers structure
|
|
278
289
|
if "headers" in auth or "header_keys" in auth:
|
|
@@ -317,9 +328,7 @@ def _prepare_custom_header_auth(
|
|
|
317
328
|
return {"type": "custom-header", "headers": headers}
|
|
318
329
|
|
|
319
330
|
|
|
320
|
-
def _extract_header_names(
|
|
321
|
-
existing_headers: Mapping[str, Any] | None, header_keys: Iterable[str] | None
|
|
322
|
-
) -> list[str]:
|
|
331
|
+
def _extract_header_names(existing_headers: Mapping[str, Any] | None, header_keys: Iterable[str] | None) -> list[str]:
|
|
323
332
|
"""Extract the list of header names to process.
|
|
324
333
|
|
|
325
334
|
Args:
|
|
@@ -331,9 +340,7 @@ def _extract_header_names(
|
|
|
331
340
|
"""
|
|
332
341
|
if existing_headers:
|
|
333
342
|
return list(existing_headers.keys())
|
|
334
|
-
|
|
335
|
-
return list(header_keys)
|
|
336
|
-
return []
|
|
343
|
+
return _normalize_header_keys(header_keys)
|
|
337
344
|
|
|
338
345
|
|
|
339
346
|
def _is_valid_secret(value: Any) -> bool:
|
|
@@ -376,9 +383,7 @@ def _prompt_or_placeholder(
|
|
|
376
383
|
)
|
|
377
384
|
|
|
378
385
|
if not click.get_text_stream("stdin").isatty():
|
|
379
|
-
console.print(
|
|
380
|
-
f"[{WARNING_STYLE}]⚠️ Non-interactive mode: using placeholder for header '{name}'[/]"
|
|
381
|
-
)
|
|
386
|
+
console.print(f"[{WARNING_STYLE}]⚠️ Non-interactive mode: using placeholder for header '{name}'[/]")
|
|
382
387
|
return placeholder
|
|
383
388
|
|
|
384
389
|
|
|
@@ -450,11 +455,12 @@ def _prompt_secret_with_placeholder(
|
|
|
450
455
|
tip = command_hint(tip_cli_command, tip_slash_command)
|
|
451
456
|
if tip:
|
|
452
457
|
console.print(
|
|
453
|
-
f"[{HINT_PREFIX_STYLE}]Tip:[/] use {format_command_hint(tip) or tip} later
|
|
458
|
+
f"[{HINT_PREFIX_STYLE}]Tip:[/] use {format_command_hint(tip) or tip} later "
|
|
459
|
+
"if you want to update these credentials."
|
|
454
460
|
)
|
|
455
461
|
|
|
456
462
|
attempts = 0
|
|
457
|
-
while attempts <= retry_limit:
|
|
463
|
+
while attempts <= retry_limit: # pragma: no cover
|
|
458
464
|
response = click.prompt(
|
|
459
465
|
prompt_message,
|
|
460
466
|
default="",
|
|
@@ -477,3 +483,217 @@ def _prompt_secret_with_placeholder(
|
|
|
477
483
|
|
|
478
484
|
# This line is unreachable as the loop always returns
|
|
479
485
|
# return placeholder
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ----------------------------- Credential Resolution ----------------------------- #
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def resolve_api_url_from_context(
|
|
492
|
+
ctx: Any,
|
|
493
|
+
*,
|
|
494
|
+
get_api_url: Callable[[Any], str | None] | None = None,
|
|
495
|
+
get_account_name: Callable[[Any], str | None] | None = None,
|
|
496
|
+
) -> str | None:
|
|
497
|
+
"""Resolve API URL from context using account store (CLI/palette ignores env creds).
|
|
498
|
+
|
|
499
|
+
Helper function to extract API URL from various context formats.
|
|
500
|
+
Used by transcript capture and slash session to avoid code duplication.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
ctx: Context object (can be dict, click.Context, or any object with attributes).
|
|
504
|
+
get_api_url: Optional function to extract api_url from context.
|
|
505
|
+
If None, tries ctx.obj.get("api_url") or ctx.get("api_url").
|
|
506
|
+
get_account_name: Optional function to extract account_name from context.
|
|
507
|
+
If None, tries ctx.obj.get("account_name") or ctx.get("account_name").
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
Resolved API URL or None.
|
|
511
|
+
"""
|
|
512
|
+
api_url = None
|
|
513
|
+
account_name = None
|
|
514
|
+
|
|
515
|
+
if get_api_url:
|
|
516
|
+
api_url = get_api_url(ctx)
|
|
517
|
+
elif isinstance(ctx, dict):
|
|
518
|
+
api_url = ctx.get("api_url")
|
|
519
|
+
elif hasattr(ctx, "obj") and isinstance(ctx.obj, dict):
|
|
520
|
+
api_url = ctx.obj.get("api_url")
|
|
521
|
+
|
|
522
|
+
if get_account_name:
|
|
523
|
+
account_name = get_account_name(ctx)
|
|
524
|
+
elif isinstance(ctx, dict):
|
|
525
|
+
account_name = ctx.get("account_name")
|
|
526
|
+
elif hasattr(ctx, "obj") and isinstance(ctx.obj, dict):
|
|
527
|
+
account_name = ctx.obj.get("account_name")
|
|
528
|
+
|
|
529
|
+
resolved_url, _, _ = resolve_credentials(
|
|
530
|
+
account_name=account_name,
|
|
531
|
+
api_url=api_url,
|
|
532
|
+
api_key=None,
|
|
533
|
+
ignore_env_creds=True,
|
|
534
|
+
)
|
|
535
|
+
return resolved_url
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _resolve_account_name(account_name: str | None) -> str | None:
|
|
539
|
+
"""Resolve account name from parameter (env var removed for CLI/palette)."""
|
|
540
|
+
return account_name
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _validate_account_exists(account_name: str | None, store: Any) -> None:
|
|
544
|
+
"""Validate that the specified account exists.
|
|
545
|
+
|
|
546
|
+
Raises:
|
|
547
|
+
AccountNotFoundError: If account_name is specified but account doesn't exist.
|
|
548
|
+
"""
|
|
549
|
+
if account_name:
|
|
550
|
+
account = store.get_account(account_name)
|
|
551
|
+
if not account:
|
|
552
|
+
raise AccountNotFoundError(
|
|
553
|
+
f"Account '{account_name}' not found. Run 'aip accounts list' to see available accounts."
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _merge_credentials(
|
|
558
|
+
api_url: str | None,
|
|
559
|
+
api_key: str | None,
|
|
560
|
+
profile_url: str | None,
|
|
561
|
+
profile_key: str | None,
|
|
562
|
+
ignore_env_creds: bool,
|
|
563
|
+
) -> tuple[str | None, str | None]:
|
|
564
|
+
"""Merge credentials from multiple sources.
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
api_url: Explicit API URL override.
|
|
568
|
+
api_key: Explicit API key override.
|
|
569
|
+
profile_url: Profile API URL.
|
|
570
|
+
profile_key: Profile API key.
|
|
571
|
+
ignore_env_creds: If True, ignore env vars.
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Tuple of (final_url, final_key).
|
|
575
|
+
"""
|
|
576
|
+
if not ignore_env_creds:
|
|
577
|
+
env_url = os.getenv("AIP_API_URL")
|
|
578
|
+
env_key = os.getenv("AIP_API_KEY")
|
|
579
|
+
final_url = api_url or env_url or profile_url
|
|
580
|
+
final_key = api_key or env_key or profile_key
|
|
581
|
+
else:
|
|
582
|
+
final_url = api_url or profile_url
|
|
583
|
+
final_key = api_key or profile_key
|
|
584
|
+
return final_url, final_key
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _determine_source(
|
|
588
|
+
api_url: str | None,
|
|
589
|
+
api_key: str | None,
|
|
590
|
+
account_name: str | None,
|
|
591
|
+
store: Any,
|
|
592
|
+
) -> str:
|
|
593
|
+
"""Determine the source of credentials.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
Source string describing where credentials came from.
|
|
597
|
+
"""
|
|
598
|
+
if api_url or api_key:
|
|
599
|
+
return "flag"
|
|
600
|
+
if account_name:
|
|
601
|
+
return f"account:{account_name}"
|
|
602
|
+
active = store.get_active_account()
|
|
603
|
+
return f"active_profile:{active}" if active else "none"
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
_ENV_WARNING_EMITTED = False
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _maybe_warn_env_creds_ignored(ignore_env_creds: bool) -> None:
|
|
610
|
+
"""Emit a one-time warning when env credentials are present but ignored."""
|
|
611
|
+
global _ENV_WARNING_EMITTED
|
|
612
|
+
|
|
613
|
+
if _ENV_WARNING_EMITTED or not ignore_env_creds:
|
|
614
|
+
return
|
|
615
|
+
|
|
616
|
+
if os.getenv("AIP_API_URL") or os.getenv("AIP_API_KEY"):
|
|
617
|
+
click.echo(
|
|
618
|
+
"Warning: CLI ignores AIP_API_URL/AIP_API_KEY; use account profiles via 'aip accounts add/use'. "
|
|
619
|
+
"Python SDK callers can opt in with ignore_env_creds=False.",
|
|
620
|
+
err=True,
|
|
621
|
+
)
|
|
622
|
+
_ENV_WARNING_EMITTED = True
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def resolve_credentials(
|
|
626
|
+
account_name: str | None = None,
|
|
627
|
+
api_url: str | None = None,
|
|
628
|
+
api_key: str | None = None,
|
|
629
|
+
*,
|
|
630
|
+
ignore_env_creds: bool = True,
|
|
631
|
+
) -> tuple[str | None, str | None, str]:
|
|
632
|
+
"""Resolve credentials from multiple sources with precedence.
|
|
633
|
+
|
|
634
|
+
For CLI/palette: ignores raw credential env vars (AIP_API_URL/AIP_API_KEY),
|
|
635
|
+
and only uses explicit account selection (no AIP_ACCOUNT env). Python SDK can use
|
|
636
|
+
ignore_env_creds=False to honor env vars if needed.
|
|
637
|
+
|
|
638
|
+
Precedence order (CLI/palette):
|
|
639
|
+
1. Explicit parameters (api_url, api_key)
|
|
640
|
+
2. Account profile (from account_name or active_account)
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
account_name: Account name to use, or None for active account.
|
|
644
|
+
api_url: Explicit API URL override.
|
|
645
|
+
api_key: Explicit API key override.
|
|
646
|
+
ignore_env_creds: If True (default), ignore AIP_API_URL/AIP_API_KEY env vars.
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
Tuple of (api_url, api_key, source) where source describes where
|
|
650
|
+
credentials came from (e.g., "flag", "active_profile", "account:name").
|
|
651
|
+
|
|
652
|
+
Raises:
|
|
653
|
+
click.ClickException: If a requested account does not exist.
|
|
654
|
+
"""
|
|
655
|
+
_maybe_warn_env_creds_ignored(ignore_env_creds)
|
|
656
|
+
|
|
657
|
+
# 1. Explicit parameters take highest precedence
|
|
658
|
+
if api_url and api_key:
|
|
659
|
+
return api_url, api_key, "flag"
|
|
660
|
+
|
|
661
|
+
# 2. Account profile resolution
|
|
662
|
+
account_name = _resolve_account_name(account_name)
|
|
663
|
+
store = get_account_store()
|
|
664
|
+
try:
|
|
665
|
+
_validate_account_exists(account_name, store)
|
|
666
|
+
except AccountNotFoundError as exc:
|
|
667
|
+
raise click.ClickException(str(exc)) from exc
|
|
668
|
+
|
|
669
|
+
try:
|
|
670
|
+
profile_url, profile_key = store.get_credentials(account_name)
|
|
671
|
+
except AccountStoreError:
|
|
672
|
+
profile_url, profile_key = None, None
|
|
673
|
+
|
|
674
|
+
final_url, final_key = _merge_credentials(api_url, api_key, profile_url, profile_key, ignore_env_creds)
|
|
675
|
+
source = _determine_source(api_url, api_key, account_name, store)
|
|
676
|
+
|
|
677
|
+
return final_url, final_key, source
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def get_credentials(
|
|
681
|
+
account_name: str | None = None,
|
|
682
|
+
api_url: str | None = None,
|
|
683
|
+
api_key: str | None = None,
|
|
684
|
+
) -> tuple[str | None, str | None]:
|
|
685
|
+
"""Get credentials for CLI commands (backward compatible wrapper).
|
|
686
|
+
|
|
687
|
+
This function maintains backward compatibility with existing code that
|
|
688
|
+
expects (url, key) tuple. For source information, use resolve_credentials.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
account_name: Account name to use, or None for active account.
|
|
692
|
+
api_url: Explicit API URL override.
|
|
693
|
+
api_key: Explicit API key override.
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
Tuple of (api_url, api_key).
|
|
697
|
+
"""
|
|
698
|
+
url, key, _ = resolve_credentials(account_name, api_url, api_key)
|
|
699
|
+
return url, key
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""CLI commands package exports."""
|
|
2
2
|
|
|
3
|
-
from glaip_sdk.cli.commands import agents, configure, mcps, models, tools, update
|
|
3
|
+
from glaip_sdk.cli.commands import agents, configure, mcps, models, tools, transcripts, update
|
|
4
4
|
|
|
5
|
-
__all__ = ["agents", "configure", "mcps", "models", "tools", "update"]
|
|
5
|
+
__all__ = ["agents", "configure", "mcps", "models", "tools", "transcripts", "update"]
|