venice-cli 0.35.0__tar.gz → 0.36.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.
Files changed (86) hide show
  1. {venice_cli-0.35.0 → venice_cli-0.36.0}/PKG-INFO +7 -1
  2. {venice_cli-0.35.0 → venice_cli-0.36.0}/README.md +6 -0
  3. venice_cli-0.36.0/src/venice/__init__.py +1 -0
  4. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_agent.py +49 -6
  5. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_code.py +4 -1
  6. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/chat.py +1 -0
  7. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/code.py +1 -0
  8. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_agent.py +67 -0
  9. venice_cli-0.35.0/src/venice/__init__.py +0 -1
  10. {venice_cli-0.35.0 → venice_cli-0.36.0}/.gitignore +0 -0
  11. {venice_cli-0.35.0 → venice_cli-0.36.0}/CONTRIBUTING.md +0 -0
  12. {venice_cli-0.35.0 → venice_cli-0.36.0}/LICENSE +0 -0
  13. {venice_cli-0.35.0 → venice_cli-0.36.0}/Makefile +0 -0
  14. {venice_cli-0.35.0 → venice_cli-0.36.0}/SECURITY.md +0 -0
  15. {venice_cli-0.35.0 → venice_cli-0.36.0}/bin/venice +0 -0
  16. {venice_cli-0.35.0 → venice_cli-0.36.0}/install.sh +0 -0
  17. {venice_cli-0.35.0 → venice_cli-0.36.0}/pyproject.toml +0 -0
  18. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/__main__.py +0 -0
  19. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/audio_player.py +0 -0
  20. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/audio_post.py +0 -0
  21. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/auth.py +0 -0
  22. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/billing.py +0 -0
  23. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/cli.py +0 -0
  24. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/client.py +0 -0
  25. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/__init__.py +0 -0
  26. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_audio.py +0 -0
  27. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_index.py +0 -0
  28. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_mcp.py +0 -0
  29. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_mcp_client.py +0 -0
  30. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_models.py +0 -0
  31. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_openai.py +0 -0
  32. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_queue.py +0 -0
  33. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_repl.py +0 -0
  34. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/_shared.py +0 -0
  35. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/balance.py +0 -0
  36. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/bg_remove.py +0 -0
  37. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/config.py +0 -0
  38. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/contact_sheet.py +0 -0
  39. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/embed.py +0 -0
  40. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/image.py +0 -0
  41. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/image_edit.py +0 -0
  42. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/index.py +0 -0
  43. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/login.py +0 -0
  44. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/master.py +0 -0
  45. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/mcp_serve.py +0 -0
  46. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/models.py +0 -0
  47. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/music.py +0 -0
  48. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/search.py +0 -0
  49. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/sfx.py +0 -0
  50. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/tts.py +0 -0
  51. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/upscale.py +0 -0
  52. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/commands/video.py +0 -0
  53. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/config.py +0 -0
  54. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/image_montage.py +0 -0
  55. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/mcp_server.py +0 -0
  56. {venice_cli-0.35.0 → venice_cli-0.36.0}/src/venice/userconfig.py +0 -0
  57. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/__init__.py +0 -0
  58. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/_mcp_fake_server.py +0 -0
  59. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_auth.py +0 -0
  60. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_balance.py +0 -0
  61. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_bg_remove.py +0 -0
  62. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_chat.py +0 -0
  63. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_client.py +0 -0
  64. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_code.py +0 -0
  65. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_code_command.py +0 -0
  66. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_config.py +0 -0
  67. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_contact_sheet.py +0 -0
  68. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_embed.py +0 -0
  69. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_image.py +0 -0
  70. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_image_edit.py +0 -0
  71. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_index.py +0 -0
  72. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_master.py +0 -0
  73. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_mcp_client.py +0 -0
  74. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_mcp_serve.py +0 -0
  75. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_mcp_tools.py +0 -0
  76. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_models.py +0 -0
  77. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_music.py +0 -0
  78. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_repl.py +0 -0
  79. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_search.py +0 -0
  80. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_sfx.py +0 -0
  81. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_shared.py +0 -0
  82. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_shared_openai.py +0 -0
  83. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_tts.py +0 -0
  84. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_upscale.py +0 -0
  85. {venice_cli-0.35.0 → venice_cli-0.36.0}/tests/test_video.py +0 -0
  86. {venice_cli-0.35.0 → venice_cli-0.36.0}/uninstall.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: venice-cli
3
- Version: 0.35.0
3
+ Version: 0.36.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
@@ -1050,6 +1050,12 @@ safety), it should be settable in config." Currently config-backable:
1050
1050
  Per-invocation flags (`--dry-run`, `--json`, `--resume`, `--seed`, inputs and
1051
1051
  positionals) stay CLI-only by design.
1052
1052
 
1053
+ These per-command defaults also apply when a generator runs as an **agent tool**
1054
+ inside `venice chat --tools` and `venice code` — e.g. `defaults.image.safe_mode`
1055
+ is honored when the model calls `venice_image`, not just on the `venice image`
1056
+ CLI. An explicit argument the model puts in the tool call still wins over config.
1057
+ (`venice mcp-serve` doesn't yet thread config into its wrappers.)
1058
+
1053
1059
  The **API key is never stored here** — it stays in
1054
1060
  `~/.config/venice/credentials`. Unknown keys are preserved on write, so the
1055
1061
  schema is forward-compatible.
@@ -1012,6 +1012,12 @@ safety), it should be settable in config." Currently config-backable:
1012
1012
  Per-invocation flags (`--dry-run`, `--json`, `--resume`, `--seed`, inputs and
1013
1013
  positionals) stay CLI-only by design.
1014
1014
 
1015
+ These per-command defaults also apply when a generator runs as an **agent tool**
1016
+ inside `venice chat --tools` and `venice code` — e.g. `defaults.image.safe_mode`
1017
+ is honored when the model calls `venice_image`, not just on the `venice image`
1018
+ CLI. An explicit argument the model puts in the tool call still wins over config.
1019
+ (`venice mcp-serve` doesn't yet thread config into its wrappers.)
1020
+
1015
1021
  The **API key is never stored here** — it stays in
1016
1022
  `~/.config/venice/credentials`. Unknown keys are preserved on write, so the
1017
1023
  schema is forward-compatible.
@@ -0,0 +1 @@
1
+ __version__ = "0.36.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
 
@@ -354,12 +356,20 @@ def _clean(arguments) -> dict:
354
356
  return {k: v for k, v in arguments.items() if k not in _CONTROLLED}
355
357
 
356
358
 
359
+ def _tool_section(name: str) -> str:
360
+ """Config section for a tool: `venice_image` -> `image` (matches userconfig
361
+ `_COMMAND_MAP` / the CLI command). Tools with no matching section (e.g.
362
+ `venice_models`, `project_search`) simply resolve nothing."""
363
+ return name[len("venice_"):] if name.startswith("venice_") else name
364
+
365
+
357
366
  def builtin_tools(
358
367
  client,
359
368
  *,
360
369
  max_spend: Optional[float] = None,
361
370
  output_dir: Optional[str] = None,
362
371
  only: Optional[set] = None,
372
+ config: Optional[dict] = None,
363
373
  ) -> List[Tool]:
364
374
  """Build the in-process venice tools, bound to `client`.
365
375
 
@@ -368,23 +378,56 @@ def builtin_tools(
368
378
  unknown name raises ValueError so the caller can exit 2). With `only=None` the
369
379
  set is exactly `_BUILTINS` (chat's default); passing `only=` also makes the
370
380
  `_CODE_ASSET_BUILTINS` extras (e.g. `venice_image_edit`) selectable.
381
+
382
+ `config` is a userconfig doc (issue #58): `defaults.<section>.*` values are
383
+ layered UNDER the model's tool arguments, so an explicit tool arg still wins
384
+ (precedence: model arg > config default > tool hardcoded default). Only keys
385
+ in `userconfig._COMMAND_MAP[section]` (the #57 allow-list) that the tool
386
+ function actually accepts are injected.
371
387
  """
372
388
 
373
- def _make_paid(impl):
389
+ def _config_defaults(section, impl) -> dict:
390
+ if config is None:
391
+ return {}
392
+ section_map = userconfig._COMMAND_MAP.get(section)
393
+ if not section_map:
394
+ return {}
395
+ try:
396
+ params = set(inspect.signature(impl).parameters)
397
+ except (TypeError, ValueError):
398
+ return {}
399
+ out: dict = {}
400
+ for key, (dest, coerce) in section_map.items():
401
+ if dest not in params:
402
+ continue # tool doesn't take this preference
403
+ raw = userconfig.resolve_default(section, key, config)
404
+ if raw is None:
405
+ continue
406
+ try:
407
+ out[dest] = coerce(raw)
408
+ except (TypeError, ValueError):
409
+ pass # a bad config value shouldn't break tool building
410
+ return out
411
+
412
+ def _make_paid(impl, section):
413
+ defaults = _config_defaults(section, impl)
414
+
374
415
  def invoke(arguments, *, confirm: bool = False):
375
416
  return impl(
376
417
  client,
377
418
  confirm=confirm,
378
419
  max_spend=max_spend,
379
420
  output_dir=output_dir,
380
- **_clean(arguments),
421
+ **{**defaults, **_clean(arguments)},
381
422
  )
382
423
 
383
424
  return invoke
384
425
 
385
- def _make_free(impl):
426
+ def _make_free(impl, section):
427
+ defaults = _config_defaults(section, impl)
428
+
386
429
  def invoke(arguments, *, confirm: bool = False):
387
- return impl(client, **_clean(arguments))
430
+ return impl(client, **{**defaults, **_clean(arguments)})
388
431
 
389
432
  return invoke
390
433
 
@@ -395,9 +438,9 @@ def builtin_tools(
395
438
  description=desc,
396
439
  parameters=schema,
397
440
  invoke=(
398
- _make_paid(getattr(_mcp, impl_name))
441
+ _make_paid(getattr(_mcp, impl_name), _tool_section(name))
399
442
  if paid
400
- else _make_free(getattr(_mcp, impl_name))
443
+ else _make_free(getattr(_mcp, impl_name), _tool_section(name))
401
444
  ),
402
445
  paid=paid,
403
446
  )
@@ -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
 
@@ -530,7 +531,8 @@ def code_tools(
530
531
  # A free model-catalog lookup so the agent can pick a valid `model` for the
531
532
  # asset tools instead of guessing (needs a client for the /models GET).
532
533
  if client is not None:
533
- tools.extend(_agent.builtin_tools(client, only={"venice_models"}))
534
+ tools.extend(_agent.builtin_tools(client, only={"venice_models"},
535
+ config=config))
534
536
 
535
537
  if assets and client is not None:
536
538
  asset_dir = os.environ.get("VENICE_MCP_OUTPUT_DIR") or root
@@ -541,6 +543,7 @@ def code_tools(
541
543
  "venice_music", "venice_tts", "venice_upscale", "venice_bg_remove",
542
544
  "venice_video",
543
545
  },
546
+ config=config, # #58: asset tools honor defaults.<cmd>.*
544
547
  ))
545
548
  return tools
546
549
 
@@ -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()
@@ -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