venice-cli 0.36.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.
Files changed (86) hide show
  1. {venice_cli-0.36.0 → venice_cli-0.37.0}/PKG-INFO +4 -2
  2. {venice_cli-0.36.0 → venice_cli-0.37.0}/README.md +3 -1
  3. venice_cli-0.37.0/src/venice/__init__.py +1 -0
  4. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_agent.py +16 -0
  5. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_code.py +5 -4
  6. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_mcp.py +25 -0
  7. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_chat.py +6 -4
  8. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_code.py +3 -0
  9. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_mcp_tools.py +31 -0
  10. venice_cli-0.36.0/src/venice/__init__.py +0 -1
  11. {venice_cli-0.36.0 → venice_cli-0.37.0}/.gitignore +0 -0
  12. {venice_cli-0.36.0 → venice_cli-0.37.0}/CONTRIBUTING.md +0 -0
  13. {venice_cli-0.36.0 → venice_cli-0.37.0}/LICENSE +0 -0
  14. {venice_cli-0.36.0 → venice_cli-0.37.0}/Makefile +0 -0
  15. {venice_cli-0.36.0 → venice_cli-0.37.0}/SECURITY.md +0 -0
  16. {venice_cli-0.36.0 → venice_cli-0.37.0}/bin/venice +0 -0
  17. {venice_cli-0.36.0 → venice_cli-0.37.0}/install.sh +0 -0
  18. {venice_cli-0.36.0 → venice_cli-0.37.0}/pyproject.toml +0 -0
  19. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/__main__.py +0 -0
  20. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/audio_player.py +0 -0
  21. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/audio_post.py +0 -0
  22. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/auth.py +0 -0
  23. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/billing.py +0 -0
  24. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/cli.py +0 -0
  25. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/client.py +0 -0
  26. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/__init__.py +0 -0
  27. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_audio.py +0 -0
  28. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_index.py +0 -0
  29. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_mcp_client.py +0 -0
  30. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_models.py +0 -0
  31. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_openai.py +0 -0
  32. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_queue.py +0 -0
  33. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_repl.py +0 -0
  34. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/_shared.py +0 -0
  35. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/balance.py +0 -0
  36. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/bg_remove.py +0 -0
  37. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/chat.py +0 -0
  38. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/code.py +0 -0
  39. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/config.py +0 -0
  40. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/contact_sheet.py +0 -0
  41. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/embed.py +0 -0
  42. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/image.py +0 -0
  43. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/image_edit.py +0 -0
  44. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/index.py +0 -0
  45. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/login.py +0 -0
  46. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/master.py +0 -0
  47. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/mcp_serve.py +0 -0
  48. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/models.py +0 -0
  49. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/music.py +0 -0
  50. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/search.py +0 -0
  51. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/sfx.py +0 -0
  52. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/tts.py +0 -0
  53. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/upscale.py +0 -0
  54. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/commands/video.py +0 -0
  55. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/config.py +0 -0
  56. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/image_montage.py +0 -0
  57. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/mcp_server.py +0 -0
  58. {venice_cli-0.36.0 → venice_cli-0.37.0}/src/venice/userconfig.py +0 -0
  59. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/__init__.py +0 -0
  60. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/_mcp_fake_server.py +0 -0
  61. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_agent.py +0 -0
  62. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_auth.py +0 -0
  63. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_balance.py +0 -0
  64. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_bg_remove.py +0 -0
  65. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_client.py +0 -0
  66. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_code_command.py +0 -0
  67. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_config.py +0 -0
  68. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_contact_sheet.py +0 -0
  69. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_embed.py +0 -0
  70. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_image.py +0 -0
  71. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_image_edit.py +0 -0
  72. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_index.py +0 -0
  73. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_master.py +0 -0
  74. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_mcp_client.py +0 -0
  75. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_mcp_serve.py +0 -0
  76. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_models.py +0 -0
  77. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_music.py +0 -0
  78. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_repl.py +0 -0
  79. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_search.py +0 -0
  80. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_sfx.py +0 -0
  81. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_shared.py +0 -0
  82. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_shared_openai.py +0 -0
  83. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_tts.py +0 -0
  84. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_upscale.py +0 -0
  85. {venice_cli-0.36.0 → venice_cli-0.37.0}/tests/test_video.py +0 -0
  86. {venice_cli-0.36.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.36.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. (`venice code` gets `venice_models` too.)
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.
@@ -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. (`venice code` gets `venice_models` too.)
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.
@@ -0,0 +1 @@
1
+ __version__ = "0.37.0"
@@ -198,6 +198,13 @@ _MODELS_SCHEMA = _obj(
198
198
  required=["type"],
199
199
  )
200
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
+
201
208
  # Schema for a tool folded in ONLY via `only=` (e.g. `venice code --assets`), so it
202
209
  # is not part of chat's default advertised set. Curated subset of
203
210
  # `_mcp.image_edit_tool`; `confirm`/`max_spend`/`output_dir` omitted (loop-injected).
@@ -308,6 +315,15 @@ _BUILTINS = [
308
315
  _MODELS_SCHEMA,
309
316
  False,
310
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
+ ),
311
327
  (
312
328
  "project_search",
313
329
  "search_tool",
@@ -528,11 +528,12 @@ def code_tools(
528
528
  _SEARCH_SCHEMA, search_invoke, paid=False,
529
529
  ))
530
530
 
531
- # A free model-catalog lookup so the agent can pick a valid `model` for the
532
- # asset tools instead of guessing (needs a client for the /models GET).
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).
533
534
  if client is not None:
534
- tools.extend(_agent.builtin_tools(client, only={"venice_models"},
535
- config=config))
535
+ tools.extend(_agent.builtin_tools(
536
+ client, only={"venice_models", "venice_model_details"}, config=config))
536
537
 
537
538
  if assets and client is not None:
538
539
  asset_dir = os.environ.get("VENICE_MCP_OUTPUT_DIR") or root
@@ -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
+ }
@@ -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), 9) # media/chat + project_search + venice_models
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), 10) # 9 built-ins + 1 remote
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])), 9) # built-ins only
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), 9) # +venice_models (a deliberate free chat tool)
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.36.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