glaip-sdk 0.6.12__py3-none-any.whl → 0.6.15__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.12.dist-info → glaip_sdk-0.6.15.dist-info}/METADATA +32 -37
- glaip_sdk-0.6.15.dist-info/RECORD +12 -0
- {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.15.dist-info}/WHEEL +2 -1
- glaip_sdk-0.6.15.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.6.15.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.12.dist-info/RECORD +0 -159
- glaip_sdk-0.6.12.dist-info/entry_points.txt +0 -3
glaip_sdk/cli/auth.py
DELETED
|
@@ -1,699 +0,0 @@
|
|
|
1
|
-
"""Authentication export helpers for MCP CLI commands and credential resolution.
|
|
2
|
-
|
|
3
|
-
This module provides utilities for preparing authentication data for export,
|
|
4
|
-
including interactive secret capture and placeholder generation.
|
|
5
|
-
|
|
6
|
-
It also provides credential resolution for the AIP CLI, supporting multiple
|
|
7
|
-
account profiles and environment variable overrides.
|
|
8
|
-
|
|
9
|
-
Authors:
|
|
10
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import os
|
|
14
|
-
from collections.abc import Callable, Iterable, Mapping
|
|
15
|
-
from typing import Any
|
|
16
|
-
|
|
17
|
-
import click
|
|
18
|
-
from rich.console import Console
|
|
19
|
-
|
|
20
|
-
from glaip_sdk.branding import HINT_PREFIX_STYLE, WARNING_STYLE
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def prepare_authentication_export(
|
|
27
|
-
auth: dict[str, Any] | None,
|
|
28
|
-
*,
|
|
29
|
-
prompt_for_secrets: bool,
|
|
30
|
-
placeholder: str,
|
|
31
|
-
console: Console,
|
|
32
|
-
) -> dict[str, Any] | None:
|
|
33
|
-
"""Prepare authentication data for export with secret handling.
|
|
34
|
-
|
|
35
|
-
This function processes authentication objects from MCP resources and prepares
|
|
36
|
-
them for export. It handles secret capture (interactive or placeholder mode),
|
|
37
|
-
reconstructs proper authentication structures from helper metadata, and ensures
|
|
38
|
-
helper metadata doesn't leak into the final export.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
auth: Authentication dictionary from an MCP resource. May contain helper
|
|
42
|
-
metadata like ``header_keys`` that should be consumed and removed.
|
|
43
|
-
prompt_for_secrets: If True, interactively prompt for missing secrets.
|
|
44
|
-
If False, use ``placeholder`` automatically.
|
|
45
|
-
placeholder: Placeholder text to use for missing secrets when not prompting.
|
|
46
|
-
console: Rich ``Console`` instance for user interaction and warnings.
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
A prepared authentication dictionary ready for export, or ``None`` if
|
|
50
|
-
``auth`` is ``None``.
|
|
51
|
-
|
|
52
|
-
Notes:
|
|
53
|
-
- Helper metadata (for example, ``header_keys``) is consumed to rebuild
|
|
54
|
-
structures but never appears in the final output.
|
|
55
|
-
- When ``prompt_for_secrets`` is False and stdin is not a TTY, a warning is
|
|
56
|
-
logged.
|
|
57
|
-
- Empty user input during prompts defaults to the placeholder value.
|
|
58
|
-
"""
|
|
59
|
-
if auth is None:
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
auth_type = auth.get("type")
|
|
63
|
-
|
|
64
|
-
# Handle no-auth case
|
|
65
|
-
if auth_type == "no-auth":
|
|
66
|
-
return {"type": "no-auth"}
|
|
67
|
-
|
|
68
|
-
# Handle bearer-token authentication
|
|
69
|
-
if auth_type == "bearer-token":
|
|
70
|
-
return _prepare_bearer_token_auth(auth, prompt_for_secrets, placeholder, console)
|
|
71
|
-
|
|
72
|
-
# Handle api-key authentication
|
|
73
|
-
if auth_type == "api-key":
|
|
74
|
-
return _prepare_api_key_auth(auth, prompt_for_secrets, placeholder, console)
|
|
75
|
-
|
|
76
|
-
# Handle custom-header authentication
|
|
77
|
-
if auth_type == "custom-header":
|
|
78
|
-
return _prepare_custom_header_auth(auth, prompt_for_secrets, placeholder, console)
|
|
79
|
-
|
|
80
|
-
# Unknown auth type - return as-is but strip helper metadata
|
|
81
|
-
result = auth.copy()
|
|
82
|
-
result.pop("header_keys", None)
|
|
83
|
-
return result
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _get_token_value(prompt_for_secrets: bool, placeholder: str, console: Console) -> str:
|
|
87
|
-
"""Get bearer token value either by prompting or using a placeholder.
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
prompt_for_secrets: If True, prompt for the token value.
|
|
91
|
-
placeholder: Placeholder to use when not prompting or when input is empty.
|
|
92
|
-
console: Rich ``Console`` used to display informational messages.
|
|
93
|
-
|
|
94
|
-
Returns:
|
|
95
|
-
The token string, either provided by the user or the placeholder.
|
|
96
|
-
"""
|
|
97
|
-
if prompt_for_secrets:
|
|
98
|
-
return _prompt_secret_with_placeholder(
|
|
99
|
-
console,
|
|
100
|
-
warning_message="Bearer token is missing or redacted. Please provide the token.",
|
|
101
|
-
prompt_message="Bearer token (leave blank for placeholder)",
|
|
102
|
-
placeholder=placeholder,
|
|
103
|
-
tip_cli_command="configure",
|
|
104
|
-
tip_slash_command="configure",
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
if not click.get_text_stream("stdin").isatty():
|
|
108
|
-
console.print(f"[{WARNING_STYLE}]⚠️ Non-interactive mode: using placeholder for bearer token[/]")
|
|
109
|
-
return placeholder
|
|
110
|
-
|
|
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
|
-
|
|
130
|
-
def _build_bearer_headers(auth: dict[str, Any], token_value: str) -> dict[str, str]:
|
|
131
|
-
"""Build headers for bearer token authentication.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
auth: Original authentication dictionary which may include ``header_keys``.
|
|
135
|
-
token_value: The token value to embed into the headers.
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
A dictionary of HTTP headers including the Authorization header when
|
|
139
|
-
applicable.
|
|
140
|
-
"""
|
|
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)
|
|
145
|
-
headers = {}
|
|
146
|
-
for key in header_keys:
|
|
147
|
-
# Prepend "Bearer " if this is Authorization header
|
|
148
|
-
if key.lower() == "authorization":
|
|
149
|
-
headers[key] = f"Bearer {token_value}"
|
|
150
|
-
else:
|
|
151
|
-
headers[key] = token_value
|
|
152
|
-
return headers
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def _prepare_bearer_token_auth(
|
|
156
|
-
auth: dict[str, Any],
|
|
157
|
-
prompt_for_secrets: bool,
|
|
158
|
-
placeholder: str,
|
|
159
|
-
console: Console,
|
|
160
|
-
) -> dict[str, Any]:
|
|
161
|
-
"""Prepare bearer-token authentication for export.
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
auth: Original authentication dictionary.
|
|
165
|
-
prompt_for_secrets: Whether to prompt for secrets.
|
|
166
|
-
placeholder: Placeholder value for secrets.
|
|
167
|
-
console: Rich ``Console`` for interaction.
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
A prepared ``bearer-token`` authentication dictionary.
|
|
171
|
-
"""
|
|
172
|
-
# Check if token exists and is not masked
|
|
173
|
-
token = auth.get("token")
|
|
174
|
-
has_valid_token = token and token not in (None, "", "***", "REDACTED")
|
|
175
|
-
|
|
176
|
-
# If we have a valid token, use it
|
|
177
|
-
if has_valid_token:
|
|
178
|
-
return {"type": "bearer-token", "token": token}
|
|
179
|
-
|
|
180
|
-
# Get token value (prompt or placeholder)
|
|
181
|
-
token_value = _get_token_value(prompt_for_secrets, placeholder, console)
|
|
182
|
-
|
|
183
|
-
# Check if original had headers structure
|
|
184
|
-
if "headers" in auth or "header_keys" in auth:
|
|
185
|
-
headers = _build_bearer_headers(auth, token_value)
|
|
186
|
-
return {"type": "bearer-token", "headers": headers}
|
|
187
|
-
|
|
188
|
-
# Use token field structure
|
|
189
|
-
return {"type": "bearer-token", "token": token_value}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def _extract_api_key_name(auth: dict[str, Any]) -> str | None:
|
|
193
|
-
"""Extract the API key name from an authentication dictionary.
|
|
194
|
-
|
|
195
|
-
Args:
|
|
196
|
-
auth: Authentication dictionary that may contain ``key`` or ``header_keys``.
|
|
197
|
-
|
|
198
|
-
Returns:
|
|
199
|
-
The API key name if available, otherwise ``None``.
|
|
200
|
-
"""
|
|
201
|
-
key_name = auth.get("key")
|
|
202
|
-
if not key_name and "header_keys" in auth:
|
|
203
|
-
header_keys = _normalize_header_keys(auth["header_keys"])
|
|
204
|
-
if header_keys:
|
|
205
|
-
key_name = header_keys[0]
|
|
206
|
-
return key_name
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def _get_api_key_value(
|
|
210
|
-
key_name: str | None,
|
|
211
|
-
prompt_for_secrets: bool,
|
|
212
|
-
placeholder: str,
|
|
213
|
-
console: Console,
|
|
214
|
-
) -> str:
|
|
215
|
-
"""Get API key value either by prompting or using a placeholder.
|
|
216
|
-
|
|
217
|
-
Args:
|
|
218
|
-
key_name: The name of the API key; used in prompt messages.
|
|
219
|
-
prompt_for_secrets: If True, prompt for the API key value.
|
|
220
|
-
placeholder: Placeholder to use when not prompting or when input is empty.
|
|
221
|
-
console: Rich ``Console`` used to display informational messages.
|
|
222
|
-
|
|
223
|
-
Returns:
|
|
224
|
-
The API key value, either provided by the user or the placeholder.
|
|
225
|
-
"""
|
|
226
|
-
if prompt_for_secrets:
|
|
227
|
-
return _prompt_secret_with_placeholder(
|
|
228
|
-
console,
|
|
229
|
-
warning_message=f"API key value for '{key_name}' is missing or redacted.",
|
|
230
|
-
prompt_message=f"API key value for '{key_name}' (leave blank for placeholder)",
|
|
231
|
-
placeholder=placeholder,
|
|
232
|
-
tip_cli_command="configure api-key",
|
|
233
|
-
tip_slash_command="configure",
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
if not click.get_text_stream("stdin").isatty():
|
|
237
|
-
console.print(f"[{WARNING_STYLE}]⚠️ Non-interactive mode: using placeholder for API key '{key_name}'[/]")
|
|
238
|
-
return placeholder
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def _build_api_key_headers(auth: dict[str, Any], key_name: str | None, key_value: str) -> dict[str, str]:
|
|
242
|
-
"""Build headers for API key authentication.
|
|
243
|
-
|
|
244
|
-
Args:
|
|
245
|
-
auth: Original authentication dictionary which may include ``header_keys``.
|
|
246
|
-
key_name: The header key name if present.
|
|
247
|
-
key_value: The API key value to populate.
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
A dictionary of HTTP headers for API key authentication.
|
|
251
|
-
"""
|
|
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)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def _prepare_api_key_auth(
|
|
261
|
-
auth: dict[str, Any],
|
|
262
|
-
prompt_for_secrets: bool,
|
|
263
|
-
placeholder: str,
|
|
264
|
-
console: Console,
|
|
265
|
-
) -> dict[str, Any]:
|
|
266
|
-
"""Prepare api-key authentication for export.
|
|
267
|
-
|
|
268
|
-
Args:
|
|
269
|
-
auth: Original authentication dictionary.
|
|
270
|
-
prompt_for_secrets: Whether to prompt for secrets.
|
|
271
|
-
placeholder: Placeholder value for secrets.
|
|
272
|
-
console: Rich ``Console`` for interaction.
|
|
273
|
-
|
|
274
|
-
Returns:
|
|
275
|
-
A prepared ``api-key`` authentication dictionary.
|
|
276
|
-
"""
|
|
277
|
-
# Extract key name and value
|
|
278
|
-
key_name = _extract_api_key_name(auth)
|
|
279
|
-
key_value = auth.get("value")
|
|
280
|
-
|
|
281
|
-
# Check if we have a valid value
|
|
282
|
-
has_valid_value = key_value and key_value not in (None, "", "***", "REDACTED")
|
|
283
|
-
|
|
284
|
-
# Capture or use placeholder for value
|
|
285
|
-
if not has_valid_value:
|
|
286
|
-
key_value = _get_api_key_value(key_name, prompt_for_secrets, placeholder, console)
|
|
287
|
-
|
|
288
|
-
# Check if original had headers structure
|
|
289
|
-
if "headers" in auth or "header_keys" in auth:
|
|
290
|
-
headers = _build_api_key_headers(auth, key_name, key_value)
|
|
291
|
-
return {"type": "api-key", "headers": headers}
|
|
292
|
-
|
|
293
|
-
# Use key/value field structure
|
|
294
|
-
return {"type": "api-key", "key": key_name, "value": key_value}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def _prepare_custom_header_auth(
|
|
298
|
-
auth: dict[str, Any],
|
|
299
|
-
prompt_for_secrets: bool,
|
|
300
|
-
placeholder: str,
|
|
301
|
-
console: Console,
|
|
302
|
-
) -> dict[str, Any]:
|
|
303
|
-
"""Prepare custom-header authentication for export.
|
|
304
|
-
|
|
305
|
-
Args:
|
|
306
|
-
auth: Original authentication dictionary.
|
|
307
|
-
prompt_for_secrets: Whether to prompt for header values.
|
|
308
|
-
placeholder: Placeholder value when not prompting or input is empty.
|
|
309
|
-
console: Rich ``Console`` for interaction.
|
|
310
|
-
|
|
311
|
-
Returns:
|
|
312
|
-
A prepared ``custom-header`` authentication dictionary.
|
|
313
|
-
"""
|
|
314
|
-
existing_headers: dict[str, Any] = auth.get("headers", {})
|
|
315
|
-
header_names = _extract_header_names(existing_headers, auth.get("header_keys", []))
|
|
316
|
-
|
|
317
|
-
if not header_names:
|
|
318
|
-
return {"type": "custom-header", "headers": {}}
|
|
319
|
-
|
|
320
|
-
headers = _build_custom_headers(
|
|
321
|
-
existing_headers=existing_headers,
|
|
322
|
-
header_names=header_names,
|
|
323
|
-
prompt_for_secrets=prompt_for_secrets,
|
|
324
|
-
placeholder=placeholder,
|
|
325
|
-
console=console,
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
return {"type": "custom-header", "headers": headers}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def _extract_header_names(existing_headers: Mapping[str, Any] | None, header_keys: Iterable[str] | None) -> list[str]:
|
|
332
|
-
"""Extract the list of header names to process.
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
existing_headers: Existing headers mapping from the auth object.
|
|
336
|
-
header_keys: Optional helper metadata listing header names.
|
|
337
|
-
|
|
338
|
-
Returns:
|
|
339
|
-
A list of header names to process.
|
|
340
|
-
"""
|
|
341
|
-
if existing_headers:
|
|
342
|
-
return list(existing_headers.keys())
|
|
343
|
-
return _normalize_header_keys(header_keys)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
def _is_valid_secret(value: Any) -> bool:
|
|
347
|
-
"""Determine whether a secret value is present and not masked.
|
|
348
|
-
|
|
349
|
-
Args:
|
|
350
|
-
value: The value to test.
|
|
351
|
-
|
|
352
|
-
Returns:
|
|
353
|
-
True if the value is non-empty and not one of the masked placeholders.
|
|
354
|
-
"""
|
|
355
|
-
return bool(value) and value not in (None, "", "***", "REDACTED")
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
def _prompt_or_placeholder(
|
|
359
|
-
name: str,
|
|
360
|
-
prompt_for_secrets: bool,
|
|
361
|
-
placeholder: str,
|
|
362
|
-
console: Console,
|
|
363
|
-
) -> str:
|
|
364
|
-
"""Prompt for a header value or return the placeholder when not prompting.
|
|
365
|
-
|
|
366
|
-
Args:
|
|
367
|
-
name: Header name used in prompt messages.
|
|
368
|
-
prompt_for_secrets: If True, prompt for the value interactively.
|
|
369
|
-
placeholder: Placeholder value used when not prompting or empty input.
|
|
370
|
-
console: Rich ``Console`` instance for user-facing messages.
|
|
371
|
-
|
|
372
|
-
Returns:
|
|
373
|
-
The provided value or the placeholder.
|
|
374
|
-
"""
|
|
375
|
-
if prompt_for_secrets:
|
|
376
|
-
return _prompt_secret_with_placeholder(
|
|
377
|
-
console,
|
|
378
|
-
warning_message=f"Header '{name}' is missing or redacted.",
|
|
379
|
-
prompt_message=f"Value for header '{name}' (leave blank for placeholder)",
|
|
380
|
-
placeholder=placeholder,
|
|
381
|
-
tip_cli_command="configure",
|
|
382
|
-
tip_slash_command="configure",
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
if not click.get_text_stream("stdin").isatty():
|
|
386
|
-
console.print(f"[{WARNING_STYLE}]⚠️ Non-interactive mode: using placeholder for header '{name}'[/]")
|
|
387
|
-
return placeholder
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
def _build_custom_headers(
|
|
391
|
-
*,
|
|
392
|
-
existing_headers: Mapping[str, Any],
|
|
393
|
-
header_names: Iterable[str],
|
|
394
|
-
prompt_for_secrets: bool,
|
|
395
|
-
placeholder: str,
|
|
396
|
-
console: Console,
|
|
397
|
-
) -> dict[str, str]:
|
|
398
|
-
"""Build a headers mapping for custom-header authentication.
|
|
399
|
-
|
|
400
|
-
Args:
|
|
401
|
-
existing_headers: Existing headers mapping from the auth object.
|
|
402
|
-
header_names: Header names to process.
|
|
403
|
-
prompt_for_secrets: Whether to prompt for missing values.
|
|
404
|
-
placeholder: Placeholder to use for missing or masked values.
|
|
405
|
-
console: Rich ``Console`` used for prompt/warning messages.
|
|
406
|
-
|
|
407
|
-
Returns:
|
|
408
|
-
A dictionary mapping header names to resolved values.
|
|
409
|
-
"""
|
|
410
|
-
headers: dict[str, str] = {}
|
|
411
|
-
for name in header_names:
|
|
412
|
-
existing_value = existing_headers.get(name)
|
|
413
|
-
if _is_valid_secret(existing_value):
|
|
414
|
-
headers[name] = str(existing_value)
|
|
415
|
-
continue
|
|
416
|
-
|
|
417
|
-
headers[name] = _prompt_or_placeholder(
|
|
418
|
-
name=name,
|
|
419
|
-
prompt_for_secrets=prompt_for_secrets,
|
|
420
|
-
placeholder=placeholder,
|
|
421
|
-
console=console,
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
return headers
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
def _prompt_secret_with_placeholder(
|
|
428
|
-
console: Console,
|
|
429
|
-
*,
|
|
430
|
-
warning_message: str,
|
|
431
|
-
prompt_message: str,
|
|
432
|
-
placeholder: str,
|
|
433
|
-
tip_cli_command: str | None = "configure",
|
|
434
|
-
tip_slash_command: str | None = "configure",
|
|
435
|
-
mask_input: bool = True,
|
|
436
|
-
retry_limit: int = 1,
|
|
437
|
-
) -> str:
|
|
438
|
-
"""Prompt for a secret value with masking, retries, and placeholder fallback.
|
|
439
|
-
|
|
440
|
-
Args:
|
|
441
|
-
console: Rich console used to render messaging.
|
|
442
|
-
warning_message: Message shown before prompting (rendered with warning style).
|
|
443
|
-
prompt_message: The message passed to :func:`click.prompt`.
|
|
444
|
-
placeholder: Placeholder value inserted when the user skips input.
|
|
445
|
-
tip_cli_command: CLI command (without ``aip`` prefix) used to build hints.
|
|
446
|
-
tip_slash_command: Slash command counterpart used in hints.
|
|
447
|
-
mask_input: Whether to hide user input while typing.
|
|
448
|
-
retry_limit: Number of additional attempts when the user submits empty input.
|
|
449
|
-
|
|
450
|
-
Returns:
|
|
451
|
-
The value entered by the user or the provided placeholder.
|
|
452
|
-
"""
|
|
453
|
-
console.print(f"[{WARNING_STYLE}]{warning_message}[/]")
|
|
454
|
-
|
|
455
|
-
tip = command_hint(tip_cli_command, tip_slash_command)
|
|
456
|
-
if tip:
|
|
457
|
-
console.print(
|
|
458
|
-
f"[{HINT_PREFIX_STYLE}]Tip:[/] use {format_command_hint(tip) or tip} later "
|
|
459
|
-
"if you want to update these credentials."
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
attempts = 0
|
|
463
|
-
while attempts <= retry_limit: # pragma: no cover
|
|
464
|
-
response = click.prompt(
|
|
465
|
-
prompt_message,
|
|
466
|
-
default="",
|
|
467
|
-
show_default=False,
|
|
468
|
-
hide_input=mask_input,
|
|
469
|
-
)
|
|
470
|
-
value = response.strip()
|
|
471
|
-
if value:
|
|
472
|
-
return value
|
|
473
|
-
|
|
474
|
-
if attempts < retry_limit:
|
|
475
|
-
console.print(
|
|
476
|
-
f"[{WARNING_STYLE}]No value entered. Enter a value or press Enter again to use the placeholder.[/]"
|
|
477
|
-
)
|
|
478
|
-
attempts += 1
|
|
479
|
-
continue
|
|
480
|
-
|
|
481
|
-
console.print("[dim]Using placeholder value.[/dim]")
|
|
482
|
-
return placeholder
|
|
483
|
-
|
|
484
|
-
# This line is unreachable as the loop always returns
|
|
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
|