venice-cli 0.28.0__tar.gz → 0.30.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.
- {venice_cli-0.28.0 → venice_cli-0.30.0}/PKG-INFO +9 -7
- {venice_cli-0.28.0 → venice_cli-0.30.0}/README.md +8 -6
- venice_cli-0.30.0/src/venice/__init__.py +1 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_agent.py +24 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_code.py +2 -1
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_repl.py +101 -3
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/code.py +3 -2
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_chat.py +7 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_code.py +3 -3
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_code_command.py +22 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_repl.py +122 -0
- venice_cli-0.28.0/src/venice/__init__.py +0 -1
- {venice_cli-0.28.0 → venice_cli-0.30.0}/.gitignore +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/CONTRIBUTING.md +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/LICENSE +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/Makefile +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/SECURITY.md +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/bin/venice +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/install.sh +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/pyproject.toml +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/__main__.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/audio_player.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/audio_post.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/auth.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/billing.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/cli.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/client.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/__init__.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_audio.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_index.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_mcp.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_mcp_client.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_models.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_openai.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_queue.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/_shared.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/balance.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/bg_remove.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/chat.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/config.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/contact_sheet.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/embed.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/image.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/image_edit.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/index.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/login.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/master.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/mcp_serve.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/models.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/music.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/search.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/sfx.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/tts.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/upscale.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/commands/video.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/config.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/image_montage.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/mcp_server.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/src/venice/userconfig.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/__init__.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/_mcp_fake_server.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_auth.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_balance.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_bg_remove.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_client.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_config.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_contact_sheet.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_embed.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_image.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_image_edit.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_index.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_master.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_mcp_client.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_mcp_serve.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_mcp_tools.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_models.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_music.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_search.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_sfx.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_shared.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_shared_openai.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_tts.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_upscale.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/tests/test_video.py +0 -0
- {venice_cli-0.28.0 → venice_cli-0.30.0}/uninstall.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: venice-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.30.0
|
|
4
4
|
Summary: A stdlib-only Python CLI for the Venice.ai API: audio, video, images, chat, and embeddings.
|
|
5
5
|
Project-URL: Homepage, https://github.com/gobha-me/venice-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/gobha-me/venice-cli
|
|
@@ -558,10 +558,12 @@ venice chat --resume session.json
|
|
|
558
558
|
```
|
|
559
559
|
|
|
560
560
|
In-REPL slash-commands: `/system [text]` (show/set the system prompt),
|
|
561
|
-
`/model [name]` (switch model
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
561
|
+
`/model [name]` (switch model; with no name, show the current one and list the
|
|
562
|
+
catalog), `/models` (list the available models, marking the current and the
|
|
563
|
+
default), `/reset` (clear history, keep the system prompt), `/save [file]`
|
|
564
|
+
(write the transcript JSON; defaults to the `--resume` file), `/help`, and
|
|
565
|
+
`/exit` (or `/quit`, or Ctrl-D). Ctrl-C aborts the current turn without ending
|
|
566
|
+
the session. Tab completes slash-commands (and model ids after `/model `).
|
|
565
567
|
|
|
566
568
|
### Venice extensions
|
|
567
569
|
|
|
@@ -886,7 +888,7 @@ complete — a loud stderr warning is printed).
|
|
|
886
888
|
| `write_file` | create/overwrite a file (atomic) | yes |
|
|
887
889
|
| `edit_file` | replace an exact, unique string in a file | yes |
|
|
888
890
|
| `run` | run a shell command (`/bin/sh -c`) at the root | yes |
|
|
889
|
-
| `venice_image` / `venice_image_edit` / `venice_sfx` / `venice_music` / `venice_tts` / `venice_upscale` / `venice_bg_remove` | generate/edit images &
|
|
891
|
+
| `venice_image` / `venice_image_edit` / `venice_sfx` / `venice_music` / `venice_tts` / `venice_upscale` / `venice_bg_remove` / `venice_video` | generate/edit images, audio & video into the project — **opt-in with `--assets`** | yes |
|
|
890
892
|
|
|
891
893
|
**Safety.** Every filesystem path is resolved and confined to the project root
|
|
892
894
|
(default: cwd, or `--root` / `$VENICE_CODE_ROOT`); a path that escapes the root, names
|
|
@@ -909,7 +911,7 @@ forced cwd, timeout, and env-scrub — which is why it always confirms. git muta
|
|
|
909
911
|
| `--root DIR` | project directory to sandbox to (default: cwd) |
|
|
910
912
|
| `--max-tool-calls N` | cap tool invocations before forcing a final answer (default 25) |
|
|
911
913
|
| `--exec-timeout SECS` | timeout for `run`/`git` (default 120) |
|
|
912
|
-
| `--assets` | also expose the in-process asset-generation tools (image / image-edit / sfx / music / tts / upscale / bg-remove) so the agent can create images &
|
|
914
|
+
| `--assets` | also expose the in-process asset-generation tools (image / image-edit / sfx / music / tts / upscale / bg-remove / video) so the agent can create images, audio & video in the project; paid — each confirms per call unless `--auto` |
|
|
913
915
|
| `-i`, `--json`, `--model`, `--system` | interactive REPL · JSON envelope · model · extra system instructions |
|
|
914
916
|
|
|
915
917
|
With `--assets`, generated files land in `$VENICE_MCP_OUTPUT_DIR` or, by default, under
|
|
@@ -520,10 +520,12 @@ venice chat --resume session.json
|
|
|
520
520
|
```
|
|
521
521
|
|
|
522
522
|
In-REPL slash-commands: `/system [text]` (show/set the system prompt),
|
|
523
|
-
`/model [name]` (switch model
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
523
|
+
`/model [name]` (switch model; with no name, show the current one and list the
|
|
524
|
+
catalog), `/models` (list the available models, marking the current and the
|
|
525
|
+
default), `/reset` (clear history, keep the system prompt), `/save [file]`
|
|
526
|
+
(write the transcript JSON; defaults to the `--resume` file), `/help`, and
|
|
527
|
+
`/exit` (or `/quit`, or Ctrl-D). Ctrl-C aborts the current turn without ending
|
|
528
|
+
the session. Tab completes slash-commands (and model ids after `/model `).
|
|
527
529
|
|
|
528
530
|
### Venice extensions
|
|
529
531
|
|
|
@@ -848,7 +850,7 @@ complete — a loud stderr warning is printed).
|
|
|
848
850
|
| `write_file` | create/overwrite a file (atomic) | yes |
|
|
849
851
|
| `edit_file` | replace an exact, unique string in a file | yes |
|
|
850
852
|
| `run` | run a shell command (`/bin/sh -c`) at the root | yes |
|
|
851
|
-
| `venice_image` / `venice_image_edit` / `venice_sfx` / `venice_music` / `venice_tts` / `venice_upscale` / `venice_bg_remove` | generate/edit images &
|
|
853
|
+
| `venice_image` / `venice_image_edit` / `venice_sfx` / `venice_music` / `venice_tts` / `venice_upscale` / `venice_bg_remove` / `venice_video` | generate/edit images, audio & video into the project — **opt-in with `--assets`** | yes |
|
|
852
854
|
|
|
853
855
|
**Safety.** Every filesystem path is resolved and confined to the project root
|
|
854
856
|
(default: cwd, or `--root` / `$VENICE_CODE_ROOT`); a path that escapes the root, names
|
|
@@ -871,7 +873,7 @@ forced cwd, timeout, and env-scrub — which is why it always confirms. git muta
|
|
|
871
873
|
| `--root DIR` | project directory to sandbox to (default: cwd) |
|
|
872
874
|
| `--max-tool-calls N` | cap tool invocations before forcing a final answer (default 25) |
|
|
873
875
|
| `--exec-timeout SECS` | timeout for `run`/`git` (default 120) |
|
|
874
|
-
| `--assets` | also expose the in-process asset-generation tools (image / image-edit / sfx / music / tts / upscale / bg-remove) so the agent can create images &
|
|
876
|
+
| `--assets` | also expose the in-process asset-generation tools (image / image-edit / sfx / music / tts / upscale / bg-remove / video) so the agent can create images, audio & video in the project; paid — each confirms per call unless `--auto` |
|
|
875
877
|
| `-i`, `--json`, `--model`, `--system` | interactive REPL · JSON envelope · model · extra system instructions |
|
|
876
878
|
|
|
877
879
|
With `--assets`, generated files land in `$VENICE_MCP_OUTPUT_DIR` or, by default, under
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.30.0"
|
|
@@ -202,6 +202,21 @@ _IMAGE_EDIT_SCHEMA = _obj(
|
|
|
202
202
|
required=["prompt"],
|
|
203
203
|
)
|
|
204
204
|
|
|
205
|
+
_VIDEO_SCHEMA = _obj(
|
|
206
|
+
{
|
|
207
|
+
"prompt": _p("string", "What the video should depict."),
|
|
208
|
+
"model": _p("string", "Video model id (default: the catalog default)."),
|
|
209
|
+
"duration": _p("string", "Clip length, e.g. '5s'."),
|
|
210
|
+
"negative_prompt": _p("string"),
|
|
211
|
+
"resolution": _p("string", "Output resolution tier, e.g. 720p/1080p."),
|
|
212
|
+
"aspect_ratio": _p("string", "Output aspect ratio, e.g. 16:9."),
|
|
213
|
+
"no_audio": _p("boolean", "Generate silent video (no soundtrack)."),
|
|
214
|
+
"image_url": _p("string", "URL of a start/reference image (image-to-video)."),
|
|
215
|
+
"end_image_url": _p("string", "URL of an end frame to interpolate toward."),
|
|
216
|
+
},
|
|
217
|
+
required=["prompt"],
|
|
218
|
+
)
|
|
219
|
+
|
|
205
220
|
# (tool name, `_mcp` impl attribute, description, schema, paid). The impl is
|
|
206
221
|
# stored by NAME and resolved via getattr(_mcp, ...) at builtin_tools() time, so a
|
|
207
222
|
# single source of truth wins and tests can patch `_mcp.<impl>`.
|
|
@@ -292,6 +307,15 @@ _CODE_ASSET_BUILTINS = [
|
|
|
292
307
|
_IMAGE_EDIT_SCHEMA,
|
|
293
308
|
True,
|
|
294
309
|
),
|
|
310
|
+
(
|
|
311
|
+
"venice_video",
|
|
312
|
+
"video_tool",
|
|
313
|
+
"Generate a short video via Venice's async video queue (blocks with a capped "
|
|
314
|
+
"wait; can be slow). Optionally image-to-video from image_url. Writes an .mp4 "
|
|
315
|
+
"and returns its path. Dynamic pricing, so it always needs confirmation.",
|
|
316
|
+
_VIDEO_SCHEMA,
|
|
317
|
+
True,
|
|
318
|
+
),
|
|
295
319
|
]
|
|
296
320
|
|
|
297
321
|
# Loop-controlled kwargs the model must never supply (stripped defensively).
|
|
@@ -459,7 +459,7 @@ def code_tools(
|
|
|
459
459
|
|
|
460
460
|
When `assets` and a `client` are supplied, the in-process asset-generation tools
|
|
461
461
|
(`venice_image`/`venice_image_edit`/`venice_sfx`/`venice_music`/`venice_tts`/
|
|
462
|
-
`venice_upscale`/`venice_bg_remove`) are folded in via `_agent.builtin_tools`,
|
|
462
|
+
`venice_upscale`/`venice_bg_remove`/`venice_video`) are folded in via `_agent.builtin_tools`,
|
|
463
463
|
so the agent can create images/audio in the project. They are paid and route
|
|
464
464
|
through the same confirm/spend gate; generated files land in
|
|
465
465
|
``$VENICE_MCP_OUTPUT_DIR`` or, by default, under `root`.
|
|
@@ -534,6 +534,7 @@ def code_tools(
|
|
|
534
534
|
only={
|
|
535
535
|
"venice_image", "venice_image_edit", "venice_sfx",
|
|
536
536
|
"venice_music", "venice_tts", "venice_upscale", "venice_bg_remove",
|
|
537
|
+
"venice_video",
|
|
537
538
|
},
|
|
538
539
|
))
|
|
539
540
|
return tools
|
|
@@ -37,13 +37,20 @@ _PROMPT = "you> "
|
|
|
37
37
|
_HELP = """\
|
|
38
38
|
Commands:
|
|
39
39
|
/system [text] show, or set, the system prompt (reseeds the conversation)
|
|
40
|
-
/model [name]
|
|
40
|
+
/model [name] switch model; with no name, show the current one and list the catalog
|
|
41
|
+
/models list the available models (marks the current and default)
|
|
41
42
|
/reset clear the conversation (keeps the system prompt)
|
|
42
43
|
/save [file] write the transcript JSON (default: the --resume file)
|
|
43
44
|
/help show this help
|
|
44
45
|
/exit, /quit leave the REPL
|
|
45
46
|
Anything else is sent to the model as your next message."""
|
|
46
47
|
|
|
48
|
+
# Slash-commands, in help order -- the single source of truth for tab-completion
|
|
49
|
+
# (#40). Keep in sync with `_dispatch_slash` and `_HELP`.
|
|
50
|
+
_COMMANDS = (
|
|
51
|
+
"/system", "/model", "/models", "/reset", "/save", "/help", "/exit", "/quit",
|
|
52
|
+
)
|
|
53
|
+
|
|
47
54
|
|
|
48
55
|
class _TranscriptError(Exception):
|
|
49
56
|
"""A --resume transcript file is missing or malformed. Message is printable."""
|
|
@@ -114,6 +121,91 @@ def _current_system(messages: List[dict]) -> Optional[str]:
|
|
|
114
121
|
return None
|
|
115
122
|
|
|
116
123
|
|
|
124
|
+
# --------------------------------------------------------------------------- #
|
|
125
|
+
# Model listing (#39) + tab-completion (#40)
|
|
126
|
+
# --------------------------------------------------------------------------- #
|
|
127
|
+
def _format_model_list(models, current: Optional[str]) -> str:
|
|
128
|
+
"""One id per line for `/models` and bare `/model`.
|
|
129
|
+
|
|
130
|
+
Marks the current model with ``*`` and the catalog's default-trait model with
|
|
131
|
+
a trailing ``(default)``; appends ``model_spec.name`` when present. Reuses the
|
|
132
|
+
already-fetched `models` list (no catalog re-fetch). Returns a printable block.
|
|
133
|
+
"""
|
|
134
|
+
if not models:
|
|
135
|
+
return "(model catalog unavailable; pass --model or /model <id> to switch)"
|
|
136
|
+
default = _models.default_model(models)
|
|
137
|
+
lines = []
|
|
138
|
+
for m in models:
|
|
139
|
+
if not isinstance(m, dict):
|
|
140
|
+
continue
|
|
141
|
+
mid = m.get("id")
|
|
142
|
+
if not mid:
|
|
143
|
+
continue
|
|
144
|
+
spec = m.get("model_spec") if isinstance(m.get("model_spec"), dict) else {}
|
|
145
|
+
name = spec.get("name")
|
|
146
|
+
marker = "*" if mid == current else " "
|
|
147
|
+
tags = " (default)" if mid == default else ""
|
|
148
|
+
line = f" {marker} {mid}{tags}"
|
|
149
|
+
if name:
|
|
150
|
+
line += f" -- {name}"
|
|
151
|
+
lines.append(line)
|
|
152
|
+
return "\n".join(lines) if lines else "(no models advertised)"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _make_completer(models, rl):
|
|
156
|
+
"""Build a `readline` completer over the slash-commands and model ids (#40).
|
|
157
|
+
|
|
158
|
+
`rl` is the readline module (injected so the closure is unit-testable without
|
|
159
|
+
a real terminal). The returned ``completer(text, state)`` completes the leading
|
|
160
|
+
``/command`` token, and model ids after ``/model ``; it is a no-op on non-slash
|
|
161
|
+
lines so ordinary prose is never auto-completed.
|
|
162
|
+
"""
|
|
163
|
+
model_ids = [
|
|
164
|
+
m.get("id") for m in (models or [])
|
|
165
|
+
if isinstance(m, dict) and m.get("id")
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
def completer(text, state):
|
|
169
|
+
buf = rl.get_line_buffer()
|
|
170
|
+
if not buf.lstrip().startswith("/"):
|
|
171
|
+
return None
|
|
172
|
+
# Empty prefix left of the token => we're completing the command word.
|
|
173
|
+
if not buf[: rl.get_begidx()].strip():
|
|
174
|
+
candidates = _COMMANDS
|
|
175
|
+
elif buf.lstrip().split(maxsplit=1)[0].lower() == "/model":
|
|
176
|
+
candidates = model_ids
|
|
177
|
+
else:
|
|
178
|
+
candidates = ()
|
|
179
|
+
matches = [c for c in candidates if c.startswith(text)]
|
|
180
|
+
return matches[state] if state < len(matches) else None
|
|
181
|
+
|
|
182
|
+
return completer
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _install_completer(rl, models, stack) -> None:
|
|
186
|
+
"""Register the tab-completer for the session, restoring the previous one on exit.
|
|
187
|
+
|
|
188
|
+
Sets whitespace-only delimiters so a leading ``/command`` is one token, installs
|
|
189
|
+
the `_make_completer` closure, and binds Tab (libedit vs GNU readline). The
|
|
190
|
+
`stack` callback restores the prior completer + delims so we never leak into a
|
|
191
|
+
parent readline context (e.g. a REPL launched from another readline program).
|
|
192
|
+
"""
|
|
193
|
+
prev_completer = rl.get_completer()
|
|
194
|
+
prev_delims = rl.get_completer_delims()
|
|
195
|
+
|
|
196
|
+
def _restore():
|
|
197
|
+
rl.set_completer(prev_completer)
|
|
198
|
+
rl.set_completer_delims(prev_delims)
|
|
199
|
+
|
|
200
|
+
stack.callback(_restore)
|
|
201
|
+
rl.set_completer_delims(" \t\n")
|
|
202
|
+
rl.set_completer(_make_completer(models, rl))
|
|
203
|
+
if "libedit" in (getattr(rl, "__doc__", "") or ""):
|
|
204
|
+
rl.parse_and_bind("bind ^I rl_complete")
|
|
205
|
+
else:
|
|
206
|
+
rl.parse_and_bind("tab: complete")
|
|
207
|
+
|
|
208
|
+
|
|
117
209
|
# --------------------------------------------------------------------------- #
|
|
118
210
|
# One turn
|
|
119
211
|
# --------------------------------------------------------------------------- #
|
|
@@ -195,6 +287,9 @@ def _dispatch_slash(line, messages, state, args, models) -> str:
|
|
|
195
287
|
print(f"(model -> {new})", file=sys.stderr)
|
|
196
288
|
else:
|
|
197
289
|
print(f"model: {state['model']}", file=sys.stderr)
|
|
290
|
+
print(_format_model_list(models, state["model"]), file=sys.stderr)
|
|
291
|
+
elif cmd == "models":
|
|
292
|
+
print(_format_model_list(models, state["model"]), file=sys.stderr)
|
|
198
293
|
elif cmd == "save":
|
|
199
294
|
target = rest or getattr(args, "resume", None)
|
|
200
295
|
if not target:
|
|
@@ -231,9 +326,9 @@ def run(args, oai, openai, client, models, model, initial=None, *,
|
|
|
231
326
|
from . import chat # lazy: chat imports this module at top (avoid a cycle)
|
|
232
327
|
|
|
233
328
|
try:
|
|
234
|
-
import readline #
|
|
329
|
+
import readline as _rl # line editing + in-session history + completion
|
|
235
330
|
except Exception: # pragma: no cover - platform without readline
|
|
236
|
-
|
|
331
|
+
_rl = None
|
|
237
332
|
|
|
238
333
|
try:
|
|
239
334
|
messages = _seed_messages(args)
|
|
@@ -267,6 +362,9 @@ def run(args, oai, openai, client, models, model, initial=None, *,
|
|
|
267
362
|
tools_on = False # model lacks function calling -> plain chat
|
|
268
363
|
state = {"model": model, "tools": tools, "tools_on": tools_on}
|
|
269
364
|
|
|
365
|
+
if _rl is not None:
|
|
366
|
+
_install_completer(_rl, models, stack)
|
|
367
|
+
|
|
270
368
|
_banner(model, tools_on, getattr(args, "resume", None), messages, label=label)
|
|
271
369
|
|
|
272
370
|
# An explicit message (`venice chat -i "hello"`) becomes the first turn.
|
|
@@ -171,8 +171,9 @@ def register(subparsers) -> None:
|
|
|
171
171
|
grp.add_argument(
|
|
172
172
|
"--assets", action="store_true", dest="assets", default=None,
|
|
173
173
|
help="Also expose the in-process asset-generation tools (venice_image, "
|
|
174
|
-
"image_edit, sfx, music, tts, upscale, bg_remove) so the agent can
|
|
175
|
-
"images/audio in the project. Paid: each confirms per call
|
|
174
|
+
"image_edit, sfx, music, tts, upscale, bg_remove, video) so the agent can "
|
|
175
|
+
"create images/audio/video in the project. Paid: each confirms per call "
|
|
176
|
+
"unless --auto.",
|
|
176
177
|
)
|
|
177
178
|
|
|
178
179
|
it = p.add_argument_group("Interactive")
|
|
@@ -760,6 +760,13 @@ class TestBuiltinToolsRegistry(unittest.TestCase):
|
|
|
760
760
|
self.assertNotIn(banned, props)
|
|
761
761
|
self.assertEqual(_agent._IMAGE_EDIT_SCHEMA.get("required"), ["prompt"])
|
|
762
762
|
|
|
763
|
+
def test_video_schema_excludes_controlled(self):
|
|
764
|
+
from venice.commands import _agent
|
|
765
|
+
props = _agent._VIDEO_SCHEMA["properties"]
|
|
766
|
+
for banned in ("confirm", "max_spend", "output_dir"):
|
|
767
|
+
self.assertNotIn(banned, props)
|
|
768
|
+
self.assertEqual(_agent._VIDEO_SCHEMA.get("required"), ["prompt"])
|
|
769
|
+
|
|
763
770
|
|
|
764
771
|
if __name__ == "__main__":
|
|
765
772
|
unittest.main()
|
|
@@ -18,7 +18,7 @@ def _tools(root, **kw):
|
|
|
18
18
|
|
|
19
19
|
_ASSET_NAMES = {
|
|
20
20
|
"venice_image", "venice_image_edit", "venice_sfx", "venice_music",
|
|
21
|
-
"venice_tts", "venice_upscale", "venice_bg_remove",
|
|
21
|
+
"venice_tts", "venice_upscale", "venice_bg_remove", "venice_video",
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
|
|
@@ -242,9 +242,9 @@ class TestCodeFactory(unittest.TestCase):
|
|
|
242
242
|
def test_assets_present_when_enabled(self):
|
|
243
243
|
names = {t.name for t in _code.code_tools("/tmp", client=object(),
|
|
244
244
|
assets=True)}
|
|
245
|
-
self.assertTrue(_ASSET_NAMES <= names) # all
|
|
245
|
+
self.assertTrue(_ASSET_NAMES <= names) # all 8 folded in
|
|
246
246
|
self.assertNotIn("venice_chat", names) # excluded by design
|
|
247
|
-
self.
|
|
247
|
+
self.assertIn("venice_video", names)
|
|
248
248
|
|
|
249
249
|
def test_asset_tools_are_paid(self):
|
|
250
250
|
by = {t.name: t for t in _code.code_tools("/tmp", client=object(),
|
|
@@ -111,6 +111,7 @@ class TestCodeCommand(unittest.TestCase):
|
|
|
111
111
|
names = self._exec_tool_names(calls)
|
|
112
112
|
self.assertIn("venice_image", names)
|
|
113
113
|
self.assertIn("venice_image_edit", names)
|
|
114
|
+
self.assertIn("venice_video", names)
|
|
114
115
|
self.assertNotIn("venice_chat", names) # excluded by design
|
|
115
116
|
|
|
116
117
|
def test_assets_absent_by_default(self):
|
|
@@ -146,6 +147,27 @@ class TestCodeCommand(unittest.TestCase):
|
|
|
146
147
|
# the model supplied only prompt; control kwargs are injected, not from args
|
|
147
148
|
self.assertEqual(stub.call_args.kwargs.get("prompt"), "a hero sprite")
|
|
148
149
|
|
|
150
|
+
def test_video_asset_dispatches_with_confirm_under_auto(self):
|
|
151
|
+
seq = [
|
|
152
|
+
FakeToolCompletion("plan"),
|
|
153
|
+
FakeToolCompletion(tool_calls=[
|
|
154
|
+
_FnCall("c1", "venice_video",
|
|
155
|
+
json.dumps({"prompt": "a koi pond at dawn"}))]),
|
|
156
|
+
FakeToolCompletion("made the clip"),
|
|
157
|
+
FakeToolCompletion("ACCEPTANCE: PASS"),
|
|
158
|
+
]
|
|
159
|
+
with mock.patch(
|
|
160
|
+
"venice.commands._mcp.video_tool",
|
|
161
|
+
return_value={"status": "ok", "path": "/x.mp4", "bytes": 1},
|
|
162
|
+
) as stub:
|
|
163
|
+
rc, calls = self._run(
|
|
164
|
+
_code_args(task="film", root=self.root, auto=True, assets=True), seq)
|
|
165
|
+
self.assertEqual(rc, 0)
|
|
166
|
+
self.assertEqual(stub.call_count, 1)
|
|
167
|
+
# --auto -> confirm=True bypasses the spend gate
|
|
168
|
+
self.assertTrue(stub.call_args.kwargs.get("confirm"))
|
|
169
|
+
self.assertEqual(stub.call_args.kwargs.get("prompt"), "a koi pond at dawn")
|
|
170
|
+
|
|
149
171
|
def test_acceptance_fail_returns_1(self):
|
|
150
172
|
seq = [
|
|
151
173
|
FakeToolCompletion("plan"),
|
|
@@ -27,6 +27,8 @@ from tests.test_chat import (
|
|
|
27
27
|
_args,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
from venice.commands import _repl # noqa: E402
|
|
31
|
+
|
|
30
32
|
_EMPTY_CFG = {"version": 1, "mcpServers": {}, "defaults": {}}
|
|
31
33
|
|
|
32
34
|
|
|
@@ -290,6 +292,126 @@ class TestRepl(unittest.TestCase):
|
|
|
290
292
|
self.assertEqual([m["role"] for m in calls[1]["messages"]], ["user"])
|
|
291
293
|
self.assertEqual(calls[1]["messages"][0]["content"], "again")
|
|
292
294
|
|
|
295
|
+
# ------------------------------------------------------------------ #
|
|
296
|
+
# #39: /models listing + bare /model listing
|
|
297
|
+
# ------------------------------------------------------------------ #
|
|
298
|
+
def test_slash_models_lists_catalog(self):
|
|
299
|
+
err = io.StringIO()
|
|
300
|
+
rc, fake, calls = _run_repl(
|
|
301
|
+
_args(interactive=True), [], ["/models", "/exit"], stderr=err,
|
|
302
|
+
)
|
|
303
|
+
self.assertEqual(rc, 0)
|
|
304
|
+
self.assertEqual(len(calls), 0) # listing never calls the model
|
|
305
|
+
out = err.getvalue()
|
|
306
|
+
self.assertIn("llama-3.3-70b", out)
|
|
307
|
+
self.assertIn("venice-uncensored", out)
|
|
308
|
+
self.assertIn("(default)", out) # default-trait model marked
|
|
309
|
+
self.assertIn("* llama-3.3-70b", out) # current model marked
|
|
310
|
+
|
|
311
|
+
def test_bare_model_shows_current_and_lists(self):
|
|
312
|
+
err = io.StringIO()
|
|
313
|
+
rc, fake, calls = _run_repl(
|
|
314
|
+
_args(interactive=True), [], ["/model", "/exit"], stderr=err,
|
|
315
|
+
)
|
|
316
|
+
self.assertEqual(rc, 0)
|
|
317
|
+
out = err.getvalue()
|
|
318
|
+
self.assertIn("model: llama-3.3-70b", out) # current still shown
|
|
319
|
+
self.assertIn("venice-uncensored", out) # ...plus the catalog list
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class _FakeRL:
|
|
323
|
+
"""Minimal readline stand-in: the completer needs only these two hooks."""
|
|
324
|
+
|
|
325
|
+
def __init__(self, buffer, begidx, *, doc=""):
|
|
326
|
+
self._buffer = buffer
|
|
327
|
+
self._begidx = begidx
|
|
328
|
+
self.__doc__ = doc
|
|
329
|
+
|
|
330
|
+
def get_line_buffer(self):
|
|
331
|
+
return self._buffer
|
|
332
|
+
|
|
333
|
+
def get_begidx(self):
|
|
334
|
+
return self._begidx
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
_CATALOG = [
|
|
338
|
+
{"id": "llama-3.3-70b", "model_spec": {"traits": ["default"]}},
|
|
339
|
+
{"id": "venice-uncensored", "model_spec": {"traits": []}},
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class TestReplCompletion(unittest.TestCase):
|
|
344
|
+
"""#40: the readline completer (unit-tested via an injected fake rl)."""
|
|
345
|
+
|
|
346
|
+
def _complete_all(self, buffer, begidx, text):
|
|
347
|
+
comp = _repl._make_completer(_CATALOG, _FakeRL(buffer, begidx))
|
|
348
|
+
out, state = [], 0
|
|
349
|
+
while (m := comp(text, state)) is not None:
|
|
350
|
+
out.append(m)
|
|
351
|
+
state += 1
|
|
352
|
+
return out
|
|
353
|
+
|
|
354
|
+
def test_commands_include_models(self):
|
|
355
|
+
self.assertIn("/models", _repl._COMMANDS)
|
|
356
|
+
|
|
357
|
+
def test_completes_slash_command(self):
|
|
358
|
+
self.assertEqual(self._complete_all("/mo", 0, "/mo"), ["/model", "/models"])
|
|
359
|
+
|
|
360
|
+
def test_completes_model_id_after_model(self):
|
|
361
|
+
self.assertEqual(
|
|
362
|
+
self._complete_all("/model ven", 7, "ven"), ["venice-uncensored"]
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def test_bare_model_space_lists_all_ids(self):
|
|
366
|
+
self.assertEqual(
|
|
367
|
+
self._complete_all("/model ", 7, ""),
|
|
368
|
+
["llama-3.3-70b", "venice-uncensored"],
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def test_no_completion_off_slash_lines(self):
|
|
372
|
+
self.assertEqual(self._complete_all("hello wor", 6, "wor"), [])
|
|
373
|
+
|
|
374
|
+
def test_other_commands_get_no_model_ids(self):
|
|
375
|
+
# after `/save ` we offer nothing (filename completion is deferred)
|
|
376
|
+
self.assertEqual(self._complete_all("/save ", 6, ""), [])
|
|
377
|
+
|
|
378
|
+
def test_completer_tolerates_empty_catalog(self):
|
|
379
|
+
comp = _repl._make_completer(None, _FakeRL("/model ", 7))
|
|
380
|
+
self.assertIsNone(comp("", 0)) # no ids, no crash
|
|
381
|
+
|
|
382
|
+
def test_install_completer_restores_on_exit(self):
|
|
383
|
+
class RL:
|
|
384
|
+
def __init__(self):
|
|
385
|
+
self.completer = "PREV"
|
|
386
|
+
self.delims = "PREVDELIMS"
|
|
387
|
+
self.bound = None
|
|
388
|
+
self.__doc__ = "" # not libedit -> "tab: complete"
|
|
389
|
+
|
|
390
|
+
def get_completer(self):
|
|
391
|
+
return self.completer
|
|
392
|
+
|
|
393
|
+
def get_completer_delims(self):
|
|
394
|
+
return self.delims
|
|
395
|
+
|
|
396
|
+
def set_completer(self, c):
|
|
397
|
+
self.completer = c
|
|
398
|
+
|
|
399
|
+
def set_completer_delims(self, d):
|
|
400
|
+
self.delims = d
|
|
401
|
+
|
|
402
|
+
def parse_and_bind(self, s):
|
|
403
|
+
self.bound = s
|
|
404
|
+
|
|
405
|
+
rl = RL()
|
|
406
|
+
with contextlib.ExitStack() as stack:
|
|
407
|
+
_repl._install_completer(rl, _CATALOG, stack)
|
|
408
|
+
self.assertTrue(callable(rl.completer)) # our completer is installed
|
|
409
|
+
self.assertEqual(rl.delims, " \t\n")
|
|
410
|
+
self.assertEqual(rl.bound, "tab: complete")
|
|
411
|
+
# leaving the REPL restores the prior completer + delims (no leak)
|
|
412
|
+
self.assertEqual(rl.completer, "PREV")
|
|
413
|
+
self.assertEqual(rl.delims, "PREVDELIMS")
|
|
414
|
+
|
|
293
415
|
|
|
294
416
|
if __name__ == "__main__":
|
|
295
417
|
unittest.main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.28.0"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|