virtuai-cli 0.2.2__tar.gz → 0.3.0__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.
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/PKG-INFO +1 -1
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/pyproject.toml +1 -1
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/__init__.py +1 -1
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/chat/command.py +2 -1
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/chat/tui.py +64 -8
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/README.md +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/setup.cfg +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/chat/__init__.py +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/chat/sse.py +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/chat/widgets.py +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/config.py +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/executor.py +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/main.py +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/runner.py +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli/security.py +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli.egg-info/SOURCES.txt +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli.egg-info/requires.txt +0 -0
- {virtuai_cli-0.2.2 → virtuai_cli-0.3.0}/src/virtuai_cli.egg-info/top_level.txt +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""VirtuAI local CLI."""
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.3.0"
|
|
@@ -92,6 +92,7 @@ def run_chat(
|
|
|
92
92
|
agent_id=picked["agent_id"],
|
|
93
93
|
agent_name=picked["name"],
|
|
94
94
|
workspace_name=info["workspace_name"],
|
|
95
|
-
default_model_id=picked.get("default_model_id"),
|
|
95
|
+
default_model_id=picked.get("default_model_id") or None,
|
|
96
|
+
allowed_models=picked.get("allowed_models") or [],
|
|
96
97
|
)
|
|
97
98
|
app.run()
|
|
@@ -64,6 +64,7 @@ class ChatApp(App):
|
|
|
64
64
|
agent_name: str,
|
|
65
65
|
workspace_name: str,
|
|
66
66
|
default_model_id: Optional[str] = None,
|
|
67
|
+
allowed_models: Optional[list[dict]] = None,
|
|
67
68
|
) -> None:
|
|
68
69
|
super().__init__()
|
|
69
70
|
self.server_url = server_url
|
|
@@ -73,6 +74,8 @@ class ChatApp(App):
|
|
|
73
74
|
self.agent_name = agent_name
|
|
74
75
|
self.workspace_name = workspace_name
|
|
75
76
|
self.default_model_id = default_model_id
|
|
77
|
+
self.current_model_id = default_model_id
|
|
78
|
+
self.allowed_models = list(allowed_models or [])
|
|
76
79
|
|
|
77
80
|
self.session_id: Optional[str] = None
|
|
78
81
|
self._stream_task: Optional[asyncio.Task] = None
|
|
@@ -94,7 +97,7 @@ class ChatApp(App):
|
|
|
94
97
|
yield Footer()
|
|
95
98
|
|
|
96
99
|
def _initial_status(self) -> str:
|
|
97
|
-
model = f" · {self.
|
|
100
|
+
model = f" · {self.current_model_id}" if self.current_model_id else ""
|
|
98
101
|
return f"{self.workspace_name} · {self.agent_name}{model} · connecting…"
|
|
99
102
|
|
|
100
103
|
# ── Mount: start the WS runner in the background ──────────────────────
|
|
@@ -119,9 +122,10 @@ class ChatApp(App):
|
|
|
119
122
|
def _set_status(self, text: str) -> None:
|
|
120
123
|
# The status widget may have been torn down during app shutdown —
|
|
121
124
|
# swallow the lookup error so we don't surface a NoMatches on exit.
|
|
125
|
+
model = f" · {self.current_model_id}" if self.current_model_id else ""
|
|
122
126
|
try:
|
|
123
127
|
self.query_one("#status", Static).update(
|
|
124
|
-
f"{self.workspace_name} · {self.agent_name} · {text}"
|
|
128
|
+
f"{self.workspace_name} · {self.agent_name}{model} · {text}"
|
|
125
129
|
)
|
|
126
130
|
except Exception:
|
|
127
131
|
pass
|
|
@@ -160,20 +164,72 @@ class ChatApp(App):
|
|
|
160
164
|
async def _handle_slash(self, cmd: str) -> None:
|
|
161
165
|
parts = cmd.split(None, 1)
|
|
162
166
|
head = parts[0].lower()
|
|
167
|
+
arg = parts[1].strip() if len(parts) > 1 else ""
|
|
163
168
|
if head in ("/exit", "/quit"):
|
|
164
169
|
self.exit()
|
|
165
170
|
elif head in ("/clear", "/new"):
|
|
166
171
|
await self.action_clear_conversation()
|
|
172
|
+
elif head == "/models":
|
|
173
|
+
await self._show_models()
|
|
174
|
+
elif head == "/model":
|
|
175
|
+
await self._set_model(arg)
|
|
167
176
|
elif head == "/help":
|
|
168
177
|
await self._append(Static(
|
|
169
178
|
"[b]Commands[/b]\n"
|
|
170
|
-
" /help
|
|
171
|
-
" /clear, /new
|
|
172
|
-
" /
|
|
173
|
-
"
|
|
179
|
+
" /help this list\n"
|
|
180
|
+
" /clear, /new start a fresh conversation\n"
|
|
181
|
+
" /models list models available for this agent\n"
|
|
182
|
+
" /model <id> switch the model for the next message\n"
|
|
183
|
+
" /exit, /quit close the TUI\n"
|
|
184
|
+
" Esc cancel current response\n"
|
|
174
185
|
))
|
|
175
186
|
else:
|
|
176
|
-
await self._append(Static(f"[red]Unknown command: {head}[/red]"))
|
|
187
|
+
await self._append(Static(f"[red]Unknown command: {head}[/red] (try /help)"))
|
|
188
|
+
|
|
189
|
+
async def _show_models(self) -> None:
|
|
190
|
+
if not self.allowed_models:
|
|
191
|
+
await self._append(Static(
|
|
192
|
+
"[yellow]No model catalog available for this agent.[/yellow]"
|
|
193
|
+
))
|
|
194
|
+
return
|
|
195
|
+
lines = ["[b]Available models[/b]"]
|
|
196
|
+
for m in self.allowed_models:
|
|
197
|
+
marker = "•" if m["id"] != self.current_model_id else "[green]✓[/green]"
|
|
198
|
+
label = m.get("label") or m["id"]
|
|
199
|
+
provider = f" [dim]({m['provider']})[/dim]" if m.get("provider") else ""
|
|
200
|
+
lines.append(f" {marker} [bold]{m['id']}[/bold] — {label}{provider}")
|
|
201
|
+
lines.append("\n[dim]Use [/dim][b]/model <id>[/b][dim] to switch.[/dim]")
|
|
202
|
+
await self._append(Static("\n".join(lines)))
|
|
203
|
+
|
|
204
|
+
async def _set_model(self, requested: str) -> None:
|
|
205
|
+
if not requested:
|
|
206
|
+
current = self.current_model_id or "(none)"
|
|
207
|
+
await self._append(Static(
|
|
208
|
+
f"Current model: [b]{current}[/b]\n"
|
|
209
|
+
f"Usage: [b]/model <id>[/b] (see [b]/models[/b] for the list)"
|
|
210
|
+
))
|
|
211
|
+
return
|
|
212
|
+
if self._stream_task and not self._stream_task.done():
|
|
213
|
+
await self._append(Static(
|
|
214
|
+
"[yellow]Cancel the in-flight response (Esc) before switching models.[/yellow]"
|
|
215
|
+
))
|
|
216
|
+
return
|
|
217
|
+
if self.allowed_models:
|
|
218
|
+
valid_ids = {m["id"] for m in self.allowed_models}
|
|
219
|
+
if requested not in valid_ids:
|
|
220
|
+
# Loose match by label too
|
|
221
|
+
by_label = {m.get("label", ""): m["id"] for m in self.allowed_models}
|
|
222
|
+
if requested in by_label:
|
|
223
|
+
requested = by_label[requested]
|
|
224
|
+
else:
|
|
225
|
+
await self._append(Static(
|
|
226
|
+
f"[red]'{requested}' is not in this agent's catalog.[/red] "
|
|
227
|
+
f"Run [b]/models[/b] to see valid IDs."
|
|
228
|
+
))
|
|
229
|
+
return
|
|
230
|
+
self.current_model_id = requested
|
|
231
|
+
self._set_status(f"model set to {requested}")
|
|
232
|
+
await self._append(Static(f"[green]✓[/green] Model switched to [b]{requested}[/b]."))
|
|
177
233
|
|
|
178
234
|
async def action_clear_conversation(self) -> None:
|
|
179
235
|
self.session_id = None
|
|
@@ -200,7 +256,7 @@ class ChatApp(App):
|
|
|
200
256
|
self.agent_id,
|
|
201
257
|
message,
|
|
202
258
|
session_id=self.session_id,
|
|
203
|
-
model_id=self.
|
|
259
|
+
model_id=self.current_model_id,
|
|
204
260
|
):
|
|
205
261
|
await self._handle_event(event, turn)
|
|
206
262
|
except asyncio.CancelledError:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|