@voria/cli 0.0.2

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 (67) hide show
  1. package/README.md +439 -0
  2. package/bin/voria +730 -0
  3. package/docs/ARCHITECTURE.md +419 -0
  4. package/docs/CHANGELOG.md +189 -0
  5. package/docs/CONTRIBUTING.md +447 -0
  6. package/docs/DESIGN_DECISIONS.md +380 -0
  7. package/docs/DEVELOPMENT.md +535 -0
  8. package/docs/EXAMPLES.md +434 -0
  9. package/docs/INSTALL.md +335 -0
  10. package/docs/IPC_PROTOCOL.md +310 -0
  11. package/docs/LLM_INTEGRATION.md +416 -0
  12. package/docs/MODULES.md +470 -0
  13. package/docs/PERFORMANCE.md +346 -0
  14. package/docs/PLUGINS.md +432 -0
  15. package/docs/QUICKSTART.md +184 -0
  16. package/docs/README.md +133 -0
  17. package/docs/ROADMAP.md +346 -0
  18. package/docs/SECURITY.md +334 -0
  19. package/docs/TROUBLESHOOTING.md +565 -0
  20. package/docs/USER_GUIDE.md +700 -0
  21. package/package.json +63 -0
  22. package/python/voria/__init__.py +8 -0
  23. package/python/voria/__pycache__/__init__.cpython-312.pyc +0 -0
  24. package/python/voria/__pycache__/engine.cpython-312.pyc +0 -0
  25. package/python/voria/core/__init__.py +1 -0
  26. package/python/voria/core/__pycache__/__init__.cpython-312.pyc +0 -0
  27. package/python/voria/core/__pycache__/setup.cpython-312.pyc +0 -0
  28. package/python/voria/core/agent/__init__.py +9 -0
  29. package/python/voria/core/agent/__pycache__/__init__.cpython-312.pyc +0 -0
  30. package/python/voria/core/agent/__pycache__/loop.cpython-312.pyc +0 -0
  31. package/python/voria/core/agent/loop.py +343 -0
  32. package/python/voria/core/executor/__init__.py +19 -0
  33. package/python/voria/core/executor/__pycache__/__init__.cpython-312.pyc +0 -0
  34. package/python/voria/core/executor/__pycache__/executor.cpython-312.pyc +0 -0
  35. package/python/voria/core/executor/executor.py +431 -0
  36. package/python/voria/core/github/__init__.py +33 -0
  37. package/python/voria/core/github/__pycache__/__init__.cpython-312.pyc +0 -0
  38. package/python/voria/core/github/__pycache__/client.cpython-312.pyc +0 -0
  39. package/python/voria/core/github/client.py +438 -0
  40. package/python/voria/core/llm/__init__.py +55 -0
  41. package/python/voria/core/llm/__pycache__/__init__.cpython-312.pyc +0 -0
  42. package/python/voria/core/llm/__pycache__/base.cpython-312.pyc +0 -0
  43. package/python/voria/core/llm/__pycache__/claude_provider.cpython-312.pyc +0 -0
  44. package/python/voria/core/llm/__pycache__/gemini_provider.cpython-312.pyc +0 -0
  45. package/python/voria/core/llm/__pycache__/modal_provider.cpython-312.pyc +0 -0
  46. package/python/voria/core/llm/__pycache__/model_discovery.cpython-312.pyc +0 -0
  47. package/python/voria/core/llm/__pycache__/openai_provider.cpython-312.pyc +0 -0
  48. package/python/voria/core/llm/base.py +152 -0
  49. package/python/voria/core/llm/claude_provider.py +188 -0
  50. package/python/voria/core/llm/gemini_provider.py +148 -0
  51. package/python/voria/core/llm/modal_provider.py +228 -0
  52. package/python/voria/core/llm/model_discovery.py +289 -0
  53. package/python/voria/core/llm/openai_provider.py +146 -0
  54. package/python/voria/core/patcher/__init__.py +9 -0
  55. package/python/voria/core/patcher/__pycache__/__init__.cpython-312.pyc +0 -0
  56. package/python/voria/core/patcher/__pycache__/patcher.cpython-312.pyc +0 -0
  57. package/python/voria/core/patcher/patcher.py +375 -0
  58. package/python/voria/core/planner/__init__.py +1 -0
  59. package/python/voria/core/setup.py +201 -0
  60. package/python/voria/core/token_manager/__init__.py +29 -0
  61. package/python/voria/core/token_manager/__pycache__/__init__.cpython-312.pyc +0 -0
  62. package/python/voria/core/token_manager/__pycache__/manager.cpython-312.pyc +0 -0
  63. package/python/voria/core/token_manager/manager.py +241 -0
  64. package/python/voria/engine.py +1185 -0
  65. package/python/voria/plugins/__init__.py +1 -0
  66. package/python/voria/plugins/python/__init__.py +1 -0
  67. package/python/voria/plugins/typescript/__init__.py +1 -0
@@ -0,0 +1,241 @@
1
+ """Token Usage Management Module
2
+
3
+ Tracks LLM token usage and costs across all providers.
4
+ """
5
+
6
+ import logging
7
+ from typing import Dict, Any
8
+ from dataclasses import dataclass, field
9
+ from datetime import datetime
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ # Provider pricing (per 1M tokens)
15
+ PRICING = {
16
+ "modal": {
17
+ "zai-org/GLM-5.1-FP8": {"input": 0.0, "output": 0.0} # Free until April 30th
18
+ },
19
+ "openai": {
20
+ "gpt-4": {"input": 0.03, "output": 0.06},
21
+ "gpt-3.5-turbo": {"input": 0.0005, "output": 0.0015},
22
+ },
23
+ "gemini": {
24
+ "gemini-pro": {"input": 0.0005, "output": 0.0015},
25
+ },
26
+ "claude": {
27
+ "claude-3-opus-20240229": {"input": 0.015, "output": 0.075},
28
+ "claude-3-sonnet-20240229": {"input": 0.003, "output": 0.015},
29
+ "claude-3-haiku-20240307": {"input": 0.00025, "output": 0.00125},
30
+ },
31
+ }
32
+
33
+
34
+ @dataclass
35
+ class TokenUsageRecord:
36
+ """Record of a single token usage event"""
37
+
38
+ provider: str
39
+ model: str
40
+ input_tokens: int
41
+ output_tokens: int
42
+ total_tokens: int
43
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
44
+ cost: float = 0.0
45
+
46
+
47
+ @dataclass
48
+ class TokenBudget:
49
+ """Token budget configuration"""
50
+
51
+ max_tokens: int = 100000 # Total token budget
52
+ warning_threshold: float = 0.7 # Warn at 70% usage
53
+ stop_threshold: float = 0.9 # Stop at 90% usage
54
+ max_cost: float = 100.0 # Stop at $100
55
+
56
+
57
+ class TokenManager:
58
+ """Manages token usage and budget tracking"""
59
+
60
+ def __init__(self, budget: TokenBudget = None):
61
+ """
62
+ Initialize token manager
63
+
64
+ Args:
65
+ budget: Token budget configuration
66
+ """
67
+ self.budget = budget or TokenBudget()
68
+ self.records: list[TokenUsageRecord] = []
69
+ self.total_input_tokens = 0
70
+ self.total_output_tokens = 0
71
+ self.total_cost = 0.0
72
+
73
+ def record_usage(
74
+ self, provider: str, model: str, input_tokens: int, output_tokens: int
75
+ ) -> TokenUsageRecord:
76
+ """
77
+ Record token usage from an LLM call
78
+
79
+ Args:
80
+ provider: LLM provider name
81
+ model: Model identifier
82
+ input_tokens: Input token count
83
+ output_tokens: Output token count
84
+
85
+ Returns:
86
+ TokenUsageRecord
87
+ """
88
+ total_tokens = input_tokens + output_tokens
89
+ cost = self._calculate_cost(provider, model, input_tokens, output_tokens)
90
+
91
+ record = TokenUsageRecord(
92
+ provider=provider,
93
+ model=model,
94
+ input_tokens=input_tokens,
95
+ output_tokens=output_tokens,
96
+ total_tokens=total_tokens,
97
+ cost=cost,
98
+ )
99
+
100
+ self.records.append(record)
101
+ self.total_input_tokens += input_tokens
102
+ self.total_output_tokens += output_tokens
103
+ self.total_cost += cost
104
+
105
+ logger.info(
106
+ f"Recorded: {total_tokens} tokens ({provider}/{model}), "
107
+ f"Cost: ${cost:.4f}, Total cost: ${self.total_cost:.2f}"
108
+ )
109
+
110
+ self._check_limits()
111
+
112
+ return record
113
+
114
+ def _calculate_cost(
115
+ self, provider: str, model: str, input_tokens: int, output_tokens: int
116
+ ) -> float:
117
+ """Calculate cost for token usage"""
118
+ try:
119
+ pricing = PRICING.get(provider, {}).get(model)
120
+ if not pricing:
121
+ logger.warning(f"No pricing data for {provider}/{model}")
122
+ return 0.0
123
+
124
+ input_cost = (input_tokens / 1_000_000) * pricing.get("input", 0)
125
+ output_cost = (output_tokens / 1_000_000) * pricing.get("output", 0)
126
+
127
+ return input_cost + output_cost
128
+
129
+ except Exception as e:
130
+ logger.error(f"Error calculating cost: {e}")
131
+ return 0.0
132
+
133
+ def _check_limits(self) -> None:
134
+ """Check if token usage exceeds limits"""
135
+ total_tokens = self.total_input_tokens + self.total_output_tokens
136
+ usage_percent = total_tokens / self.budget.max_tokens
137
+
138
+ if usage_percent >= self.budget.stop_threshold:
139
+ logger.warning(
140
+ f"Token usage at {usage_percent*100:.1f}% threshold "
141
+ f"({total_tokens}/{self.budget.max_tokens})"
142
+ )
143
+
144
+ if self.total_cost >= self.budget.max_cost:
145
+ logger.warning(
146
+ f"Cost limit reached: ${self.total_cost:.2f} "
147
+ f"(budget: ${self.budget.max_cost})"
148
+ )
149
+
150
+ def get_remaining_tokens(self) -> int:
151
+ """Get remaining tokens in budget"""
152
+ total = self.total_input_tokens + self.total_output_tokens
153
+ return max(0, self.budget.max_tokens - total)
154
+
155
+ def get_usage_summary(self) -> Dict[str, Any]:
156
+ """Get usage summary statistics"""
157
+ total_tokens = self.total_input_tokens + self.total_output_tokens
158
+ usage_percent = (total_tokens / self.budget.max_tokens) * 100
159
+
160
+ # Group by provider
161
+ by_provider = {}
162
+ for record in self.records:
163
+ if record.provider not in by_provider:
164
+ by_provider[record.provider] = {"calls": 0, "tokens": 0, "cost": 0.0}
165
+ by_provider[record.provider]["calls"] += 1
166
+ by_provider[record.provider]["tokens"] += record.total_tokens
167
+ by_provider[record.provider]["cost"] += record.cost
168
+
169
+ return {
170
+ "total_tokens": total_tokens,
171
+ "input_tokens": self.total_input_tokens,
172
+ "output_tokens": self.total_output_tokens,
173
+ "remaining_tokens": self.get_remaining_tokens(),
174
+ "usage_percent": usage_percent,
175
+ "total_cost": self.total_cost,
176
+ "budget": {
177
+ "max_tokens": self.budget.max_tokens,
178
+ "max_cost": self.budget.max_cost,
179
+ "warning_threshold": self.budget.warning_threshold,
180
+ "stop_threshold": self.budget.stop_threshold,
181
+ },
182
+ "by_provider": by_provider,
183
+ "num_calls": len(self.records),
184
+ }
185
+
186
+ def should_stop(self) -> bool:
187
+ """Check if processing should stop due to limits"""
188
+ total = self.total_input_tokens + self.total_output_tokens
189
+
190
+ if total >= self.budget.max_tokens * self.budget.stop_threshold:
191
+ return True
192
+
193
+ if self.total_cost >= self.budget.max_cost:
194
+ return True
195
+
196
+ return False
197
+
198
+ def log_summary(self) -> None:
199
+ """Log usage summary"""
200
+ summary = self.get_usage_summary()
201
+
202
+ logger.info("=" * 60)
203
+ logger.info("Token Usage Summary")
204
+ logger.info("=" * 60)
205
+ logger.info(f"Total tokens: {summary['total_tokens']:,}")
206
+ logger.info(f" Input: {summary['input_tokens']:,}")
207
+ logger.info(f" Output: {summary['output_tokens']:,}")
208
+ logger.info(f" Remaining: {summary['remaining_tokens']:,}")
209
+ logger.info(f"Usage: {summary['usage_percent']:.1f}% of budget")
210
+ logger.info(f"Total cost: ${summary['total_cost']:.2f}")
211
+
212
+ if summary["by_provider"]:
213
+ logger.info("\nBy Provider:")
214
+ for provider, stats in summary["by_provider"].items():
215
+ logger.info(
216
+ f" {provider}: "
217
+ f"{stats['calls']} calls, "
218
+ f"{stats['tokens']:,} tokens, "
219
+ f"${stats['cost']:.2f}"
220
+ )
221
+
222
+ logger.info("=" * 60)
223
+
224
+
225
+ # Global token manager instance
226
+ _token_manager: TokenManager = None
227
+
228
+
229
+ def get_token_manager() -> TokenManager:
230
+ """Get or create global token manager"""
231
+ global _token_manager
232
+ if _token_manager is None:
233
+ _token_manager = TokenManager()
234
+ return _token_manager
235
+
236
+
237
+ def init_token_manager(budget: TokenBudget) -> TokenManager:
238
+ """Initialize global token manager with custom budget"""
239
+ global _token_manager
240
+ _token_manager = TokenManager(budget)
241
+ return _token_manager