venice-cli 0.35.0__tar.gz → 0.37.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.35.0 → venice_cli-0.37.0}/PKG-INFO +10 -2
- {venice_cli-0.35.0 → venice_cli-0.37.0}/README.md +9 -1
- venice_cli-0.37.0/src/venice/__init__.py +1 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_agent.py +65 -6
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_code.py +7 -3
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_mcp.py +25 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/chat.py +1 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/code.py +1 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_agent.py +67 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_chat.py +6 -4
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_code.py +3 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_mcp_tools.py +31 -0
- venice_cli-0.35.0/src/venice/__init__.py +0 -1
- {venice_cli-0.35.0 → venice_cli-0.37.0}/.gitignore +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/CONTRIBUTING.md +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/LICENSE +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/Makefile +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/SECURITY.md +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/bin/venice +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/install.sh +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/pyproject.toml +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/__main__.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/audio_player.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/audio_post.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/auth.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/billing.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/cli.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/client.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/__init__.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_audio.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_index.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_mcp_client.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_models.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_openai.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_queue.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_repl.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/_shared.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/balance.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/bg_remove.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/config.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/contact_sheet.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/embed.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/image.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/image_edit.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/index.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/login.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/master.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/mcp_serve.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/models.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/music.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/search.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/sfx.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/tts.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/upscale.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/commands/video.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/config.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/image_montage.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/mcp_server.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/src/venice/userconfig.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/__init__.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/_mcp_fake_server.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_auth.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_balance.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_bg_remove.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_client.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_code_command.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_config.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_contact_sheet.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_embed.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_image.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_image_edit.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_index.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_master.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_mcp_client.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_mcp_serve.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_models.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_music.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_repl.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_search.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_sfx.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_shared.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_shared_openai.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_tts.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_upscale.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.0}/tests/test_video.py +0 -0
- {venice_cli-0.35.0 → venice_cli-0.37.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.37.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
|
|
@@ -630,7 +630,9 @@ a read-only [semantic search](#semantic-search) over the project's local
|
|
|
630
630
|
`venice_models`, a read-only lookup that lists model ids for a given catalog
|
|
631
631
|
type (its single `type` arg — text/code/image/video/music/tts/embedding/upscale,
|
|
632
632
|
or `all`) so the model can pick a valid `model` for the other tools instead of
|
|
633
|
-
guessing
|
|
633
|
+
guessing, and `venice_model_details` (single `model` arg) which returns one
|
|
634
|
+
model's pricing (cost), input context/prompt-length limits, and capabilities so
|
|
635
|
+
the agent can budget its input. (`venice code` gets both too.)
|
|
634
636
|
|
|
635
637
|
```sh
|
|
636
638
|
# One command, multiple steps: the model generates an image, then critiques it.
|
|
@@ -1050,6 +1052,12 @@ safety), it should be settable in config." Currently config-backable:
|
|
|
1050
1052
|
Per-invocation flags (`--dry-run`, `--json`, `--resume`, `--seed`, inputs and
|
|
1051
1053
|
positionals) stay CLI-only by design.
|
|
1052
1054
|
|
|
1055
|
+
These per-command defaults also apply when a generator runs as an **agent tool**
|
|
1056
|
+
inside `venice chat --tools` and `venice code` — e.g. `defaults.image.safe_mode`
|
|
1057
|
+
is honored when the model calls `venice_image`, not just on the `venice image`
|
|
1058
|
+
CLI. An explicit argument the model puts in the tool call still wins over config.
|
|
1059
|
+
(`venice mcp-serve` doesn't yet thread config into its wrappers.)
|
|
1060
|
+
|
|
1053
1061
|
The **API key is never stored here** — it stays in
|
|
1054
1062
|
`~/.config/venice/credentials`. Unknown keys are preserved on write, so the
|
|
1055
1063
|
schema is forward-compatible.
|
|
@@ -592,7 +592,9 @@ a read-only [semantic search](#semantic-search) over the project's local
|
|
|
592
592
|
`venice_models`, a read-only lookup that lists model ids for a given catalog
|
|
593
593
|
type (its single `type` arg — text/code/image/video/music/tts/embedding/upscale,
|
|
594
594
|
or `all`) so the model can pick a valid `model` for the other tools instead of
|
|
595
|
-
guessing
|
|
595
|
+
guessing, and `venice_model_details` (single `model` arg) which returns one
|
|
596
|
+
model's pricing (cost), input context/prompt-length limits, and capabilities so
|
|
597
|
+
the agent can budget its input. (`venice code` gets both too.)
|
|
596
598
|
|
|
597
599
|
```sh
|
|
598
600
|
# One command, multiple steps: the model generates an image, then critiques it.
|
|
@@ -1012,6 +1014,12 @@ safety), it should be settable in config." Currently config-backable:
|
|
|
1012
1014
|
Per-invocation flags (`--dry-run`, `--json`, `--resume`, `--seed`, inputs and
|
|
1013
1015
|
positionals) stay CLI-only by design.
|
|
1014
1016
|
|
|
1017
|
+
These per-command defaults also apply when a generator runs as an **agent tool**
|
|
1018
|
+
inside `venice chat --tools` and `venice code` — e.g. `defaults.image.safe_mode`
|
|
1019
|
+
is honored when the model calls `venice_image`, not just on the `venice image`
|
|
1020
|
+
CLI. An explicit argument the model puts in the tool call still wins over config.
|
|
1021
|
+
(`venice mcp-serve` doesn't yet thread config into its wrappers.)
|
|
1022
|
+
|
|
1015
1023
|
The **API key is never stored here** — it stays in
|
|
1016
1024
|
`~/.config/venice/credentials`. Unknown keys are preserved on write, so the
|
|
1017
1025
|
schema is forward-compatible.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.37.0"
|
|
@@ -24,6 +24,7 @@ and passes the combined list to :func:`run_loop`. Nothing in the loop changes.
|
|
|
24
24
|
"""
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
+
import inspect
|
|
27
28
|
import itertools
|
|
28
29
|
import json
|
|
29
30
|
import sys
|
|
@@ -31,6 +32,7 @@ import threading
|
|
|
31
32
|
from dataclasses import dataclass
|
|
32
33
|
from typing import Callable, Dict, List, Optional
|
|
33
34
|
|
|
35
|
+
from .. import userconfig
|
|
34
36
|
from . import _mcp
|
|
35
37
|
from .models import MODEL_TYPES
|
|
36
38
|
|
|
@@ -196,6 +198,13 @@ _MODELS_SCHEMA = _obj(
|
|
|
196
198
|
required=["type"],
|
|
197
199
|
)
|
|
198
200
|
|
|
201
|
+
_MODEL_DETAILS_SCHEMA = _obj(
|
|
202
|
+
{
|
|
203
|
+
"model": _p("string", "The model id to describe (e.g. from venice_models)."),
|
|
204
|
+
},
|
|
205
|
+
required=["model"],
|
|
206
|
+
)
|
|
207
|
+
|
|
199
208
|
# Schema for a tool folded in ONLY via `only=` (e.g. `venice code --assets`), so it
|
|
200
209
|
# is not part of chat's default advertised set. Curated subset of
|
|
201
210
|
# `_mcp.image_edit_tool`; `confirm`/`max_spend`/`output_dir` omitted (loop-injected).
|
|
@@ -306,6 +315,15 @@ _BUILTINS = [
|
|
|
306
315
|
_MODELS_SCHEMA,
|
|
307
316
|
False,
|
|
308
317
|
),
|
|
318
|
+
(
|
|
319
|
+
"venice_model_details",
|
|
320
|
+
"model_details_tool",
|
|
321
|
+
"Get one model's details -- pricing (cost), input context/prompt-length "
|
|
322
|
+
"limits (your input budget), and capabilities -- so you can budget input "
|
|
323
|
+
"and confirm a model fits before using it. Read-only; not spend-gated.",
|
|
324
|
+
_MODEL_DETAILS_SCHEMA,
|
|
325
|
+
False,
|
|
326
|
+
),
|
|
309
327
|
(
|
|
310
328
|
"project_search",
|
|
311
329
|
"search_tool",
|
|
@@ -354,12 +372,20 @@ def _clean(arguments) -> dict:
|
|
|
354
372
|
return {k: v for k, v in arguments.items() if k not in _CONTROLLED}
|
|
355
373
|
|
|
356
374
|
|
|
375
|
+
def _tool_section(name: str) -> str:
|
|
376
|
+
"""Config section for a tool: `venice_image` -> `image` (matches userconfig
|
|
377
|
+
`_COMMAND_MAP` / the CLI command). Tools with no matching section (e.g.
|
|
378
|
+
`venice_models`, `project_search`) simply resolve nothing."""
|
|
379
|
+
return name[len("venice_"):] if name.startswith("venice_") else name
|
|
380
|
+
|
|
381
|
+
|
|
357
382
|
def builtin_tools(
|
|
358
383
|
client,
|
|
359
384
|
*,
|
|
360
385
|
max_spend: Optional[float] = None,
|
|
361
386
|
output_dir: Optional[str] = None,
|
|
362
387
|
only: Optional[set] = None,
|
|
388
|
+
config: Optional[dict] = None,
|
|
363
389
|
) -> List[Tool]:
|
|
364
390
|
"""Build the in-process venice tools, bound to `client`.
|
|
365
391
|
|
|
@@ -368,23 +394,56 @@ def builtin_tools(
|
|
|
368
394
|
unknown name raises ValueError so the caller can exit 2). With `only=None` the
|
|
369
395
|
set is exactly `_BUILTINS` (chat's default); passing `only=` also makes the
|
|
370
396
|
`_CODE_ASSET_BUILTINS` extras (e.g. `venice_image_edit`) selectable.
|
|
397
|
+
|
|
398
|
+
`config` is a userconfig doc (issue #58): `defaults.<section>.*` values are
|
|
399
|
+
layered UNDER the model's tool arguments, so an explicit tool arg still wins
|
|
400
|
+
(precedence: model arg > config default > tool hardcoded default). Only keys
|
|
401
|
+
in `userconfig._COMMAND_MAP[section]` (the #57 allow-list) that the tool
|
|
402
|
+
function actually accepts are injected.
|
|
371
403
|
"""
|
|
372
404
|
|
|
373
|
-
def
|
|
405
|
+
def _config_defaults(section, impl) -> dict:
|
|
406
|
+
if config is None:
|
|
407
|
+
return {}
|
|
408
|
+
section_map = userconfig._COMMAND_MAP.get(section)
|
|
409
|
+
if not section_map:
|
|
410
|
+
return {}
|
|
411
|
+
try:
|
|
412
|
+
params = set(inspect.signature(impl).parameters)
|
|
413
|
+
except (TypeError, ValueError):
|
|
414
|
+
return {}
|
|
415
|
+
out: dict = {}
|
|
416
|
+
for key, (dest, coerce) in section_map.items():
|
|
417
|
+
if dest not in params:
|
|
418
|
+
continue # tool doesn't take this preference
|
|
419
|
+
raw = userconfig.resolve_default(section, key, config)
|
|
420
|
+
if raw is None:
|
|
421
|
+
continue
|
|
422
|
+
try:
|
|
423
|
+
out[dest] = coerce(raw)
|
|
424
|
+
except (TypeError, ValueError):
|
|
425
|
+
pass # a bad config value shouldn't break tool building
|
|
426
|
+
return out
|
|
427
|
+
|
|
428
|
+
def _make_paid(impl, section):
|
|
429
|
+
defaults = _config_defaults(section, impl)
|
|
430
|
+
|
|
374
431
|
def invoke(arguments, *, confirm: bool = False):
|
|
375
432
|
return impl(
|
|
376
433
|
client,
|
|
377
434
|
confirm=confirm,
|
|
378
435
|
max_spend=max_spend,
|
|
379
436
|
output_dir=output_dir,
|
|
380
|
-
**_clean(arguments),
|
|
437
|
+
**{**defaults, **_clean(arguments)},
|
|
381
438
|
)
|
|
382
439
|
|
|
383
440
|
return invoke
|
|
384
441
|
|
|
385
|
-
def _make_free(impl):
|
|
442
|
+
def _make_free(impl, section):
|
|
443
|
+
defaults = _config_defaults(section, impl)
|
|
444
|
+
|
|
386
445
|
def invoke(arguments, *, confirm: bool = False):
|
|
387
|
-
return impl(client, **_clean(arguments))
|
|
446
|
+
return impl(client, **{**defaults, **_clean(arguments)})
|
|
388
447
|
|
|
389
448
|
return invoke
|
|
390
449
|
|
|
@@ -395,9 +454,9 @@ def builtin_tools(
|
|
|
395
454
|
description=desc,
|
|
396
455
|
parameters=schema,
|
|
397
456
|
invoke=(
|
|
398
|
-
_make_paid(getattr(_mcp, impl_name))
|
|
457
|
+
_make_paid(getattr(_mcp, impl_name), _tool_section(name))
|
|
399
458
|
if paid
|
|
400
|
-
else _make_free(getattr(_mcp, impl_name))
|
|
459
|
+
else _make_free(getattr(_mcp, impl_name), _tool_section(name))
|
|
401
460
|
),
|
|
402
461
|
paid=paid,
|
|
403
462
|
)
|
|
@@ -449,6 +449,7 @@ def code_tools(
|
|
|
449
449
|
include_search: bool = False,
|
|
450
450
|
assets: bool = False,
|
|
451
451
|
max_spend=None,
|
|
452
|
+
config=None,
|
|
452
453
|
) -> List[_agent.Tool]:
|
|
453
454
|
"""Build the coding tools bound to a realpath-resolved project `root`.
|
|
454
455
|
|
|
@@ -527,10 +528,12 @@ def code_tools(
|
|
|
527
528
|
_SEARCH_SCHEMA, search_invoke, paid=False,
|
|
528
529
|
))
|
|
529
530
|
|
|
530
|
-
#
|
|
531
|
-
# asset tools instead of guessing (
|
|
531
|
+
# Free model-catalog lookups so the agent can pick a valid `model` for the
|
|
532
|
+
# asset tools (and see cost/context limits) instead of guessing (need a client
|
|
533
|
+
# for the /models GET).
|
|
532
534
|
if client is not None:
|
|
533
|
-
tools.extend(_agent.builtin_tools(
|
|
535
|
+
tools.extend(_agent.builtin_tools(
|
|
536
|
+
client, only={"venice_models", "venice_model_details"}, config=config))
|
|
534
537
|
|
|
535
538
|
if assets and client is not None:
|
|
536
539
|
asset_dir = os.environ.get("VENICE_MCP_OUTPUT_DIR") or root
|
|
@@ -541,6 +544,7 @@ def code_tools(
|
|
|
541
544
|
"venice_music", "venice_tts", "venice_upscale", "venice_bg_remove",
|
|
542
545
|
"venice_video",
|
|
543
546
|
},
|
|
547
|
+
config=config, # #58: asset tools honor defaults.<cmd>.*
|
|
544
548
|
))
|
|
545
549
|
return tools
|
|
546
550
|
|
|
@@ -781,3 +781,28 @@ def models_tool(client, *, type: str) -> dict:
|
|
|
781
781
|
"models": by_type}
|
|
782
782
|
return {"status": "ok", "type": type,
|
|
783
783
|
"count": len(by_type[type]), "models": by_type[type]}
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def model_details_tool(client, *, model: str) -> dict:
|
|
787
|
+
"""Details for one model id: pricing (cost), context/prompt-length limits, and
|
|
788
|
+
capabilities -- so an agent can budget its input and confirm a model fits
|
|
789
|
+
before using it. Read-only; not spend-gated. May scan the catalog by type to
|
|
790
|
+
locate the id.
|
|
791
|
+
"""
|
|
792
|
+
if not model or not str(model).strip():
|
|
793
|
+
return _err("models: a model id is required")
|
|
794
|
+
m = _models_cmd._find_model(client, str(model).strip())
|
|
795
|
+
if m is None:
|
|
796
|
+
return _err(f"models: no model with id {model!r}")
|
|
797
|
+
spec = m.get("model_spec") if isinstance(m.get("model_spec"), dict) else {}
|
|
798
|
+
return {
|
|
799
|
+
"status": "ok",
|
|
800
|
+
"id": m.get("id"),
|
|
801
|
+
"type": m.get("type"),
|
|
802
|
+
"name": spec.get("name") or m.get("name"),
|
|
803
|
+
"pricing": spec.get("pricing"),
|
|
804
|
+
"available_context_tokens": spec.get("availableContextTokens"),
|
|
805
|
+
"prompt_character_limit": spec.get("promptCharacterLimit"),
|
|
806
|
+
"capabilities": spec.get("capabilities"),
|
|
807
|
+
"traits": spec.get("traits"),
|
|
808
|
+
}
|
|
@@ -338,6 +338,7 @@ def _tools_for(args, client, models, model):
|
|
|
338
338
|
max_spend=args.max_spend,
|
|
339
339
|
output_dir=str(args.output) if args.output else None,
|
|
340
340
|
only=set(args.tool) if args.tool else None,
|
|
341
|
+
config=userconfig.load_config(), # #58: honor defaults.<cmd>.* in tools
|
|
341
342
|
)
|
|
342
343
|
except ValueError as e:
|
|
343
344
|
print(f"chat: {e}", file=sys.stderr)
|
|
@@ -358,6 +358,7 @@ def _run(args) -> int:
|
|
|
358
358
|
exec_timeout=args.exec_timeout or _code.DEFAULT_EXEC_TIMEOUT,
|
|
359
359
|
include_search=True,
|
|
360
360
|
assets=bool(args.assets),
|
|
361
|
+
config=userconfig.load_config(), # #58: honor defaults.<cmd>.* in tools
|
|
361
362
|
)
|
|
362
363
|
system = _system_prompt(args, root, tools)
|
|
363
364
|
gen_kwargs = _gen_kwargs(args)
|
|
@@ -194,5 +194,72 @@ class TestProgress(unittest.TestCase):
|
|
|
194
194
|
self.assertIn("· t", err.getvalue()) # per-tool-call activity line
|
|
195
195
|
|
|
196
196
|
|
|
197
|
+
class TestToolSection(unittest.TestCase):
|
|
198
|
+
def test_section_derivation(self):
|
|
199
|
+
self.assertEqual(_agent._tool_section("venice_image"), "image")
|
|
200
|
+
self.assertEqual(_agent._tool_section("venice_image_edit"), "image_edit")
|
|
201
|
+
self.assertEqual(_agent._tool_section("project_search"), "project_search")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class TestConfigDefaults(unittest.TestCase):
|
|
205
|
+
"""#58: defaults.<cmd>.* are layered UNDER a tool's model-supplied args."""
|
|
206
|
+
|
|
207
|
+
def _spy(self):
|
|
208
|
+
captured = {}
|
|
209
|
+
|
|
210
|
+
def image_tool(client, prompt=None, *, hide_watermark=None, steps=None,
|
|
211
|
+
safe_mode=None, confirm=False, max_spend=None,
|
|
212
|
+
output_dir=None, **kw):
|
|
213
|
+
captured.update(hide_watermark=hide_watermark, steps=steps,
|
|
214
|
+
safe_mode=safe_mode)
|
|
215
|
+
captured.update(kw)
|
|
216
|
+
return {"status": "ok"}
|
|
217
|
+
|
|
218
|
+
return captured, image_tool
|
|
219
|
+
|
|
220
|
+
def _tool(self, spy, doc):
|
|
221
|
+
from venice.commands import _mcp
|
|
222
|
+
with mock.patch.object(_mcp, "image_tool", spy):
|
|
223
|
+
return _agent.builtin_tools(object(), config=doc,
|
|
224
|
+
only={"venice_image"})[0]
|
|
225
|
+
|
|
226
|
+
def test_injected_and_model_wins(self):
|
|
227
|
+
captured, spy = self._spy()
|
|
228
|
+
doc = {"defaults": {"image": {"hide_watermark": True, "steps": 40}}}
|
|
229
|
+
tool = self._tool(spy, doc)
|
|
230
|
+
tool.invoke({"prompt": "p"}) # model set no preference
|
|
231
|
+
self.assertIs(captured["hide_watermark"], True) # from config
|
|
232
|
+
self.assertEqual(captured["steps"], 40)
|
|
233
|
+
captured.clear()
|
|
234
|
+
tool.invoke({"prompt": "p", "hide_watermark": False, "steps": 5})
|
|
235
|
+
self.assertIs(captured["hide_watermark"], False) # explicit model arg wins
|
|
236
|
+
self.assertEqual(captured["steps"], 5)
|
|
237
|
+
|
|
238
|
+
def test_no_config_no_injection(self):
|
|
239
|
+
captured, spy = self._spy()
|
|
240
|
+
tool = self._tool(spy, None)
|
|
241
|
+
tool.invoke({"prompt": "p"})
|
|
242
|
+
self.assertIsNone(captured["hide_watermark"]) # tool's own default applies
|
|
243
|
+
self.assertIsNone(captured["steps"])
|
|
244
|
+
|
|
245
|
+
def test_only_accepted_allowlisted_keys_inject(self):
|
|
246
|
+
# `preset` is config-backable for image (#57) but image_tool takes no such
|
|
247
|
+
# param -> must NOT be injected; the accepted key still is.
|
|
248
|
+
captured, spy = self._spy()
|
|
249
|
+
doc = {"defaults": {"image": {"preset": "foo", "safe_mode": False}}}
|
|
250
|
+
tool = self._tool(spy, doc)
|
|
251
|
+
tool.invoke({"prompt": "p"})
|
|
252
|
+
self.assertNotIn("preset", captured)
|
|
253
|
+
self.assertIs(captured["safe_mode"], False)
|
|
254
|
+
|
|
255
|
+
def test_string_config_value_is_coerced(self):
|
|
256
|
+
captured, spy = self._spy()
|
|
257
|
+
doc = {"defaults": {"image": {"hide_watermark": "true", "steps": "12"}}}
|
|
258
|
+
tool = self._tool(spy, doc)
|
|
259
|
+
tool.invoke({"prompt": "p"})
|
|
260
|
+
self.assertIs(captured["hide_watermark"], True) # _as_bool
|
|
261
|
+
self.assertEqual(captured["steps"], 12) # int
|
|
262
|
+
|
|
263
|
+
|
|
197
264
|
if __name__ == "__main__":
|
|
198
265
|
unittest.main()
|
|
@@ -505,9 +505,10 @@ class TestChatAgent(unittest.TestCase):
|
|
|
505
505
|
f"{t['function']['name']} leaks control kwarg {banned}",
|
|
506
506
|
)
|
|
507
507
|
names = {t["function"]["name"] for t in tools}
|
|
508
|
-
self.assertEqual(len(names),
|
|
508
|
+
self.assertEqual(len(names), 10) # media/chat + project_search + models(+details)
|
|
509
509
|
self.assertIn("project_search", names)
|
|
510
510
|
self.assertIn("venice_models", names)
|
|
511
|
+
self.assertIn("venice_model_details", names)
|
|
511
512
|
|
|
512
513
|
def test_tool_error_surfaced_not_fatal(self):
|
|
513
514
|
seq = [
|
|
@@ -637,7 +638,7 @@ class TestChatMcp(unittest.TestCase):
|
|
|
637
638
|
names = self._tool_names(calls[0])
|
|
638
639
|
self.assertIn("fs__read", names) # remote tool advertised
|
|
639
640
|
self.assertIn("venice_image", names) # alongside the built-ins
|
|
640
|
-
self.assertEqual(len(names),
|
|
641
|
+
self.assertEqual(len(names), 11) # 10 built-ins + 1 remote
|
|
641
642
|
self.assertEqual(attach.specs, [("fs", {"command": "srv", "args": []})])
|
|
642
643
|
tool_msgs = [m for m in calls[1]["messages"] if m.get("role") == "tool"]
|
|
643
644
|
self.assertIn("127.0.0.1", tool_msgs[0]["content"])
|
|
@@ -651,7 +652,7 @@ class TestChatMcp(unittest.TestCase):
|
|
|
651
652
|
)
|
|
652
653
|
self.assertEqual(rc, 0)
|
|
653
654
|
attach.assert_not_called()
|
|
654
|
-
self.assertEqual(len(self._tool_names(calls[0])),
|
|
655
|
+
self.assertEqual(len(self._tool_names(calls[0])), 10) # built-ins only
|
|
655
656
|
|
|
656
657
|
def test_unknown_mcp_server_exits_2_before_model(self):
|
|
657
658
|
attach = mock.MagicMock()
|
|
@@ -739,8 +740,9 @@ class TestBuiltinToolsRegistry(unittest.TestCase):
|
|
|
739
740
|
# chat's default advertisement must not grow when code gains asset tools
|
|
740
741
|
from venice.commands import _agent
|
|
741
742
|
names = {t.name for t in _agent.builtin_tools(object())}
|
|
742
|
-
self.assertEqual(len(names),
|
|
743
|
+
self.assertEqual(len(names), 10) # +venice_models +venice_model_details (free)
|
|
743
744
|
self.assertIn("venice_models", names)
|
|
745
|
+
self.assertIn("venice_model_details", names)
|
|
744
746
|
self.assertNotIn("venice_image_edit", names)
|
|
745
747
|
|
|
746
748
|
def test_only_can_select_code_asset_extra(self):
|
|
@@ -249,11 +249,14 @@ class TestCodeFactory(unittest.TestCase):
|
|
|
249
249
|
def test_models_tool_present_with_client(self):
|
|
250
250
|
by = {t.name: t for t in _code.code_tools("/tmp", client=object())}
|
|
251
251
|
self.assertIn("venice_models", by) # free model-catalog lookup for the agent
|
|
252
|
+
self.assertIn("venice_model_details", by) # cost/context-limit lookup
|
|
252
253
|
self.assertFalse(by["venice_models"].paid) # read-only, not spend-gated
|
|
254
|
+
self.assertFalse(by["venice_model_details"].paid)
|
|
253
255
|
|
|
254
256
|
def test_models_tool_absent_without_client(self):
|
|
255
257
|
names = {t.name for t in _code.code_tools("/tmp")}
|
|
256
258
|
self.assertNotIn("venice_models", names) # needs a client for the /models GET
|
|
259
|
+
self.assertNotIn("venice_model_details", names)
|
|
257
260
|
|
|
258
261
|
def test_asset_tools_are_paid(self):
|
|
259
262
|
by = {t.name: t for t in _code.code_tools("/tmp", client=object(),
|
|
@@ -526,5 +526,36 @@ class TestModelsTool(_ToolTest):
|
|
|
526
526
|
self.assertIn("unknown type", out["message"])
|
|
527
527
|
|
|
528
528
|
|
|
529
|
+
class TestModelDetailsTool(_ToolTest):
|
|
530
|
+
def test_returns_curated_details(self):
|
|
531
|
+
# `text` is first in MODEL_TYPES, so _find_model matches on the first GET.
|
|
532
|
+
spec = {"name": "Big", "pricing": {"input": {"usd": 1.5}},
|
|
533
|
+
"availableContextTokens": 131072, "promptCharacterLimit": 1500,
|
|
534
|
+
"capabilities": {"supportsFunctionCalling": True},
|
|
535
|
+
"traits": ["default"]}
|
|
536
|
+
catalog = json.dumps(
|
|
537
|
+
{"data": [{"id": "big-model", "type": "text", "model_spec": spec}]}
|
|
538
|
+
).encode()
|
|
539
|
+
with mock.patch("venice.client.urllib.request.urlopen",
|
|
540
|
+
_seq(FakeResp(200, catalog))), self.stdout_guard():
|
|
541
|
+
out = _mcp.model_details_tool(_client(), model="big-model")
|
|
542
|
+
self.assertEqual(out["status"], "ok")
|
|
543
|
+
self.assertEqual(out["id"], "big-model")
|
|
544
|
+
self.assertEqual(out["available_context_tokens"], 131072)
|
|
545
|
+
self.assertEqual(out["prompt_character_limit"], 1500)
|
|
546
|
+
self.assertEqual(out["pricing"], {"input": {"usd": 1.5}})
|
|
547
|
+
self.assertTrue(out["capabilities"]["supportsFunctionCalling"])
|
|
548
|
+
|
|
549
|
+
def test_unknown_model_errors(self):
|
|
550
|
+
from venice.commands.models import MODEL_TYPES
|
|
551
|
+
empty = json.dumps({"data": []}).encode()
|
|
552
|
+
resps = _seq(*[FakeResp(200, empty) for _ in MODEL_TYPES])
|
|
553
|
+
with mock.patch("venice.client.urllib.request.urlopen", resps), \
|
|
554
|
+
self.stdout_guard():
|
|
555
|
+
out = _mcp.model_details_tool(_client(), model="nope")
|
|
556
|
+
self.assertEqual(out["status"], "error")
|
|
557
|
+
self.assertIn("no model", out["message"])
|
|
558
|
+
|
|
559
|
+
|
|
529
560
|
if __name__ == "__main__":
|
|
530
561
|
unittest.main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.35.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
|