agentstack-cli 0.4.2rc1__tar.gz → 0.4.2rc3__tar.gz
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.
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/PKG-INFO +1 -1
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/pyproject.toml +1 -1
- agentstack_cli-0.4.2rc3/src/agentstack_cli/__init__.py +116 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/api.py +26 -12
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/async_typer.py +2 -1
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/agent.py +95 -69
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/build.py +14 -1
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/mcp.py +12 -3
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/model.py +27 -2
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/platform/wsl_driver.py +7 -2
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/server.py +1 -1
- agentstack_cli-0.4.2rc3/src/agentstack_cli/data/helm-chart.tgz +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/utils.py +34 -1
- agentstack_cli-0.4.2rc1/src/agentstack_cli/__init__.py +0 -77
- agentstack_cli-0.4.2rc1/src/agentstack_cli/data/helm-chart.tgz +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/README.md +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/auth_manager.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/__init__.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/platform/__init__.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/platform/base_driver.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/platform/istio.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/platform/lima_driver.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/self.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/configuration.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/console.py +0 -0
- {agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/data/.gitignore +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import typing
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
import agentstack_cli.commands.agent
|
|
11
|
+
import agentstack_cli.commands.build
|
|
12
|
+
import agentstack_cli.commands.mcp
|
|
13
|
+
import agentstack_cli.commands.model
|
|
14
|
+
import agentstack_cli.commands.platform
|
|
15
|
+
import agentstack_cli.commands.self
|
|
16
|
+
import agentstack_cli.commands.server
|
|
17
|
+
from agentstack_cli.async_typer import AliasGroup, AsyncTyper
|
|
18
|
+
from agentstack_cli.configuration import Configuration
|
|
19
|
+
|
|
20
|
+
logging.basicConfig(level=logging.INFO if Configuration().debug else logging.FATAL)
|
|
21
|
+
logging.getLogger("httpx").setLevel(logging.WARNING) # not sure why this is necessary
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RootHelpGroup(AliasGroup):
|
|
25
|
+
def get_help(self, ctx):
|
|
26
|
+
return """\
|
|
27
|
+
Usage: agentstack [OPTIONS] COMMAND [ARGS]...
|
|
28
|
+
|
|
29
|
+
╭─ Getting Started ──────────────────────────────────────────────────────────╮
|
|
30
|
+
│ ui Launch the web interface │
|
|
31
|
+
│ list View all available agents │
|
|
32
|
+
│ run Run an agent interactively │
|
|
33
|
+
╰────────────────────────────────────────────────────────────────────────────╯
|
|
34
|
+
|
|
35
|
+
╭─ Agent Management ─────────────────────────────────────────────────────────╮
|
|
36
|
+
│ add Install an agent (Docker, GitHub, local) │
|
|
37
|
+
│ remove Uninstall an agent │
|
|
38
|
+
│ info Show agent details │
|
|
39
|
+
│ logs Stream agent execution logs │
|
|
40
|
+
│ build Build an agent container image │
|
|
41
|
+
│ env Manage agent environment variables │
|
|
42
|
+
│ server-side-build [EXPERIMENTAL] Build agents remotely │
|
|
43
|
+
╰────────────────────────────────────────────────────────────────────────────╯
|
|
44
|
+
|
|
45
|
+
╭─ Platform & Configuration ─────────────────────────────────────────────────╮
|
|
46
|
+
│ model Configure 15+ LLM providers │
|
|
47
|
+
│ platform Start, stop, or delete local platform │
|
|
48
|
+
│ server Connect to remote Agent Stack servers │
|
|
49
|
+
│ self version Show Agent Stack CLI and Platform version │
|
|
50
|
+
│ self upgrade Upgrade Agent Stack CLI and Platform │
|
|
51
|
+
│ self uninstall Uninstall Agent Stack CLI and Platform │
|
|
52
|
+
╰────────────────────────────────────────────────────────────────────────────╯
|
|
53
|
+
|
|
54
|
+
╭─ Options ──────────────────────────────────────────────────────────────────╮
|
|
55
|
+
│ --help Show this help message │
|
|
56
|
+
│ --show-completion Show tab completion script │
|
|
57
|
+
│ --install-completion Enable tab completion for commands │
|
|
58
|
+
╰────────────────────────────────────────────────────────────────────────────╯
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
app = AsyncTyper(no_args_is_help=True, cls=RootHelpGroup)
|
|
63
|
+
app.add_typer(agentstack_cli.commands.model.app, name="model", no_args_is_help=True, help="Manage model providers.")
|
|
64
|
+
app.add_typer(agentstack_cli.commands.agent.app, name="agent", no_args_is_help=True, help="Manage agents.")
|
|
65
|
+
app.add_typer(
|
|
66
|
+
agentstack_cli.commands.platform.app, name="platform", no_args_is_help=True, help="Manage Agent Stack platform."
|
|
67
|
+
)
|
|
68
|
+
app.add_typer(
|
|
69
|
+
agentstack_cli.commands.mcp.app, name="mcp", no_args_is_help=True, help="Manage MCP servers and toolkits."
|
|
70
|
+
)
|
|
71
|
+
app.add_typer(agentstack_cli.commands.build.app, name="", no_args_is_help=True, help="Build agent images.")
|
|
72
|
+
app.add_typer(
|
|
73
|
+
agentstack_cli.commands.server.app,
|
|
74
|
+
name="server",
|
|
75
|
+
no_args_is_help=True,
|
|
76
|
+
help="Manage Agent Stack servers and authentication.",
|
|
77
|
+
)
|
|
78
|
+
app.add_typer(
|
|
79
|
+
agentstack_cli.commands.self.app,
|
|
80
|
+
name="self",
|
|
81
|
+
no_args_is_help=True,
|
|
82
|
+
help="Manage Agent Stack installation.",
|
|
83
|
+
hidden=True,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
agent_alias = deepcopy(agentstack_cli.commands.agent.app)
|
|
88
|
+
for cmd in agent_alias.registered_commands:
|
|
89
|
+
cmd.rich_help_panel = "Agent commands"
|
|
90
|
+
|
|
91
|
+
app.add_typer(agent_alias, name="", no_args_is_help=True)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.command("version")
|
|
95
|
+
async def version(verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False):
|
|
96
|
+
"""Print version of the Agent Stack CLI."""
|
|
97
|
+
import agentstack_cli.commands.self
|
|
98
|
+
|
|
99
|
+
await agentstack_cli.commands.self.version(verbose=verbose)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@app.command("ui")
|
|
103
|
+
async def ui():
|
|
104
|
+
"""Launch the graphical interface."""
|
|
105
|
+
import webbrowser
|
|
106
|
+
|
|
107
|
+
import agentstack_cli.commands.model
|
|
108
|
+
|
|
109
|
+
await agentstack_cli.commands.model.ensure_llm_provider()
|
|
110
|
+
webbrowser.open(
|
|
111
|
+
"http://localhost:8334"
|
|
112
|
+
) # TODO: This always opens the local UI, how to open the UI of a logged in server instead?
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
app()
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
3
|
+
import json
|
|
4
4
|
import re
|
|
5
5
|
import urllib
|
|
6
6
|
import urllib.parse
|
|
7
7
|
from collections.abc import AsyncIterator
|
|
8
8
|
from contextlib import asynccontextmanager
|
|
9
9
|
from datetime import timedelta
|
|
10
|
+
from textwrap import indent
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
import httpx
|
|
13
14
|
import openai
|
|
14
|
-
from a2a.client import Client, ClientConfig, ClientFactory
|
|
15
|
+
from a2a.client import A2AClientHTTPError, Client, ClientConfig, ClientFactory
|
|
15
16
|
from a2a.types import AgentCard
|
|
16
17
|
from httpx import HTTPStatusError
|
|
17
18
|
from httpx._types import RequestFiles
|
|
@@ -103,16 +104,29 @@ async def api_stream(
|
|
|
103
104
|
|
|
104
105
|
@asynccontextmanager
|
|
105
106
|
async def a2a_client(agent_card: AgentCard, use_auth: bool = True) -> AsyncIterator[Client]:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
try:
|
|
108
|
+
async with httpx.AsyncClient(
|
|
109
|
+
headers=(
|
|
110
|
+
{"Authorization": f"Bearer {token}"}
|
|
111
|
+
if use_auth and (token := config.auth_manager.load_auth_token())
|
|
112
|
+
else {}
|
|
113
|
+
),
|
|
114
|
+
follow_redirects=True,
|
|
115
|
+
timeout=timedelta(hours=1).total_seconds(),
|
|
116
|
+
) as httpx_client:
|
|
117
|
+
yield ClientFactory(ClientConfig(httpx_client=httpx_client, use_client_preference=True)).create(
|
|
118
|
+
card=agent_card
|
|
119
|
+
)
|
|
120
|
+
except A2AClientHTTPError as ex:
|
|
121
|
+
card_data = json.dumps(
|
|
122
|
+
agent_card.model_dump(include={"url", "additional_interfaces", "preferred_transport"}), indent=2
|
|
123
|
+
)
|
|
124
|
+
raise RuntimeError(
|
|
125
|
+
f"The agent is not reachable, please check that the agent card is configured properly.\n"
|
|
126
|
+
f"Agent connection info:\n{indent(card_data, prefix=' ')}\n"
|
|
127
|
+
"Full Error:\n"
|
|
128
|
+
f"{indent(str(ex), prefix=' ')}"
|
|
129
|
+
) from ex
|
|
116
130
|
|
|
117
131
|
|
|
118
132
|
@asynccontextmanager
|
|
@@ -67,7 +67,8 @@ class AliasGroup(TyperGroup):
|
|
|
67
67
|
|
|
68
68
|
class AsyncTyper(typer.Typer):
|
|
69
69
|
def __init__(self, *args, **kwargs):
|
|
70
|
-
|
|
70
|
+
kwargs["cls"] = kwargs.get("cls", AliasGroup)
|
|
71
|
+
super().__init__(*args, **kwargs)
|
|
71
72
|
|
|
72
73
|
def command(self, *args, **kwargs):
|
|
73
74
|
parent_decorator = super().command(*args, **kwargs)
|
|
@@ -36,6 +36,8 @@ from agentstack_sdk.a2a.extensions import (
|
|
|
36
36
|
EmbeddingFulfillment,
|
|
37
37
|
EmbeddingServiceExtensionClient,
|
|
38
38
|
EmbeddingServiceExtensionSpec,
|
|
39
|
+
FormRequestExtensionSpec,
|
|
40
|
+
FormServiceExtensionSpec,
|
|
39
41
|
LLMFulfillment,
|
|
40
42
|
LLMServiceExtensionClient,
|
|
41
43
|
LLMServiceExtensionSpec,
|
|
@@ -44,17 +46,18 @@ from agentstack_sdk.a2a.extensions import (
|
|
|
44
46
|
TrajectoryExtensionClient,
|
|
45
47
|
TrajectoryExtensionSpec,
|
|
46
48
|
)
|
|
47
|
-
from agentstack_sdk.a2a.extensions.
|
|
49
|
+
from agentstack_sdk.a2a.extensions.common.form import (
|
|
48
50
|
CheckboxField,
|
|
49
51
|
CheckboxFieldValue,
|
|
50
52
|
DateField,
|
|
51
53
|
DateFieldValue,
|
|
52
|
-
FormExtensionSpec,
|
|
53
54
|
FormFieldValue,
|
|
54
55
|
FormRender,
|
|
55
56
|
FormResponse,
|
|
56
57
|
MultiSelectField,
|
|
57
58
|
MultiSelectFieldValue,
|
|
59
|
+
SingleSelectField,
|
|
60
|
+
SingleSelectFieldValue,
|
|
58
61
|
TextField,
|
|
59
62
|
TextFieldValue,
|
|
60
63
|
)
|
|
@@ -84,6 +87,7 @@ if sys.platform != "win32":
|
|
|
84
87
|
from collections.abc import Callable
|
|
85
88
|
from pathlib import Path
|
|
86
89
|
from typing import Any
|
|
90
|
+
from urllib.parse import urlparse
|
|
87
91
|
|
|
88
92
|
import jsonschema
|
|
89
93
|
import rich.json
|
|
@@ -94,6 +98,8 @@ from rich.table import Column
|
|
|
94
98
|
from agentstack_cli.api import a2a_client
|
|
95
99
|
from agentstack_cli.async_typer import AsyncTyper, console, create_table, err_console
|
|
96
100
|
from agentstack_cli.utils import (
|
|
101
|
+
announce_server_action,
|
|
102
|
+
confirm_server_action,
|
|
97
103
|
generate_schema_example,
|
|
98
104
|
parse_env_var,
|
|
99
105
|
print_log,
|
|
@@ -153,39 +159,44 @@ async def add_agent(
|
|
|
153
159
|
dockerfile: typing.Annotated[str | None, typer.Option(help="Use custom dockerfile path")] = None,
|
|
154
160
|
vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
|
|
155
161
|
verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
|
|
162
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
156
163
|
) -> None:
|
|
157
164
|
"""Install discovered agent or add public docker image or github repository [aliases: install]"""
|
|
165
|
+
url = announce_server_action(f"Installing agent '{location}' for")
|
|
166
|
+
await confirm_server_action("Proceed with installing this agent on", url=url, yes=yes)
|
|
158
167
|
agent_card = None
|
|
159
|
-
# Try extracting manifest locally for local images
|
|
160
168
|
with verbosity(verbose):
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
169
|
+
if (
|
|
170
|
+
process := await run_command(
|
|
171
|
+
["docker", "inspect", location], check=False, message="Inspecting docker images"
|
|
172
|
+
)
|
|
173
|
+
).returncode == 0:
|
|
174
|
+
console.success(f"Found local image [bold]{location}[/bold]")
|
|
175
|
+
manifest = base64.b64decode(
|
|
176
|
+
json.loads(process.stdout)[0]["Config"]["Labels"]["beeai.dev.agent.json"]
|
|
177
|
+
).decode()
|
|
178
|
+
agent_card = json.loads(manifest)
|
|
179
|
+
elif (
|
|
180
|
+
Path(location).expanduser().exists()
|
|
181
|
+
or location.startswith("git@")
|
|
182
|
+
or location.startswith("github.com/")
|
|
183
|
+
or location.startswith("www.github.com/")
|
|
184
|
+
or location.endswith(".git")
|
|
185
|
+
or ((u := urlparse(location)).scheme.startswith("http") and u.netloc.endswith("github.com"))
|
|
186
|
+
or u.scheme in {"ssh", "git", "git+ssh"}
|
|
187
|
+
):
|
|
188
|
+
console.info(f"Assuming build context, attempting to build agent from [bold]{location}[/bold]")
|
|
189
|
+
location, agent_card = await build(location, dockerfile, tag=None, vm_name=vm_name, import_image=True)
|
|
190
|
+
else:
|
|
191
|
+
console.info(f"Assuming public docker image, attempting to pull {location}")
|
|
165
192
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
).decode()
|
|
174
|
-
agent_card = json.loads(manifest)
|
|
175
|
-
# If all build and inspect succeeded, use the local image, else use the original; maybe it exists remotely
|
|
176
|
-
except CalledProcessError as e:
|
|
177
|
-
errors.append(e)
|
|
178
|
-
console.print("Attempting to use remote image...")
|
|
179
|
-
try:
|
|
180
|
-
with status("Registering agent to platform"):
|
|
181
|
-
async with configuration.use_platform_client():
|
|
182
|
-
await Provider.create(
|
|
183
|
-
location=location,
|
|
184
|
-
agent_card=AgentCard.model_validate(agent_card) if agent_card else None,
|
|
185
|
-
)
|
|
186
|
-
console.print("Registering agent to platform [[green]DONE[/green]]")
|
|
187
|
-
except Exception as e:
|
|
188
|
-
raise ExceptionGroup("Error occured", [*errors, e]) from e
|
|
193
|
+
with status("Registering agent to platform"):
|
|
194
|
+
async with configuration.use_platform_client():
|
|
195
|
+
await Provider.create(
|
|
196
|
+
location=location,
|
|
197
|
+
agent_card=AgentCard.model_validate(agent_card) if agent_card else None,
|
|
198
|
+
)
|
|
199
|
+
console.success(f"Agent [bold]{location}[/bold] added to platform")
|
|
189
200
|
await list_agents()
|
|
190
201
|
|
|
191
202
|
|
|
@@ -207,8 +218,11 @@ async def uninstall_agent(
|
|
|
207
218
|
search_path: typing.Annotated[
|
|
208
219
|
str, typer.Argument(..., help="Short ID, agent name or part of the provider location")
|
|
209
220
|
],
|
|
221
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
210
222
|
) -> None:
|
|
211
223
|
"""Remove agent"""
|
|
224
|
+
url = announce_server_action(f"Removing agent '{search_path}' from")
|
|
225
|
+
await confirm_server_action("Proceed with removing this agent from", url=url, yes=yes)
|
|
212
226
|
with console.status("Uninstalling agent (may take a few minutes)...", spinner="dots"):
|
|
213
227
|
async with configuration.use_platform_client():
|
|
214
228
|
remove_provider = select_provider(search_path, await Provider.list()).id
|
|
@@ -223,6 +237,7 @@ async def stream_logs(
|
|
|
223
237
|
],
|
|
224
238
|
):
|
|
225
239
|
"""Stream agent provider logs"""
|
|
240
|
+
announce_server_action(f"Streaming logs for '{search_path}' from")
|
|
226
241
|
async with configuration.use_platform_client():
|
|
227
242
|
provider = select_provider(search_path, await Provider.list()).id
|
|
228
243
|
async for message in Provider.stream_logs(provider):
|
|
@@ -245,6 +260,15 @@ async def _ask_form_questions(form_render: FormRender) -> FormResponse:
|
|
|
245
260
|
validate=EmptyInputValidator() if field.required else None,
|
|
246
261
|
).execute_async()
|
|
247
262
|
form_values[field.id] = TextFieldValue(value=answer)
|
|
263
|
+
elif isinstance(field, SingleSelectField):
|
|
264
|
+
choices = [Choice(value=opt.id, name=opt.label) for opt in field.options]
|
|
265
|
+
answer = await inquirer.fuzzy( # pyright: ignore[reportPrivateImportUsage]
|
|
266
|
+
message=field.label + ":",
|
|
267
|
+
choices=choices,
|
|
268
|
+
default=field.default_value,
|
|
269
|
+
validate=EmptyInputValidator() if field.required else None,
|
|
270
|
+
).execute_async()
|
|
271
|
+
form_values[field.id] = SingleSelectFieldValue(value=answer)
|
|
248
272
|
elif isinstance(field, MultiSelectField):
|
|
249
273
|
choices = [Choice(value=opt.id, name=opt.label) for opt in field.options]
|
|
250
274
|
answer = await inquirer.checkbox( # pyright: ignore[reportPrivateImportUsage]
|
|
@@ -254,6 +278,7 @@ async def _ask_form_questions(form_render: FormRender) -> FormResponse:
|
|
|
254
278
|
validate=EmptyInputValidator() if field.required else None,
|
|
255
279
|
).execute_async()
|
|
256
280
|
form_values[field.id] = MultiSelectFieldValue(value=answer)
|
|
281
|
+
|
|
257
282
|
elif isinstance(field, DateField):
|
|
258
283
|
year = await inquirer.text( # pyright: ignore[reportPrivateImportUsage]
|
|
259
284
|
message=f"{field.label} (year):",
|
|
@@ -295,7 +320,7 @@ async def _ask_form_questions(form_render: FormRender) -> FormResponse:
|
|
|
295
320
|
).execute_async()
|
|
296
321
|
form_values[field.id] = CheckboxFieldValue(value=answer)
|
|
297
322
|
console.print()
|
|
298
|
-
return FormResponse(
|
|
323
|
+
return FormResponse(values=form_values)
|
|
299
324
|
|
|
300
325
|
|
|
301
326
|
async def _run_agent(
|
|
@@ -360,7 +385,11 @@ async def _run_agent(
|
|
|
360
385
|
else {}
|
|
361
386
|
)
|
|
362
387
|
| (
|
|
363
|
-
{
|
|
388
|
+
{
|
|
389
|
+
FormServiceExtensionSpec.URI: {
|
|
390
|
+
"form_fulfillments": {"initial_form": typing.cast(FormResponse, input).model_dump(mode="json")}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
364
393
|
if isinstance(input, FormResponse)
|
|
365
394
|
else {}
|
|
366
395
|
)
|
|
@@ -446,7 +475,7 @@ async def _run_agent(
|
|
|
446
475
|
raise ValueError("Agent requires input but no input handler provided")
|
|
447
476
|
|
|
448
477
|
if form_metadata := (
|
|
449
|
-
message.metadata.get(
|
|
478
|
+
message.metadata.get(FormRequestExtensionSpec.URI) if message and message.metadata else None
|
|
450
479
|
):
|
|
451
480
|
stream = client.send_message(
|
|
452
481
|
Message(
|
|
@@ -456,7 +485,7 @@ async def _run_agent(
|
|
|
456
485
|
task_id=task_id,
|
|
457
486
|
context_id=context_token.context_id,
|
|
458
487
|
metadata={
|
|
459
|
-
|
|
488
|
+
FormRequestExtensionSpec.URI: (
|
|
460
489
|
await _ask_form_questions(FormRender.model_validate(form_metadata))
|
|
461
490
|
).model_dump(mode="json")
|
|
462
491
|
},
|
|
@@ -737,6 +766,7 @@ async def run_agent(
|
|
|
737
766
|
] = None,
|
|
738
767
|
) -> None:
|
|
739
768
|
"""Run an agent."""
|
|
769
|
+
announce_server_action(f"Running agent '{search_path}' on")
|
|
740
770
|
async with configuration.use_platform_client():
|
|
741
771
|
providers = await Provider.list()
|
|
742
772
|
await ensure_llm_provider()
|
|
@@ -778,9 +808,9 @@ async def run_agent(
|
|
|
778
808
|
|
|
779
809
|
initial_form_render = next(
|
|
780
810
|
(
|
|
781
|
-
FormRender.model_validate(ext.params)
|
|
811
|
+
FormRender.model_validate(ext.params["form_demands"]["initial_form"])
|
|
782
812
|
for ext in agent.capabilities.extensions or ()
|
|
783
|
-
if ext.uri ==
|
|
813
|
+
if ext.uri == FormServiceExtensionSpec.URI and ext.params
|
|
784
814
|
),
|
|
785
815
|
None,
|
|
786
816
|
)
|
|
@@ -827,19 +857,13 @@ async def run_agent(
|
|
|
827
857
|
)
|
|
828
858
|
|
|
829
859
|
|
|
830
|
-
def render_enum(value: str, colors: dict[str, str]) -> str:
|
|
831
|
-
if color := colors.get(value):
|
|
832
|
-
return f"[{color}]{value}[/{color}]"
|
|
833
|
-
return value
|
|
834
|
-
|
|
835
|
-
|
|
836
860
|
@app.command("list")
|
|
837
861
|
async def list_agents():
|
|
838
862
|
"""List agents."""
|
|
863
|
+
announce_server_action("Listing agents on")
|
|
839
864
|
async with configuration.use_platform_client():
|
|
840
865
|
providers = await Provider.list()
|
|
841
866
|
max_provider_len = max(len(ProviderUtils.short_location(p)) for p in providers) if providers else 0
|
|
842
|
-
max_error_len = max(len(ProviderUtils.last_error(p) or "") for p in providers) if providers else 0
|
|
843
867
|
|
|
844
868
|
def _sort_fn(provider: Provider):
|
|
845
869
|
state = {"missing": "1"}
|
|
@@ -852,39 +876,32 @@ async def list_agents():
|
|
|
852
876
|
with create_table(
|
|
853
877
|
Column("Short ID", style="yellow"),
|
|
854
878
|
Column("Name", style="yellow"),
|
|
855
|
-
Column("State"
|
|
856
|
-
Column("Description", ratio=2),
|
|
857
|
-
Column("Interaction"),
|
|
879
|
+
Column("State"),
|
|
858
880
|
Column("Location", max_width=min(max(max_provider_len, len("Location")), 70)),
|
|
859
|
-
Column("
|
|
860
|
-
Column("Last Error", max_width=min(max(max_error_len, len("Last Error")), 50)),
|
|
881
|
+
Column("Info", ratio=2),
|
|
861
882
|
no_wrap=True,
|
|
862
883
|
) as table:
|
|
863
884
|
for provider in sorted(providers, key=_sort_fn):
|
|
864
|
-
state = None
|
|
865
|
-
missing_env = None
|
|
866
|
-
state = provider.state
|
|
867
|
-
missing_env = ",".join(var.name for var in provider.missing_configuration)
|
|
868
885
|
table.add_row(
|
|
869
886
|
provider.id[:8],
|
|
870
887
|
provider.agent_card.name,
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
"error": "red",
|
|
881
|
-
},
|
|
882
|
-
),
|
|
883
|
-
(provider.agent_card.description or "<none>").replace("\n", " "),
|
|
884
|
-
(ProviderUtils.detail(provider) or {}).get("interaction_mode") or "<none>",
|
|
888
|
+
{
|
|
889
|
+
"running": "[green]▶ running[/green]",
|
|
890
|
+
"online": "[green]● connected[/green]",
|
|
891
|
+
"ready": "[green]● idle[/green]",
|
|
892
|
+
"starting": "[yellow]✱ starting[/yellow]",
|
|
893
|
+
"missing": "[bright_black]○ not started[/bright_black]",
|
|
894
|
+
"offline": "[bright_black]○ disconnected[/bright_black]",
|
|
895
|
+
"error": "[red]✘ error[/red]",
|
|
896
|
+
}.get(provider.state, provider.state or "<unknown>"),
|
|
885
897
|
ProviderUtils.short_location(provider) or "<none>",
|
|
886
|
-
|
|
887
|
-
|
|
898
|
+
(
|
|
899
|
+
f"Error: {error}"
|
|
900
|
+
if provider.state == "error" and (error := ProviderUtils.last_error(provider))
|
|
901
|
+
else f"Missing ENV: {{{', '.join(missing_env)}}}"
|
|
902
|
+
if (missing_env := [var.name for var in provider.missing_configuration])
|
|
903
|
+
else "<none>"
|
|
904
|
+
),
|
|
888
905
|
)
|
|
889
906
|
console.print(table)
|
|
890
907
|
|
|
@@ -931,7 +948,9 @@ async def agent_detail(
|
|
|
931
948
|
],
|
|
932
949
|
):
|
|
933
950
|
"""Show agent details."""
|
|
934
|
-
|
|
951
|
+
announce_server_action(f"Showing agent details for '{search_path}' on")
|
|
952
|
+
async with configuration.use_platform_client():
|
|
953
|
+
provider = select_provider(search_path, await Provider.list())
|
|
935
954
|
agent = provider.agent_card
|
|
936
955
|
|
|
937
956
|
basic_info = f"# {agent.name}\n{agent.description}"
|
|
@@ -977,8 +996,11 @@ async def add_env(
|
|
|
977
996
|
str, typer.Argument(..., help="Short ID, agent name or part of the provider location")
|
|
978
997
|
],
|
|
979
998
|
env: typing.Annotated[list[str], typer.Argument(help="Environment variables to pass to agent")],
|
|
999
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
980
1000
|
) -> None:
|
|
981
1001
|
"""Store environment variables"""
|
|
1002
|
+
url = announce_server_action(f"Adding environment variables for '{search_path}' on")
|
|
1003
|
+
await confirm_server_action("Apply these environment variable changes on", url=url, yes=yes)
|
|
982
1004
|
env_vars = dict(parse_env_var(var) for var in env)
|
|
983
1005
|
async with configuration.use_platform_client():
|
|
984
1006
|
provider = select_provider(search_path, await Provider.list())
|
|
@@ -993,6 +1015,7 @@ async def list_env(
|
|
|
993
1015
|
],
|
|
994
1016
|
):
|
|
995
1017
|
"""List stored environment variables"""
|
|
1018
|
+
announce_server_action(f"Listing environment variables for '{search_path}' on")
|
|
996
1019
|
async with configuration.use_platform_client():
|
|
997
1020
|
provider = select_provider(search_path, await Provider.list())
|
|
998
1021
|
await _list_env(provider)
|
|
@@ -1004,7 +1027,10 @@ async def remove_env(
|
|
|
1004
1027
|
str, typer.Argument(..., help="Short ID, agent name or part of the provider location")
|
|
1005
1028
|
],
|
|
1006
1029
|
env: typing.Annotated[list[str], typer.Argument(help="Environment variable(s) to remove")],
|
|
1030
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
1007
1031
|
):
|
|
1032
|
+
url = announce_server_action(f"Removing environment variables from '{search_path}' on")
|
|
1033
|
+
await confirm_server_action("Remove the selected environment variables on", url=url, yes=yes)
|
|
1008
1034
|
async with configuration.use_platform_client():
|
|
1009
1035
|
provider = select_provider(search_path, await Provider.list())
|
|
1010
1036
|
await provider.update_variables(variables=dict.fromkeys(env))
|
|
@@ -24,7 +24,16 @@ from tenacity import AsyncRetrying, retry_if_exception_type, stop_after_delay, w
|
|
|
24
24
|
|
|
25
25
|
from agentstack_cli.async_typer import AsyncTyper
|
|
26
26
|
from agentstack_cli.console import console
|
|
27
|
-
from agentstack_cli.utils import
|
|
27
|
+
from agentstack_cli.utils import (
|
|
28
|
+
announce_server_action,
|
|
29
|
+
capture_output,
|
|
30
|
+
confirm_server_action,
|
|
31
|
+
extract_messages,
|
|
32
|
+
print_log,
|
|
33
|
+
run_command,
|
|
34
|
+
status,
|
|
35
|
+
verbosity,
|
|
36
|
+
)
|
|
28
37
|
|
|
29
38
|
|
|
30
39
|
async def find_free_port():
|
|
@@ -145,6 +154,7 @@ async def server_side_build_experimental(
|
|
|
145
154
|
str | None, typer.Option(help="Short ID, agent name or part of the provider location")
|
|
146
155
|
] = None,
|
|
147
156
|
add: typing.Annotated[bool, typer.Option(help="Add agent to the platform after build")] = False,
|
|
157
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
148
158
|
):
|
|
149
159
|
"""EXPERIMENTAL: Build agent from github repository in the platform."""
|
|
150
160
|
from agentstack_cli.commands.agent import select_provider
|
|
@@ -157,6 +167,9 @@ async def server_side_build_experimental(
|
|
|
157
167
|
if dockerfile:
|
|
158
168
|
build_configuration = BuildConfiguration(dockerfile_path=Path(dockerfile))
|
|
159
169
|
|
|
170
|
+
url = announce_server_action(f"Starting server-side build for '{github_url}' on")
|
|
171
|
+
await confirm_server_action("Proceed with building this agent on", url=url, yes=yes)
|
|
172
|
+
|
|
160
173
|
async with Configuration().use_platform_client():
|
|
161
174
|
on_complete = None
|
|
162
175
|
if replace:
|
|
@@ -9,9 +9,7 @@ from rich.table import Column
|
|
|
9
9
|
|
|
10
10
|
from agentstack_cli.api import api_request
|
|
11
11
|
from agentstack_cli.async_typer import AsyncTyper, console, create_table
|
|
12
|
-
from agentstack_cli.utils import
|
|
13
|
-
status,
|
|
14
|
-
)
|
|
12
|
+
from agentstack_cli.utils import announce_server_action, confirm_server_action, status
|
|
15
13
|
|
|
16
14
|
app = AsyncTyper()
|
|
17
15
|
|
|
@@ -28,9 +26,12 @@ async def add_provider(
|
|
|
28
26
|
transport: typing.Annotated[
|
|
29
27
|
Transport, typer.Argument(help="Transport the MCP server uses")
|
|
30
28
|
] = Transport.STREAMABLE_HTTP,
|
|
29
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
31
30
|
) -> None:
|
|
32
31
|
"""Install discovered MCP server."""
|
|
33
32
|
|
|
33
|
+
url = announce_server_action(f"Registering MCP server '{name}' on")
|
|
34
|
+
await confirm_server_action("Proceed with registering this MCP server on", url=url, yes=yes)
|
|
34
35
|
with status("Registering server to platform"):
|
|
35
36
|
await api_request(
|
|
36
37
|
"POST", "mcp/providers", json={"name": name, "location": location, "transport": transport.value}
|
|
@@ -43,6 +44,7 @@ async def add_provider(
|
|
|
43
44
|
async def list_providers():
|
|
44
45
|
"""List MCP servers."""
|
|
45
46
|
|
|
47
|
+
announce_server_action("Listing MCP servers on")
|
|
46
48
|
providers = await api_request("GET", "mcp/providers")
|
|
47
49
|
assert providers
|
|
48
50
|
with create_table(
|
|
@@ -61,8 +63,11 @@ async def list_providers():
|
|
|
61
63
|
@app.command("remove | uninstall | rm | delete")
|
|
62
64
|
async def uninstall_provider(
|
|
63
65
|
name: typing.Annotated[str, typer.Argument(help="Name of the MCP provider to remove")],
|
|
66
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
64
67
|
) -> None:
|
|
65
68
|
"""Remove MCP server."""
|
|
69
|
+
url = announce_server_action(f"Removing MCP server '{name}' from")
|
|
70
|
+
await confirm_server_action("Proceed with removing this MCP server from", url=url, yes=yes)
|
|
66
71
|
provider = await _get_provider_by_name(name)
|
|
67
72
|
if provider:
|
|
68
73
|
await api_request("delete", f"mcp/providers/{provider['id']}")
|
|
@@ -79,6 +84,7 @@ app.add_typer(tool_app, name="tool", no_args_is_help=True, help="Inspect tools."
|
|
|
79
84
|
async def list_tools() -> None:
|
|
80
85
|
"""List tools."""
|
|
81
86
|
|
|
87
|
+
announce_server_action("Listing MCP tools on")
|
|
82
88
|
tools = await api_request("GET", "mcp/tools")
|
|
83
89
|
assert tools
|
|
84
90
|
with create_table(
|
|
@@ -99,9 +105,12 @@ app.add_typer(toolkit_app, name="toolkit", no_args_is_help=True, help="Create to
|
|
|
99
105
|
@toolkit_app.command("create")
|
|
100
106
|
async def toolkit(
|
|
101
107
|
tools: typing.Annotated[list[str], typer.Argument(help="Tools to put in the toolkit")],
|
|
108
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
102
109
|
) -> None:
|
|
103
110
|
"""Create a toolkit."""
|
|
104
111
|
|
|
112
|
+
url = announce_server_action("Creating MCP toolkit on")
|
|
113
|
+
await confirm_server_action("Proceed with creating this MCP toolkit on", url=url, yes=yes)
|
|
105
114
|
api_tools = await _get_tools_by_names(tools)
|
|
106
115
|
assert api_tools
|
|
107
116
|
toolkit = await api_request("POST", "mcp/toolkits", json={"tools": [tool["id"] for tool in api_tools]})
|
|
@@ -25,7 +25,7 @@ from rich.table import Column
|
|
|
25
25
|
from agentstack_cli.api import openai_client
|
|
26
26
|
from agentstack_cli.async_typer import AsyncTyper, console, create_table
|
|
27
27
|
from agentstack_cli.configuration import Configuration
|
|
28
|
-
from agentstack_cli.utils import run_command, verbosity
|
|
28
|
+
from agentstack_cli.utils import announce_server_action, confirm_server_action, run_command, verbosity
|
|
29
29
|
|
|
30
30
|
app = AsyncTyper()
|
|
31
31
|
configuration = Configuration()
|
|
@@ -378,6 +378,7 @@ async def _select_default_model(capability: ModelCapability) -> str | None:
|
|
|
378
378
|
|
|
379
379
|
@app.command("list")
|
|
380
380
|
async def list_models():
|
|
381
|
+
announce_server_action("Listing models on")
|
|
381
382
|
async with configuration.use_platform_client():
|
|
382
383
|
config = await SystemConfiguration.get()
|
|
383
384
|
async with openai_client() as client:
|
|
@@ -415,6 +416,7 @@ async def setup(
|
|
|
415
416
|
verbose: typing.Annotated[bool, typer.Option("-v")] = False,
|
|
416
417
|
):
|
|
417
418
|
"""Interactive setup for LLM and embedding provider environment variables"""
|
|
419
|
+
announce_server_action("Configuring model providers for")
|
|
418
420
|
|
|
419
421
|
with verbosity(verbose):
|
|
420
422
|
async with configuration.use_platform_client():
|
|
@@ -439,7 +441,17 @@ async def setup(
|
|
|
439
441
|
default_llm_model = await _select_default_model(ModelCapability.LLM)
|
|
440
442
|
|
|
441
443
|
default_embedding_model = None
|
|
442
|
-
if
|
|
444
|
+
if (
|
|
445
|
+
ModelCapability.EMBEDDING in llm_provider.capabilities
|
|
446
|
+
and llm_provider.type
|
|
447
|
+
!= ModelProviderType.RITS # RITS does not support embeddings, but we treat it as OTHER
|
|
448
|
+
and (
|
|
449
|
+
llm_provider.type != ModelProviderType.OTHER # OTHER may not support embeddings, so we ask
|
|
450
|
+
or inquirer.confirm( # type: ignore
|
|
451
|
+
"Do you want to also set up an embedding model from the same provider?", default=True
|
|
452
|
+
)
|
|
453
|
+
)
|
|
454
|
+
):
|
|
443
455
|
default_embedding_model = await _select_default_model(ModelCapability.EMBEDDING)
|
|
444
456
|
elif await inquirer.confirm( # type: ignore
|
|
445
457
|
message="Do you want to configure an embedding provider? (recommended)", default=True
|
|
@@ -447,6 +459,8 @@ async def setup(
|
|
|
447
459
|
console.print("[bold]Setting up embedding provider...[/bold]")
|
|
448
460
|
await _add_provider(capability=ModelCapability.EMBEDDING, use_true_localhost=use_true_localhost)
|
|
449
461
|
default_embedding_model = await _select_default_model(ModelCapability.EMBEDDING)
|
|
462
|
+
else:
|
|
463
|
+
console.hint("You can add an embedding provider later with: [green]agentstack model add[/green]")
|
|
450
464
|
|
|
451
465
|
with console.status("Saving configuration...", spinner="dots"):
|
|
452
466
|
await SystemConfiguration.update(
|
|
@@ -468,7 +482,10 @@ async def select_default_model(
|
|
|
468
482
|
ModelCapability | None, typer.Argument(help="Which default model to change (llm/embedding)")
|
|
469
483
|
] = None,
|
|
470
484
|
model_id: typing.Annotated[str | None, typer.Argument(help="Model ID to be used as default")] = None,
|
|
485
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
471
486
|
):
|
|
487
|
+
url = announce_server_action("Updating default model for")
|
|
488
|
+
await confirm_server_action("Proceed with updating default model on", url=url, yes=yes)
|
|
472
489
|
if not capability:
|
|
473
490
|
capability = await inquirer.select( # type: ignore
|
|
474
491
|
message="Which default model would you like to change?",
|
|
@@ -479,6 +496,8 @@ async def select_default_model(
|
|
|
479
496
|
).execute_async()
|
|
480
497
|
|
|
481
498
|
assert capability
|
|
499
|
+
capability_name = str(getattr(capability, "value", capability)).lower()
|
|
500
|
+
await confirm_server_action(f"Proceed with updating the default {capability_name} model on", url=url, yes=yes)
|
|
482
501
|
async with configuration.use_platform_client():
|
|
483
502
|
model = model_id if model_id else await _select_default_model(capability)
|
|
484
503
|
conf = await SystemConfiguration.get()
|
|
@@ -504,6 +523,7 @@ def _list_providers(providers: list[ModelProvider]):
|
|
|
504
523
|
|
|
505
524
|
@model_provider_app.command("list")
|
|
506
525
|
async def list_model_providers():
|
|
526
|
+
announce_server_action("Listing model providers on")
|
|
507
527
|
async with configuration.use_platform_client():
|
|
508
528
|
providers = await ModelProvider.list()
|
|
509
529
|
_list_providers(providers)
|
|
@@ -516,6 +536,7 @@ async def add_provider(
|
|
|
516
536
|
ModelCapability | None, typer.Argument(help="Which default model to change (llm/embedding)")
|
|
517
537
|
] = None,
|
|
518
538
|
):
|
|
539
|
+
announce_server_action("Adding provider for")
|
|
519
540
|
if not capability:
|
|
520
541
|
capability = await inquirer.select( # type: ignore
|
|
521
542
|
message="Which default provider would you like to add?",
|
|
@@ -561,7 +582,11 @@ async def remove_provider(
|
|
|
561
582
|
search_path: typing.Annotated[
|
|
562
583
|
str | None, typer.Argument(..., help="Provider type or part of the provider base url")
|
|
563
584
|
] = None,
|
|
585
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
564
586
|
):
|
|
587
|
+
descriptor = search_path or "selected provider"
|
|
588
|
+
url = announce_server_action(f"Removing model provider '{descriptor}' from")
|
|
589
|
+
await confirm_server_action("Proceed with removing the selected model provider from", url=url, yes=yes)
|
|
565
590
|
async with configuration.use_platform_client():
|
|
566
591
|
conf = await SystemConfiguration.get()
|
|
567
592
|
|
|
@@ -57,8 +57,13 @@ class WSLDriver(BaseDriver):
|
|
|
57
57
|
@typing.override
|
|
58
58
|
async def create_vm(self):
|
|
59
59
|
if (await run_command(["wsl.exe", "--status"], "Checking for WSL2", check=False)).returncode != 0:
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
console.error(
|
|
61
|
+
"WSL is not installed. Please follow the Agent Stack installation instructions: https://agentstack.beeai.dev/introduction/quickstart#windows"
|
|
62
|
+
)
|
|
63
|
+
console.hint(
|
|
64
|
+
"Run [green]wsl.exe --install[/green] as administrator. If you just did this, restart your PC and run the same command again. Full installation may require up to two restarts. WSL is properly set up once you reach a working Linux terminal. You can verify this by running [green]wsl.exe[/green] without arguments."
|
|
65
|
+
)
|
|
66
|
+
sys.exit(1)
|
|
62
67
|
|
|
63
68
|
config_file = (
|
|
64
69
|
pathlib.Path.home()
|
|
@@ -68,7 +68,7 @@ async def _wait_for_auth_code(port: int = 9001) -> str:
|
|
|
68
68
|
return code
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
@app.command("login | change | select | default")
|
|
71
|
+
@app.command("login | change | select | default | switch")
|
|
72
72
|
async def server_login(server: typing.Annotated[str | None, typer.Argument()] = None):
|
|
73
73
|
"""Login to a server or switch between logged in servers."""
|
|
74
74
|
server = server or (
|
|
Binary file
|
|
@@ -21,6 +21,7 @@ import typer
|
|
|
21
21
|
import yaml
|
|
22
22
|
from anyio import create_task_group
|
|
23
23
|
from anyio.abc import ByteReceiveStream, TaskGroup
|
|
24
|
+
from InquirerPy import inquirer
|
|
24
25
|
from jsf import JSF
|
|
25
26
|
from prompt_toolkit import PromptSession
|
|
26
27
|
from prompt_toolkit.shortcuts import CompleteStyle
|
|
@@ -28,6 +29,7 @@ from pydantic import BaseModel
|
|
|
28
29
|
from rich.console import Capture
|
|
29
30
|
from rich.text import Text
|
|
30
31
|
|
|
32
|
+
from agentstack_cli.configuration import Configuration
|
|
31
33
|
from agentstack_cli.console import console, err_console
|
|
32
34
|
|
|
33
35
|
if TYPE_CHECKING:
|
|
@@ -115,6 +117,37 @@ def remove_nullable(schema: dict[str, Any]) -> dict[str, Any]:
|
|
|
115
117
|
prompt_session = None
|
|
116
118
|
|
|
117
119
|
|
|
120
|
+
def require_active_server() -> str:
|
|
121
|
+
"""Return the active server URL or exit if none is selected."""
|
|
122
|
+
if url := Configuration().auth_manager.active_server:
|
|
123
|
+
return url
|
|
124
|
+
console.error("No server selected.")
|
|
125
|
+
console.hint(
|
|
126
|
+
"Run [green]agentstack platform start[/green] to start a local server, or [green]agentstack server login[/green] to connect to a remote one."
|
|
127
|
+
)
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def announce_server_action(message: str, url: str | None = None) -> str:
|
|
132
|
+
"""Log an info message that includes the active server URL and return it."""
|
|
133
|
+
url = url or require_active_server()
|
|
134
|
+
console.info(f"{message} [cyan]{url}[/cyan]")
|
|
135
|
+
return url
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def confirm_server_action(message: str, url: str | None = None, *, yes: bool = False) -> None:
|
|
139
|
+
"""Ask for confirmation before continuing with an action on the active server."""
|
|
140
|
+
if yes:
|
|
141
|
+
return
|
|
142
|
+
url = url or require_active_server()
|
|
143
|
+
confirmed = await inquirer.confirm( # type: ignore
|
|
144
|
+
message=f"{message} [cyan]{url}[/cyan]?", default=False
|
|
145
|
+
).execute_async()
|
|
146
|
+
if not confirmed:
|
|
147
|
+
console.info("Action cancelled.")
|
|
148
|
+
raise typer.Exit(1)
|
|
149
|
+
|
|
150
|
+
|
|
118
151
|
def prompt_user(
|
|
119
152
|
prompt: str | None = None,
|
|
120
153
|
completer: "Completer | None" = None,
|
|
@@ -162,7 +195,7 @@ async def capture_output(process: anyio.abc.Process, stream_contents: list | Non
|
|
|
162
195
|
async def receive_logs(stream: ByteReceiveStream, index=0):
|
|
163
196
|
buffer = BytesIO()
|
|
164
197
|
async for chunk in stream:
|
|
165
|
-
err_console.print(Text.from_ansi(chunk.decode()), style="dim")
|
|
198
|
+
err_console.print(Text.from_ansi(chunk.decode(errors="replace")), style="dim")
|
|
166
199
|
buffer.write(chunk)
|
|
167
200
|
if stream_contents:
|
|
168
201
|
stream_contents[index] = buffer.getvalue()
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import logging
|
|
5
|
-
import typing
|
|
6
|
-
from copy import deepcopy
|
|
7
|
-
|
|
8
|
-
import typer
|
|
9
|
-
|
|
10
|
-
import agentstack_cli.commands.agent
|
|
11
|
-
import agentstack_cli.commands.build
|
|
12
|
-
import agentstack_cli.commands.mcp
|
|
13
|
-
import agentstack_cli.commands.model
|
|
14
|
-
import agentstack_cli.commands.platform
|
|
15
|
-
import agentstack_cli.commands.self
|
|
16
|
-
import agentstack_cli.commands.server
|
|
17
|
-
from agentstack_cli.async_typer import AsyncTyper
|
|
18
|
-
from agentstack_cli.configuration import Configuration
|
|
19
|
-
|
|
20
|
-
logging.basicConfig(level=logging.INFO if Configuration().debug else logging.FATAL)
|
|
21
|
-
logging.getLogger("httpx").setLevel(logging.WARNING) # not sure why this is necessary
|
|
22
|
-
|
|
23
|
-
app = AsyncTyper(no_args_is_help=True)
|
|
24
|
-
app.add_typer(agentstack_cli.commands.model.app, name="model", no_args_is_help=True, help="Manage model providers.")
|
|
25
|
-
app.add_typer(agentstack_cli.commands.agent.app, name="agent", no_args_is_help=True, help="Manage agents.")
|
|
26
|
-
app.add_typer(
|
|
27
|
-
agentstack_cli.commands.platform.app, name="platform", no_args_is_help=True, help="Manage Agent Stack platform."
|
|
28
|
-
)
|
|
29
|
-
app.add_typer(
|
|
30
|
-
agentstack_cli.commands.mcp.app, name="mcp", no_args_is_help=True, help="Manage MCP servers and toolkits."
|
|
31
|
-
)
|
|
32
|
-
app.add_typer(agentstack_cli.commands.build.app, name="", no_args_is_help=True, help="Build agent images.")
|
|
33
|
-
app.add_typer(
|
|
34
|
-
agentstack_cli.commands.server.app,
|
|
35
|
-
name="server",
|
|
36
|
-
no_args_is_help=True,
|
|
37
|
-
help="Manage Agent Stack servers and authentication.",
|
|
38
|
-
)
|
|
39
|
-
app.add_typer(
|
|
40
|
-
agentstack_cli.commands.self.app,
|
|
41
|
-
name="self",
|
|
42
|
-
no_args_is_help=True,
|
|
43
|
-
help="Manage Agent Stack installation.",
|
|
44
|
-
hidden=True,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
agent_alias = deepcopy(agentstack_cli.commands.agent.app)
|
|
49
|
-
for cmd in agent_alias.registered_commands:
|
|
50
|
-
cmd.rich_help_panel = "Agent commands"
|
|
51
|
-
|
|
52
|
-
app.add_typer(agent_alias, name="", no_args_is_help=True)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@app.command("version")
|
|
56
|
-
async def version(verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False):
|
|
57
|
-
"""Print version of the Agent Stack CLI."""
|
|
58
|
-
import agentstack_cli.commands.self
|
|
59
|
-
|
|
60
|
-
await agentstack_cli.commands.self.version(verbose=verbose)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@app.command("ui")
|
|
64
|
-
async def ui():
|
|
65
|
-
"""Launch the graphical interface."""
|
|
66
|
-
import webbrowser
|
|
67
|
-
|
|
68
|
-
import agentstack_cli.commands.model
|
|
69
|
-
|
|
70
|
-
await agentstack_cli.commands.model.ensure_llm_provider()
|
|
71
|
-
webbrowser.open(
|
|
72
|
-
"http://localhost:8334"
|
|
73
|
-
) # TODO: This always opens the local UI, how to open the UI of a logged in server instead?
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if __name__ == "__main__":
|
|
77
|
-
app()
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/platform/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentstack_cli-0.4.2rc1 → agentstack_cli-0.4.2rc3}/src/agentstack_cli/commands/platform/istio.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|