glaip-sdk 0.1.3__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.
Files changed (141) hide show
  1. glaip_sdk/__init__.py +5 -2
  2. glaip_sdk/_version.py +9 -0
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1191 -0
  5. glaip_sdk/branding.py +13 -0
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/auth.py +254 -15
  8. glaip_sdk/cli/commands/__init__.py +2 -2
  9. glaip_sdk/cli/commands/accounts.py +746 -0
  10. glaip_sdk/cli/commands/agents.py +213 -73
  11. glaip_sdk/cli/commands/common_config.py +101 -0
  12. glaip_sdk/cli/commands/configure.py +729 -113
  13. glaip_sdk/cli/commands/mcps.py +241 -72
  14. glaip_sdk/cli/commands/models.py +11 -5
  15. glaip_sdk/cli/commands/tools.py +49 -57
  16. glaip_sdk/cli/commands/transcripts.py +755 -0
  17. glaip_sdk/cli/config.py +48 -4
  18. glaip_sdk/cli/constants.py +38 -0
  19. glaip_sdk/cli/context.py +8 -0
  20. glaip_sdk/cli/core/__init__.py +79 -0
  21. glaip_sdk/cli/core/context.py +124 -0
  22. glaip_sdk/cli/core/output.py +846 -0
  23. glaip_sdk/cli/core/prompting.py +649 -0
  24. glaip_sdk/cli/core/rendering.py +187 -0
  25. glaip_sdk/cli/display.py +35 -19
  26. glaip_sdk/cli/hints.py +57 -0
  27. glaip_sdk/cli/io.py +6 -3
  28. glaip_sdk/cli/main.py +228 -119
  29. glaip_sdk/cli/masking.py +21 -33
  30. glaip_sdk/cli/pager.py +9 -10
  31. glaip_sdk/cli/parsers/__init__.py +1 -3
  32. glaip_sdk/cli/slash/__init__.py +0 -9
  33. glaip_sdk/cli/slash/accounts_controller.py +578 -0
  34. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  35. glaip_sdk/cli/slash/agent_session.py +62 -21
  36. glaip_sdk/cli/slash/prompt.py +21 -0
  37. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  38. glaip_sdk/cli/slash/session.py +771 -140
  39. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  40. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  41. glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
  42. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  43. glaip_sdk/cli/slash/tui/loading.py +58 -0
  44. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  45. glaip_sdk/cli/transcript/__init__.py +12 -52
  46. glaip_sdk/cli/transcript/cache.py +255 -44
  47. glaip_sdk/cli/transcript/capture.py +27 -1
  48. glaip_sdk/cli/transcript/history.py +815 -0
  49. glaip_sdk/cli/transcript/viewer.py +72 -499
  50. glaip_sdk/cli/update_notifier.py +14 -5
  51. glaip_sdk/cli/utils.py +243 -1252
  52. glaip_sdk/cli/validators.py +5 -6
  53. glaip_sdk/client/__init__.py +2 -1
  54. glaip_sdk/client/_agent_payloads.py +45 -9
  55. glaip_sdk/client/agent_runs.py +147 -0
  56. glaip_sdk/client/agents.py +287 -29
  57. glaip_sdk/client/base.py +1 -0
  58. glaip_sdk/client/main.py +19 -10
  59. glaip_sdk/client/mcps.py +122 -12
  60. glaip_sdk/client/run_rendering.py +133 -88
  61. glaip_sdk/client/shared.py +21 -0
  62. glaip_sdk/client/tools.py +155 -10
  63. glaip_sdk/config/constants.py +11 -0
  64. glaip_sdk/mcps/__init__.py +21 -0
  65. glaip_sdk/mcps/base.py +345 -0
  66. glaip_sdk/models/__init__.py +90 -0
  67. glaip_sdk/models/agent.py +47 -0
  68. glaip_sdk/models/agent_runs.py +116 -0
  69. glaip_sdk/models/common.py +42 -0
  70. glaip_sdk/models/mcp.py +33 -0
  71. glaip_sdk/models/tool.py +33 -0
  72. glaip_sdk/payload_schemas/__init__.py +1 -13
  73. glaip_sdk/registry/__init__.py +55 -0
  74. glaip_sdk/registry/agent.py +164 -0
  75. glaip_sdk/registry/base.py +139 -0
  76. glaip_sdk/registry/mcp.py +253 -0
  77. glaip_sdk/registry/tool.py +232 -0
  78. glaip_sdk/rich_components.py +58 -2
  79. glaip_sdk/runner/__init__.py +59 -0
  80. glaip_sdk/runner/base.py +84 -0
  81. glaip_sdk/runner/deps.py +115 -0
  82. glaip_sdk/runner/langgraph.py +706 -0
  83. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  84. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  85. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  86. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  87. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  88. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  89. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  90. glaip_sdk/tools/__init__.py +22 -0
  91. glaip_sdk/tools/base.py +435 -0
  92. glaip_sdk/utils/__init__.py +58 -12
  93. glaip_sdk/utils/a2a/__init__.py +34 -0
  94. glaip_sdk/utils/a2a/event_processor.py +188 -0
  95. glaip_sdk/utils/bundler.py +267 -0
  96. glaip_sdk/utils/client.py +111 -0
  97. glaip_sdk/utils/client_utils.py +39 -7
  98. glaip_sdk/utils/datetime_helpers.py +58 -0
  99. glaip_sdk/utils/discovery.py +78 -0
  100. glaip_sdk/utils/display.py +23 -15
  101. glaip_sdk/utils/export.py +143 -0
  102. glaip_sdk/utils/general.py +0 -33
  103. glaip_sdk/utils/import_export.py +12 -7
  104. glaip_sdk/utils/import_resolver.py +492 -0
  105. glaip_sdk/utils/instructions.py +101 -0
  106. glaip_sdk/utils/rendering/__init__.py +115 -1
  107. glaip_sdk/utils/rendering/formatting.py +5 -30
  108. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  109. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  110. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  111. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  112. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  113. glaip_sdk/utils/rendering/models.py +1 -0
  114. glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
  115. glaip_sdk/utils/rendering/renderer/base.py +217 -1476
  116. glaip_sdk/utils/rendering/renderer/debug.py +26 -20
  117. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  118. glaip_sdk/utils/rendering/renderer/stream.py +4 -12
  119. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  120. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  121. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  122. glaip_sdk/utils/rendering/state.py +204 -0
  123. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  124. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
  125. glaip_sdk/utils/rendering/steps/format.py +176 -0
  126. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  127. glaip_sdk/utils/rendering/timing.py +36 -0
  128. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  129. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  130. glaip_sdk/utils/resource_refs.py +25 -13
  131. glaip_sdk/utils/runtime_config.py +425 -0
  132. glaip_sdk/utils/serialization.py +18 -0
  133. glaip_sdk/utils/sync.py +142 -0
  134. glaip_sdk/utils/tool_detection.py +33 -0
  135. glaip_sdk/utils/validation.py +16 -24
  136. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
  137. glaip_sdk-0.6.10.dist-info/RECORD +159 -0
  138. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
  139. glaip_sdk/models.py +0 -240
  140. glaip_sdk-0.1.3.dist-info/RECORD +0 -83
  141. {glaip_sdk-0.1.3.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
- These helpers are distinct from the AIP CLI's own authentication, which always
7
- relies on the API URL and API key managed via ``aip configure`` / `AIP_API_*`
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
- from collections.abc import Iterable, Mapping
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.utils import command_hint, format_command_hint
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(
@@ -107,6 +109,24 @@ def _get_token_value(prompt_for_secrets: bool, placeholder: str, console: Consol
107
109
  return placeholder
108
110
 
109
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
+
110
130
  def _build_bearer_headers(auth: dict[str, Any], token_value: str) -> dict[str, str]:
111
131
  """Build headers for bearer token authentication.
112
132
 
@@ -118,7 +138,10 @@ def _build_bearer_headers(auth: dict[str, Any], token_value: str) -> dict[str, s
118
138
  A dictionary of HTTP headers including the Authorization header when
119
139
  applicable.
120
140
  """
121
- header_keys = auth.get("header_keys", ["Authorization"])
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)
122
145
  headers = {}
123
146
  for key in header_keys:
124
147
  # Prepend "Bearer " if this is Authorization header
@@ -177,8 +200,8 @@ def _extract_api_key_name(auth: dict[str, Any]) -> str | None:
177
200
  """
178
201
  key_name = auth.get("key")
179
202
  if not key_name and "header_keys" in auth:
180
- header_keys = auth["header_keys"]
181
- if isinstance(header_keys, list) and header_keys:
203
+ header_keys = _normalize_header_keys(auth["header_keys"])
204
+ if header_keys:
182
205
  key_name = header_keys[0]
183
206
  return key_name
184
207
 
@@ -226,8 +249,12 @@ def _build_api_key_headers(auth: dict[str, Any], key_name: str | None, key_value
226
249
  Returns:
227
250
  A dictionary of HTTP headers for API key authentication.
228
251
  """
229
- header_keys = auth.get("header_keys", [key_name] if key_name else [])
230
- return {key: key_value for key in header_keys}
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)
231
258
 
232
259
 
233
260
  def _prepare_api_key_auth(
@@ -313,9 +340,7 @@ def _extract_header_names(existing_headers: Mapping[str, Any] | None, header_key
313
340
  """
314
341
  if existing_headers:
315
342
  return list(existing_headers.keys())
316
- if header_keys:
317
- return list(header_keys)
318
- return []
343
+ return _normalize_header_keys(header_keys)
319
344
 
320
345
 
321
346
  def _is_valid_secret(value: Any) -> bool:
@@ -435,7 +460,7 @@ def _prompt_secret_with_placeholder(
435
460
  )
436
461
 
437
462
  attempts = 0
438
- while attempts <= retry_limit:
463
+ while attempts <= retry_limit: # pragma: no cover
439
464
  response = click.prompt(
440
465
  prompt_message,
441
466
  default="",
@@ -458,3 +483,217 @@ def _prompt_secret_with_placeholder(
458
483
 
459
484
  # This line is unreachable as the loop always returns
460
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"]