synth-ai 0.4.1__py3-none-any.whl → 0.4.4__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.

Potentially problematic release.


This version of synth-ai might be problematic. Click here for more details.

Files changed (153) hide show
  1. synth_ai/__init__.py +13 -13
  2. synth_ai/cli/__init__.py +6 -15
  3. synth_ai/cli/commands/eval/__init__.py +6 -15
  4. synth_ai/cli/commands/eval/config.py +338 -0
  5. synth_ai/cli/commands/eval/core.py +236 -1091
  6. synth_ai/cli/commands/eval/runner.py +704 -0
  7. synth_ai/cli/commands/eval/validation.py +44 -117
  8. synth_ai/cli/commands/filter/core.py +7 -7
  9. synth_ai/cli/commands/filter/validation.py +2 -2
  10. synth_ai/cli/commands/smoke/core.py +7 -17
  11. synth_ai/cli/commands/status/__init__.py +1 -64
  12. synth_ai/cli/commands/status/client.py +50 -151
  13. synth_ai/cli/commands/status/config.py +3 -83
  14. synth_ai/cli/commands/status/errors.py +4 -13
  15. synth_ai/cli/commands/status/subcommands/__init__.py +2 -8
  16. synth_ai/cli/commands/status/subcommands/config.py +13 -0
  17. synth_ai/cli/commands/status/subcommands/files.py +18 -63
  18. synth_ai/cli/commands/status/subcommands/jobs.py +28 -311
  19. synth_ai/cli/commands/status/subcommands/models.py +18 -62
  20. synth_ai/cli/commands/status/subcommands/runs.py +16 -63
  21. synth_ai/cli/commands/status/subcommands/session.py +67 -172
  22. synth_ai/cli/commands/status/subcommands/summary.py +24 -32
  23. synth_ai/cli/commands/status/subcommands/utils.py +41 -0
  24. synth_ai/cli/commands/status/utils.py +16 -107
  25. synth_ai/cli/commands/train/__init__.py +18 -20
  26. synth_ai/cli/commands/train/errors.py +3 -3
  27. synth_ai/cli/commands/train/prompt_learning_validation.py +15 -16
  28. synth_ai/cli/commands/train/validation.py +7 -7
  29. synth_ai/cli/commands/train/{judge_schemas.py → verifier_schemas.py} +33 -34
  30. synth_ai/cli/commands/train/verifier_validation.py +235 -0
  31. synth_ai/cli/demo_apps/demo_task_apps/math/config.toml +0 -1
  32. synth_ai/cli/demo_apps/demo_task_apps/math/modal_task_app.py +2 -6
  33. synth_ai/cli/demo_apps/math/config.toml +0 -1
  34. synth_ai/cli/demo_apps/math/modal_task_app.py +2 -6
  35. synth_ai/cli/demo_apps/mipro/task_app.py +25 -47
  36. synth_ai/cli/lib/apps/task_app.py +12 -13
  37. synth_ai/cli/lib/task_app_discovery.py +6 -6
  38. synth_ai/cli/lib/train_cfgs.py +10 -10
  39. synth_ai/cli/task_apps/__init__.py +11 -0
  40. synth_ai/cli/task_apps/commands.py +7 -15
  41. synth_ai/core/env.py +12 -1
  42. synth_ai/core/errors.py +1 -2
  43. synth_ai/core/integrations/cloudflare.py +209 -33
  44. synth_ai/core/tracing_v3/abstractions.py +46 -0
  45. synth_ai/data/__init__.py +3 -30
  46. synth_ai/data/enums.py +1 -20
  47. synth_ai/data/rewards.py +100 -3
  48. synth_ai/products/graph_evolve/__init__.py +1 -2
  49. synth_ai/products/graph_evolve/config.py +16 -16
  50. synth_ai/products/graph_evolve/converters/__init__.py +3 -3
  51. synth_ai/products/graph_evolve/converters/openai_sft.py +7 -7
  52. synth_ai/products/graph_evolve/examples/hotpotqa/config.toml +1 -1
  53. synth_ai/products/graph_gepa/__init__.py +23 -0
  54. synth_ai/products/graph_gepa/converters/__init__.py +19 -0
  55. synth_ai/products/graph_gepa/converters/openai_sft.py +29 -0
  56. synth_ai/sdk/__init__.py +45 -35
  57. synth_ai/sdk/api/eval/__init__.py +33 -0
  58. synth_ai/sdk/api/eval/job.py +732 -0
  59. synth_ai/sdk/api/research_agent/__init__.py +276 -66
  60. synth_ai/sdk/api/train/builders.py +181 -0
  61. synth_ai/sdk/api/train/cli.py +41 -33
  62. synth_ai/sdk/api/train/configs/__init__.py +6 -4
  63. synth_ai/sdk/api/train/configs/prompt_learning.py +127 -33
  64. synth_ai/sdk/api/train/configs/rl.py +264 -16
  65. synth_ai/sdk/api/train/configs/sft.py +165 -1
  66. synth_ai/sdk/api/train/graph_validators.py +12 -12
  67. synth_ai/sdk/api/train/graphgen.py +169 -51
  68. synth_ai/sdk/api/train/graphgen_models.py +95 -45
  69. synth_ai/sdk/api/train/local_api.py +10 -0
  70. synth_ai/sdk/api/train/pollers.py +36 -0
  71. synth_ai/sdk/api/train/prompt_learning.py +390 -60
  72. synth_ai/sdk/api/train/rl.py +41 -5
  73. synth_ai/sdk/api/train/sft.py +2 -0
  74. synth_ai/sdk/api/train/task_app.py +20 -0
  75. synth_ai/sdk/api/train/validators.py +17 -17
  76. synth_ai/sdk/graphs/completions.py +239 -33
  77. synth_ai/sdk/{judging/schemas.py → graphs/verifier_schemas.py} +23 -23
  78. synth_ai/sdk/learning/__init__.py +35 -5
  79. synth_ai/sdk/learning/context_learning_client.py +531 -0
  80. synth_ai/sdk/learning/context_learning_types.py +294 -0
  81. synth_ai/sdk/learning/prompt_learning_client.py +1 -1
  82. synth_ai/sdk/learning/prompt_learning_types.py +2 -1
  83. synth_ai/sdk/learning/rl/__init__.py +0 -4
  84. synth_ai/sdk/learning/rl/contracts.py +0 -4
  85. synth_ai/sdk/localapi/__init__.py +40 -0
  86. synth_ai/sdk/localapi/apps/__init__.py +28 -0
  87. synth_ai/sdk/localapi/client.py +10 -0
  88. synth_ai/sdk/localapi/contracts.py +10 -0
  89. synth_ai/sdk/localapi/helpers.py +519 -0
  90. synth_ai/sdk/localapi/rollouts.py +93 -0
  91. synth_ai/sdk/localapi/server.py +29 -0
  92. synth_ai/sdk/localapi/template.py +49 -0
  93. synth_ai/sdk/streaming/handlers.py +6 -6
  94. synth_ai/sdk/streaming/streamer.py +10 -6
  95. synth_ai/sdk/task/__init__.py +18 -5
  96. synth_ai/sdk/task/apps/__init__.py +37 -1
  97. synth_ai/sdk/task/client.py +9 -1
  98. synth_ai/sdk/task/config.py +6 -11
  99. synth_ai/sdk/task/contracts.py +137 -95
  100. synth_ai/sdk/task/in_process.py +32 -22
  101. synth_ai/sdk/task/in_process_runner.py +9 -4
  102. synth_ai/sdk/task/rubrics/__init__.py +2 -3
  103. synth_ai/sdk/task/rubrics/loaders.py +4 -4
  104. synth_ai/sdk/task/rubrics/strict.py +3 -4
  105. synth_ai/sdk/task/server.py +76 -16
  106. synth_ai/sdk/task/trace_correlation_helpers.py +190 -139
  107. synth_ai/sdk/task/validators.py +34 -49
  108. synth_ai/sdk/training/__init__.py +7 -16
  109. synth_ai/sdk/tunnels/__init__.py +118 -0
  110. synth_ai/sdk/tunnels/cleanup.py +83 -0
  111. synth_ai/sdk/tunnels/ports.py +120 -0
  112. synth_ai/sdk/tunnels/tunneled_api.py +363 -0
  113. {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/METADATA +71 -4
  114. {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/RECORD +118 -128
  115. synth_ai/cli/commands/baseline/__init__.py +0 -12
  116. synth_ai/cli/commands/baseline/core.py +0 -636
  117. synth_ai/cli/commands/baseline/list.py +0 -94
  118. synth_ai/cli/commands/eval/errors.py +0 -81
  119. synth_ai/cli/commands/status/formatters.py +0 -164
  120. synth_ai/cli/commands/status/subcommands/pricing.py +0 -23
  121. synth_ai/cli/commands/status/subcommands/usage.py +0 -203
  122. synth_ai/cli/commands/train/judge_validation.py +0 -305
  123. synth_ai/cli/usage.py +0 -159
  124. synth_ai/data/specs.py +0 -36
  125. synth_ai/sdk/api/research_agent/cli.py +0 -428
  126. synth_ai/sdk/api/research_agent/config.py +0 -357
  127. synth_ai/sdk/api/research_agent/job.py +0 -717
  128. synth_ai/sdk/baseline/__init__.py +0 -25
  129. synth_ai/sdk/baseline/config.py +0 -209
  130. synth_ai/sdk/baseline/discovery.py +0 -216
  131. synth_ai/sdk/baseline/execution.py +0 -154
  132. synth_ai/sdk/judging/__init__.py +0 -15
  133. synth_ai/sdk/judging/base.py +0 -24
  134. synth_ai/sdk/judging/client.py +0 -191
  135. synth_ai/sdk/judging/types.py +0 -42
  136. synth_ai/sdk/research_agent/__init__.py +0 -34
  137. synth_ai/sdk/research_agent/container_builder.py +0 -328
  138. synth_ai/sdk/research_agent/container_spec.py +0 -198
  139. synth_ai/sdk/research_agent/defaults.py +0 -34
  140. synth_ai/sdk/research_agent/results_collector.py +0 -69
  141. synth_ai/sdk/specs/__init__.py +0 -46
  142. synth_ai/sdk/specs/dataclasses.py +0 -149
  143. synth_ai/sdk/specs/loader.py +0 -144
  144. synth_ai/sdk/specs/serializer.py +0 -199
  145. synth_ai/sdk/specs/validation.py +0 -250
  146. synth_ai/sdk/tracing/__init__.py +0 -39
  147. synth_ai/sdk/usage/__init__.py +0 -37
  148. synth_ai/sdk/usage/client.py +0 -171
  149. synth_ai/sdk/usage/models.py +0 -261
  150. {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/WHEEL +0 -0
  151. {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/entry_points.txt +0 -0
  152. {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/licenses/LICENSE +0 -0
  153. {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/top_level.txt +0 -0
@@ -1,79 +1,35 @@
1
- """`synth models` command group."""
1
+ """Status models subcommand."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
 
7
7
  import click
8
- from rich.json import JSON
9
8
 
10
- from ..client import StatusAPIClient
11
- from ..errors import StatusAPIError
12
- from ..formatters import console, models_table, print_json
13
- from ..utils import bail, common_options, resolve_context_config
9
+ from .config import StatusConfig
10
+ from .utils import StatusAPIClient, print_json
14
11
 
15
12
 
16
- @click.group("models", help="Inspect fine-tuned models.")
17
- @click.pass_context
18
- def models_group(ctx: click.Context) -> None: # pragma: no cover - Click wiring
19
- ctx.ensure_object(dict)
13
+ @click.group("models")
14
+ def models_group() -> None:
15
+ return None
20
16
 
21
17
 
22
18
  @models_group.command("list")
23
- @common_options()
24
- @click.option("--limit", type=int, default=None, help="Maximum number of models to return.")
25
- @click.option("--type", "model_type", type=click.Choice(["rl", "sft"]), default=None, help="Filter by model type.")
26
- @click.option("--json", "output_json", is_flag=True)
27
- @click.pass_context
28
- def list_models(
29
- ctx: click.Context,
30
- base_url: str | None,
31
- api_key: str | None,
32
- timeout: float,
33
- limit: int | None,
34
- model_type: str | None,
35
- output_json: bool,
36
- ) -> None:
37
- cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
38
-
19
+ @click.option("--limit", default=None, type=int)
20
+ @click.option("--type", "model_type", default=None)
21
+ @click.option("--json", "as_json", is_flag=True, default=False)
22
+ def list_models(limit: int | None, model_type: str | None, as_json: bool) -> None:
39
23
  async def _run() -> None:
40
- try:
41
- async with StatusAPIClient(cfg) as client:
42
- models = await client.list_models(limit=limit, model_type=model_type)
43
- if output_json:
44
- print_json(models)
45
- else:
46
- console.print(models_table(models))
47
- except StatusAPIError as exc:
48
- bail(f"Backend error: {exc}")
24
+ config = StatusConfig()
25
+ async with StatusAPIClient(config) as client:
26
+ data = await client.list_models(limit=limit, model_type=model_type) # type: ignore[attr-defined]
27
+ if as_json:
28
+ print_json(data)
29
+ else:
30
+ click.echo(data)
49
31
 
50
32
  asyncio.run(_run())
51
33
 
52
34
 
53
- @models_group.command("get")
54
- @common_options()
55
- @click.argument("model_id")
56
- @click.option("--json", "output_json", is_flag=True)
57
- @click.pass_context
58
- def get_model(
59
- ctx: click.Context,
60
- base_url: str | None,
61
- api_key: str | None,
62
- timeout: float,
63
- model_id: str,
64
- output_json: bool,
65
- ) -> None:
66
- cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
67
-
68
- async def _run() -> None:
69
- try:
70
- async with StatusAPIClient(cfg) as client:
71
- model = await client.get_model(model_id)
72
- if output_json:
73
- print_json(model)
74
- else:
75
- console.print(JSON.from_data(model))
76
- except StatusAPIError as exc:
77
- bail(f"Backend error: {exc}")
78
-
79
- asyncio.run(_run())
35
+ __all__ = ["models_group"]
@@ -1,4 +1,4 @@
1
- """`synth runs` command group."""
1
+ """Status runs subcommand."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -6,76 +6,29 @@ import asyncio
6
6
 
7
7
  import click
8
8
 
9
- from ..client import StatusAPIClient
10
- from ..errors import StatusAPIError
11
- from ..formatters import console, events_panel, print_json, runs_table
12
- from ..utils import bail, common_options, parse_relative_time, resolve_context_config
9
+ from .config import StatusConfig
10
+ from .utils import StatusAPIClient, print_json
13
11
 
14
12
 
15
- @click.group("runs", help="Inspect individual job runs/attempts.")
16
- @click.pass_context
17
- def runs_group(ctx: click.Context) -> None: # pragma: no cover - Click wiring
18
- ctx.ensure_object(dict)
13
+ @click.group("runs")
14
+ def runs_group() -> None:
15
+ return None
19
16
 
20
17
 
21
18
  @runs_group.command("list")
22
- @common_options()
23
19
  @click.argument("job_id")
24
- @click.option("--json", "output_json", is_flag=True)
25
- @click.pass_context
26
- def list_runs(
27
- ctx: click.Context,
28
- base_url: str | None,
29
- api_key: str | None,
30
- timeout: float,
31
- job_id: str,
32
- output_json: bool,
33
- ) -> None:
34
- cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
35
-
20
+ @click.option("--json", "as_json", is_flag=True, default=False)
21
+ def list_runs(job_id: str, as_json: bool) -> None:
36
22
  async def _run() -> None:
37
- try:
38
- async with StatusAPIClient(cfg) as client:
39
- runs = await client.list_job_runs(job_id)
40
- if output_json:
41
- print_json(runs)
42
- else:
43
- console.print(runs_table(runs))
44
- except StatusAPIError as exc:
45
- bail(f"Backend error: {exc}")
23
+ config = StatusConfig()
24
+ async with StatusAPIClient(config) as client:
25
+ data = await client.list_job_runs(job_id) # type: ignore[attr-defined]
26
+ if as_json:
27
+ print_json(data)
28
+ else:
29
+ click.echo(data)
46
30
 
47
31
  asyncio.run(_run())
48
32
 
49
33
 
50
- @runs_group.command("logs")
51
- @common_options()
52
- @click.argument("job_id")
53
- @click.option("--run", "run_id", required=True, help="Run identifier (number or ID) to inspect.")
54
- @click.option("--since", help="Filter events after the supplied timestamp/relative offset.")
55
- @click.option("--json", "output_json", is_flag=True)
56
- @click.pass_context
57
- def run_logs(
58
- ctx: click.Context,
59
- base_url: str | None,
60
- api_key: str | None,
61
- timeout: float,
62
- job_id: str,
63
- run_id: str,
64
- since: str | None,
65
- output_json: bool,
66
- ) -> None:
67
- cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
68
- since_filter = parse_relative_time(since)
69
-
70
- async def _run() -> None:
71
- try:
72
- async with StatusAPIClient(cfg) as client:
73
- events = await client.get_job_events(job_id, since=since_filter, run_id=run_id)
74
- if output_json:
75
- print_json(events)
76
- else:
77
- console.print(events_panel(events))
78
- except StatusAPIError as exc:
79
- bail(f"Backend error: {exc}")
80
-
81
- asyncio.run(_run())
34
+ __all__ = ["runs_group"]
@@ -1,182 +1,77 @@
1
- """Session status command for Synth CLI."""
1
+ """Status session subcommand."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
- from typing import Optional
6
+ import os
7
+ from decimal import Decimal
7
8
 
8
9
  import click
9
- from rich.console import Console
10
- from rich.table import Table
11
-
12
- from synth_ai.cli.commands.status.config import resolve_backend_config
13
- from synth_ai.cli.local.session import AgentSessionClient, SessionNotFoundError
14
-
15
- console = Console()
16
-
17
-
18
- @click.command("session", help="Show current agent session status and limits.")
19
- @click.option(
20
- "--session-id",
21
- type=str,
22
- default=None,
23
- help="Session ID to check (default: uses active session from environment)"
24
- )
25
- @click.option(
26
- "--base-url",
27
- envvar="SYNTH_STATUS_BASE_URL",
28
- default=None,
29
- help="Synth backend base URL (defaults to environment configuration).",
30
- )
31
- @click.option(
32
- "--api-key",
33
- envvar="SYNTH_STATUS_API_KEY",
34
- default=None,
35
- help="API key for authenticated requests (falls back to Synth defaults).",
36
- )
37
- @click.option(
38
- "--timeout",
39
- default=30.0,
40
- show_default=True,
41
- type=float,
42
- help="HTTP request timeout in seconds.",
43
- )
44
- def session_status_cmd(
45
- session_id: Optional[str],
46
- base_url: Optional[str],
47
- api_key: Optional[str],
48
- timeout: float,
49
- ) -> None:
50
- """Show agent session status, limits, and current usage."""
51
- cfg = resolve_backend_config(base_url=base_url, api_key=api_key, timeout=timeout)
52
-
10
+
11
+ from synth_ai.cli.commands.artifacts.config import resolve_backend_config
12
+ from synth_ai.cli.local.session.client import AgentSessionClient
13
+ from synth_ai.cli.local.session.exceptions import SessionNotFoundError
14
+ from synth_ai.cli.local.session.models import AgentSession
15
+
16
+
17
+ def _format_currency(value: Decimal) -> str:
18
+ return f"${value:.2f}"
19
+
20
+
21
+ def _format_int(value: Decimal | int) -> str:
22
+ return f"{int(value):,}"
23
+
24
+
25
+ def _session_exceeded(session: AgentSession) -> bool:
26
+ if session.status == "limit_exceeded":
27
+ return True
28
+ for limit in session.limits:
29
+ if limit.current_usage >= limit.limit_value:
30
+ return True
31
+ return False
32
+
33
+
34
+ def _render_session(session: AgentSession) -> None:
35
+ click.echo(f"Session: {session.session_id}")
36
+ click.echo(f"Status: {session.status}")
37
+ click.echo(f"Usage: {_format_currency(session.usage.cost_usd)}")
38
+ click.echo(f"Tokens: {_format_int(session.usage.tokens)}")
39
+ for limit in session.limits:
40
+ if limit.metric_type == "cost_usd":
41
+ click.echo(f"Limit: {_format_currency(limit.limit_value)}")
42
+ elif limit.metric_type == "tokens":
43
+ click.echo(f"Limit: {_format_int(limit.limit_value)}")
44
+ if _session_exceeded(session):
45
+ click.echo("✗ EXCEEDED")
46
+ click.echo("WARNING: Session limits exceeded!")
47
+ else:
48
+ click.echo("✓ OK")
49
+
50
+
51
+ @click.command("session")
52
+ @click.option("--session-id", default="", help="Session ID to inspect.")
53
+ def session_status_cmd(session_id: str) -> None:
53
54
  async def _run() -> None:
54
- import os
55
-
56
- # Use provided session_id or try to get from environment
57
- if not session_id:
58
- session_id_env = os.getenv("SYNTH_SESSION_ID")
59
- if session_id_env:
60
- session_id_to_use = session_id_env
61
- else:
62
- # Try to get active session
63
- if not cfg.api_key:
64
- console.print("[red]API key is required. Set SYNTH_API_KEY environment variable.[/red]")
65
- return
66
- client = AgentSessionClient(f"{cfg.base_url}/api", cfg.api_key)
67
- try:
68
- async with client._http:
69
- sessions = await client.list(status="active", limit=1)
70
- if sessions:
71
- session_id_to_use = sessions[0].session_id
72
- else:
73
- console.print("[yellow]No active session found.[/yellow]")
74
- console.print("Create a session with: [cyan]uvx synth-ai codex[/cyan] or [cyan]uvx synth-ai opencode[/cyan]")
75
- return
76
- except Exception as e:
77
- console.print(f"[red]Error fetching active session: {e}[/red]")
78
- return
79
- else:
80
- session_id_to_use = session_id
81
-
82
- if not cfg.api_key:
83
- console.print("[red]API key is required. Set SYNTH_API_KEY environment variable.[/red]")
55
+ resolved_session_id = session_id or os.getenv("SYNTH_SESSION_ID", "")
56
+ config = resolve_backend_config()
57
+ client = AgentSessionClient(base_url=config.base_url, api_key=config.api_key)
58
+
59
+ if resolved_session_id:
60
+ try:
61
+ session = await client.get(resolved_session_id)
62
+ except SessionNotFoundError:
63
+ click.echo("Session not found")
64
+ return
65
+ _render_session(session)
84
66
  return
85
- client = AgentSessionClient(f"{cfg.base_url}/api", cfg.api_key)
86
- try:
87
- session = await client.get(session_id_to_use)
88
-
89
- # Display session info
90
- table = Table(title=f"Session: {session.session_id[:16]}...", show_header=True, header_style="bold cyan")
91
- table.add_column("Property", style="cyan")
92
- table.add_column("Value", style="green")
93
-
94
- table.add_row("Session ID", session.session_id)
95
- table.add_row("Status", f"[bold]{session.status}[/bold]")
96
- table.add_row("Created", session.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
97
- if session.ended_at:
98
- table.add_row("Ended", session.ended_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
99
- if session.expires_at:
100
- table.add_row("Expires", session.expires_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
101
-
102
- console.print(table)
103
- console.print()
104
-
105
- # Display usage summary
106
- usage_table = Table(title="Usage Summary", show_header=True, header_style="bold cyan")
107
- usage_table.add_column("Metric", style="cyan")
108
- usage_table.add_column("Value", style="green")
109
-
110
- usage_table.add_row("Tokens", f"{session.usage.tokens:,}")
111
- usage_table.add_row("Cost (USD)", f"${session.usage.cost_usd:.4f}")
112
- usage_table.add_row("GPU Hours", f"{session.usage.gpu_hours:.4f}")
113
- usage_table.add_row("API Calls", f"{session.usage.api_calls:,}")
114
-
115
- console.print(usage_table)
116
- console.print()
117
-
118
- # Display limits
119
- if session.limits:
120
- limits_table = Table(title="Session Limits", show_header=True, header_style="bold cyan")
121
- limits_table.add_column("Type", style="cyan")
122
- limits_table.add_column("Limit", style="green")
123
- limits_table.add_column("Used", style="yellow")
124
- limits_table.add_column("Remaining", style="green")
125
- limits_table.add_column("Status", style="bold")
126
-
127
- for limit in session.limits:
128
- remaining = limit.remaining
129
- status = "[green]✓ OK[/green]" if remaining > 0 else "[red]✗ EXCEEDED[/red]"
130
- if limit.metric_type == "cost_usd":
131
- limits_table.add_row(
132
- "Cost (USD)",
133
- f"${limit.limit_value:.2f}",
134
- f"${limit.current_usage:.4f}",
135
- f"${remaining:.4f}",
136
- status
137
- )
138
- elif limit.metric_type == "tokens":
139
- limits_table.add_row(
140
- "Tokens",
141
- f"{limit.limit_value:,.0f}",
142
- f"{limit.current_usage:,.0f}",
143
- f"{remaining:,.0f}",
144
- status
145
- )
146
- elif limit.metric_type == "gpu_hours":
147
- limits_table.add_row(
148
- "GPU Hours",
149
- f"{limit.limit_value:.2f}",
150
- f"{limit.current_usage:.4f}",
151
- f"{remaining:.4f}",
152
- status
153
- )
154
- else:
155
- limits_table.add_row(
156
- limit.metric_type,
157
- str(limit.limit_value),
158
- str(limit.current_usage),
159
- str(remaining),
160
- status
161
- )
162
-
163
- console.print(limits_table)
164
-
165
- # Warn if any limit exceeded
166
- exceeded = [limit for limit in session.limits if limit.current_usage >= limit.limit_value]
167
- if exceeded:
168
- console.print()
169
- console.print("[red][bold]⚠ WARNING: Session limits exceeded![/bold][/red]")
170
- console.print("The session will reject new requests until limits are increased.")
171
- for limit in exceeded:
172
- console.print(f" - {limit.metric_type}: {limit.current_usage} >= {limit.limit_value}")
173
- else:
174
- console.print("[yellow]No limits configured for this session.[/yellow]")
175
-
176
- except SessionNotFoundError:
177
- console.print(f"[red]Session not found: {session_id_to_use}[/red]")
178
- except Exception as e:
179
- console.print(f"[red]Error fetching session: {e}[/red]")
180
-
67
+
68
+ sessions = await client.list(status="active")
69
+ if not sessions:
70
+ click.echo("No active session found")
71
+ return
72
+ _render_session(sessions[0])
73
+
181
74
  asyncio.run(_run())
182
75
 
76
+
77
+ __all__ = ["session_status_cmd"]
@@ -1,4 +1,4 @@
1
- """`synth status summary` command."""
1
+ """Status summary command."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -6,42 +6,34 @@ import asyncio
6
6
 
7
7
  import click
8
8
 
9
- from ..client import StatusAPIClient
10
- from ..errors import StatusAPIError
11
- from ..formatters import console, files_table, jobs_table, models_table
12
- from ..utils import common_options, resolve_context_config
13
-
14
-
15
- @click.command("summary", help="Show a condensed overview of recent jobs, models, and files.")
16
- @common_options()
17
- @click.option("--limit", default=5, show_default=True, type=int, help="Rows per section.")
18
- @click.pass_context
19
- def summary_command(
20
- ctx: click.Context,
21
- base_url: str | None,
22
- api_key: str | None,
23
- timeout: float,
24
- limit: int,
25
- ) -> None:
26
- cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
27
-
28
- async def _run() -> tuple[list[dict[str, object]], list[dict[str, object]], list[dict[str, object]]]:
29
- async with StatusAPIClient(cfg) as client:
9
+ from synth_ai.cli.commands.status.errors import StatusAPIError
10
+ from .config import StatusConfig
11
+ from .utils import StatusAPIClient
12
+
13
+
14
+ @click.command("summary")
15
+ def summary_command() -> None:
16
+ async def _run() -> None:
17
+ config = StatusConfig()
18
+ async with StatusAPIClient(config) as client:
30
19
  try:
31
- jobs = await client.list_jobs(limit=limit)
20
+ jobs = await client.list_jobs() # type: ignore[attr-defined]
32
21
  except StatusAPIError:
33
22
  jobs = []
34
23
  try:
35
- models = await client.list_models(limit=limit)
24
+ await client.list_models() # type: ignore[attr-defined]
36
25
  except StatusAPIError:
37
- models = []
26
+ pass
38
27
  try:
39
- files = await client.list_files(limit=limit)
28
+ await client.list_files() # type: ignore[attr-defined]
40
29
  except StatusAPIError:
41
- files = []
42
- return jobs, models, files
30
+ pass
31
+
32
+ click.echo("Training Jobs")
33
+ for job in jobs:
34
+ click.echo(str(job))
35
+
36
+ asyncio.run(_run())
37
+
43
38
 
44
- jobs, models, files = asyncio.run(_run())
45
- console.print(jobs_table(jobs[:limit]))
46
- console.print(models_table(models[:limit]))
47
- console.print(files_table(files[:limit]))
39
+ __all__ = ["summary_command"]
@@ -0,0 +1,41 @@
1
+ """Shared helpers for status subcommands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import click
8
+ import json
9
+
10
+
11
+ class StatusAPIClient:
12
+ def __init__(self, config: Any) -> None:
13
+ self.config = config
14
+
15
+ async def __aenter__(self) -> "StatusAPIClient":
16
+ return self
17
+
18
+ async def __aexit__(self, exc_type, exc, tb) -> None:
19
+ return None
20
+
21
+ async def list_jobs(self, **_: Any) -> list[dict[str, Any]]:
22
+ return []
23
+
24
+ async def get_job_events(self, *_: Any, **__: Any) -> list[dict[str, Any]]:
25
+ return []
26
+
27
+ async def get_file(self, *_: Any, **__: Any) -> dict[str, Any]:
28
+ return {}
29
+
30
+ async def list_models(self, **_: Any) -> list[dict[str, Any]]:
31
+ return []
32
+
33
+ async def list_job_runs(self, *_: Any, **__: Any) -> list[dict[str, Any]]:
34
+ return []
35
+
36
+ async def list_files(self, **_: Any) -> list[dict[str, Any]]:
37
+ return []
38
+
39
+
40
+ def print_json(data: Any) -> None:
41
+ click.echo(json.dumps(data))