velune-cli 0.9.0__py3-none-any.whl
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.
- velune/__init__.py +5 -0
- velune/__main__.py +6 -0
- velune/cli/__init__.py +5 -0
- velune/cli/app.py +208 -0
- velune/cli/autocomplete.py +80 -0
- velune/cli/banner.py +60 -0
- velune/cli/commands/__init__.py +32 -0
- velune/cli/commands/ask.py +175 -0
- velune/cli/commands/base.py +16 -0
- velune/cli/commands/chat.py +228 -0
- velune/cli/commands/config.py +224 -0
- velune/cli/commands/daemon.py +88 -0
- velune/cli/commands/doctor.py +721 -0
- velune/cli/commands/init.py +170 -0
- velune/cli/commands/mcp.py +82 -0
- velune/cli/commands/memory.py +293 -0
- velune/cli/commands/models.py +683 -0
- velune/cli/commands/preflight.py +95 -0
- velune/cli/commands/run.py +270 -0
- velune/cli/commands/setup.py +184 -0
- velune/cli/commands/workspace.py +249 -0
- velune/cli/context.py +36 -0
- velune/cli/councilmodel_ui.py +199 -0
- velune/cli/display/council_view.py +254 -0
- velune/cli/display/memory_view.py +126 -0
- velune/cli/display/panels.py +35 -0
- velune/cli/display/progress.py +25 -0
- velune/cli/display/themes.py +25 -0
- velune/cli/main.py +15 -0
- velune/cli/model_selector.py +51 -0
- velune/cli/modes.py +86 -0
- velune/cli/pull_ui.py +123 -0
- velune/cli/registry.py +80 -0
- velune/cli/rendering/__init__.py +5 -0
- velune/cli/rendering/error_panel.py +79 -0
- velune/cli/rendering/markdown.py +63 -0
- velune/cli/repl.py +1855 -0
- velune/cli/session_manager.py +71 -0
- velune/cli/slash_commands.py +37 -0
- velune/cli/theme.py +8 -0
- velune/cognition/__init__.py +23 -0
- velune/cognition/agents/__init__.py +7 -0
- velune/cognition/agents/coder.py +209 -0
- velune/cognition/agents/planner.py +156 -0
- velune/cognition/agents/reviewer.py +195 -0
- velune/cognition/arbitrator.py +220 -0
- velune/cognition/architecture.py +415 -0
- velune/cognition/budget.py +65 -0
- velune/cognition/council/__init__.py +47 -0
- velune/cognition/council/base.py +217 -0
- velune/cognition/council/challenger.py +74 -0
- velune/cognition/council/coder.py +79 -0
- velune/cognition/council/critic_agent.py +43 -0
- velune/cognition/council/critic_configs.py +111 -0
- velune/cognition/council/critics.py +41 -0
- velune/cognition/council/debate.py +46 -0
- velune/cognition/council/factory.py +140 -0
- velune/cognition/council/messages.py +56 -0
- velune/cognition/council/planner.py +124 -0
- velune/cognition/council/reviewer.py +74 -0
- velune/cognition/council/synthesizer.py +67 -0
- velune/cognition/council/tiers.py +188 -0
- velune/cognition/council_orchestrator.py +282 -0
- velune/cognition/firewall.py +354 -0
- velune/cognition/module.py +46 -0
- velune/cognition/orchestrator.py +1205 -0
- velune/cognition/personality.py +238 -0
- velune/cognition/state.py +104 -0
- velune/cognition/style_resolver.py +64 -0
- velune/cognition/verification.py +205 -0
- velune/context/__init__.py +28 -0
- velune/context/assembler.py +240 -0
- velune/context/budget.py +97 -0
- velune/context/extractive.py +95 -0
- velune/context/prompt_adaptation.py +480 -0
- velune/context/sections.py +99 -0
- velune/context/token_counter.py +134 -0
- velune/context/utilization.py +33 -0
- velune/context/window.py +63 -0
- velune/core/__init__.py +89 -0
- velune/core/background.py +5 -0
- velune/core/config/__init__.py +37 -0
- velune/core/errors/__init__.py +90 -0
- velune/core/errors/catalog.py +188 -0
- velune/core/errors/execution.py +31 -0
- velune/core/errors/memory.py +25 -0
- velune/core/errors/orchestration.py +31 -0
- velune/core/errors/provider.py +37 -0
- velune/core/event_loop.py +35 -0
- velune/core/logging.py +83 -0
- velune/core/paths.py +165 -0
- velune/core/runtime.py +113 -0
- velune/core/startup_profiler.py +56 -0
- velune/core/task_registry.py +117 -0
- velune/core/trace.py +83 -0
- velune/core/types/__init__.py +48 -0
- velune/core/types/agent.py +53 -0
- velune/core/types/context.py +42 -0
- velune/core/types/inference.py +38 -0
- velune/core/types/memory.py +42 -0
- velune/core/types/model.py +70 -0
- velune/core/types/provider.py +62 -0
- velune/core/types/repository.py +38 -0
- velune/core/types/task.py +61 -0
- velune/core/types/workspace.py +28 -0
- velune/daemon/client.py +13 -0
- velune/daemon/server.py +127 -0
- velune/daemon/transport.py +179 -0
- velune/events.py +204 -0
- velune/execution/__init__.py +22 -0
- velune/execution/benchmarker.py +315 -0
- velune/execution/cancellation.py +53 -0
- velune/execution/checkpointer.py +130 -0
- velune/execution/command_spec.py +165 -0
- velune/execution/diff_preview.py +197 -0
- velune/execution/executor.py +181 -0
- velune/execution/module.py +18 -0
- velune/execution/multi_diff.py +67 -0
- velune/execution/path_guard.py +74 -0
- velune/execution/planner.py +91 -0
- velune/execution/rollback.py +89 -0
- velune/execution/sandbox.py +268 -0
- velune/execution/validator.py +115 -0
- velune/hardware/__init__.py +1 -0
- velune/hardware/detector.py +192 -0
- velune/kernel/__init__.py +55 -0
- velune/kernel/bootstrap.py +125 -0
- velune/kernel/config.py +426 -0
- velune/kernel/entrypoint.py +78 -0
- velune/kernel/health.py +54 -0
- velune/kernel/lifecycle.py +143 -0
- velune/kernel/module.py +17 -0
- velune/kernel/modules.py +23 -0
- velune/kernel/registry.py +96 -0
- velune/kernel/schemas.py +28 -0
- velune/main.py +9 -0
- velune/mcp/__init__.py +9 -0
- velune/mcp/client.py +115 -0
- velune/mcp/config.py +19 -0
- velune/mcp/server.py +624 -0
- velune/memory/__init__.py +32 -0
- velune/memory/compaction.py +506 -0
- velune/memory/embedding_pipeline.py +241 -0
- velune/memory/lifecycle.py +680 -0
- velune/memory/module.py +218 -0
- velune/memory/prioritizer.py +67 -0
- velune/memory/storage/episodic_schema.sql +53 -0
- velune/memory/storage/lancedb_store.py +282 -0
- velune/memory/storage/sqlite_manager.py +369 -0
- velune/memory/storage/sqlite_pool.py +149 -0
- velune/memory/tiers/episodic.py +588 -0
- velune/memory/tiers/graph.py +378 -0
- velune/memory/tiers/lineage.py +416 -0
- velune/memory/tiers/semantic.py +475 -0
- velune/memory/tiers/working.py +168 -0
- velune/memory/vitality.py +132 -0
- velune/models/__init__.py +15 -0
- velune/models/family.py +76 -0
- velune/models/module.py +20 -0
- velune/models/probes.py +192 -0
- velune/models/profile_cache.py +84 -0
- velune/models/profiler.py +108 -0
- velune/models/registry.py +251 -0
- velune/models/scorer.py +233 -0
- velune/models/specializations.py +205 -0
- velune/orchestration/__init__.py +19 -0
- velune/orchestration/engine.py +239 -0
- velune/orchestration/module.py +15 -0
- velune/orchestration/role_assignments.py +82 -0
- velune/orchestration/schemas.py +98 -0
- velune/plugins/__init__.py +20 -0
- velune/plugins/hooks.py +50 -0
- velune/plugins/loader.py +161 -0
- velune/plugins/registry.py +56 -0
- velune/plugins/schemas.py +21 -0
- velune/providers/__init__.py +23 -0
- velune/providers/adapters/anthropic.py +257 -0
- velune/providers/adapters/fireworks.py +115 -0
- velune/providers/adapters/google.py +234 -0
- velune/providers/adapters/groq.py +151 -0
- velune/providers/adapters/huggingface.py +210 -0
- velune/providers/adapters/llamacpp.py +208 -0
- velune/providers/adapters/lmstudio.py +175 -0
- velune/providers/adapters/ollama.py +233 -0
- velune/providers/adapters/openai.py +213 -0
- velune/providers/adapters/openrouter.py +81 -0
- velune/providers/adapters/together.py +134 -0
- velune/providers/adapters/xai.py +60 -0
- velune/providers/base.py +86 -0
- velune/providers/benchmarker.py +138 -0
- velune/providers/discovery/__init__.py +33 -0
- velune/providers/discovery/anthropic.py +79 -0
- velune/providers/discovery/benchmarks.py +44 -0
- velune/providers/discovery/classifier.py +69 -0
- velune/providers/discovery/fireworks.py +95 -0
- velune/providers/discovery/gguf.py +88 -0
- velune/providers/discovery/google.py +95 -0
- velune/providers/discovery/gpu.py +117 -0
- velune/providers/discovery/groq.py +21 -0
- velune/providers/discovery/huggingface.py +67 -0
- velune/providers/discovery/lmstudio.py +80 -0
- velune/providers/discovery/ollama.py +162 -0
- velune/providers/discovery/openai.py +96 -0
- velune/providers/discovery/openrouter.py +113 -0
- velune/providers/discovery/scanner.py +115 -0
- velune/providers/discovery/together.py +114 -0
- velune/providers/discovery/xai.py +57 -0
- velune/providers/health.py +67 -0
- velune/providers/health_monitor.py +169 -0
- velune/providers/keystore.py +142 -0
- velune/providers/local_paths.py +49 -0
- velune/providers/local_resolver.py +229 -0
- velune/providers/module.py +51 -0
- velune/providers/ollama_manager.py +193 -0
- velune/providers/registry.py +220 -0
- velune/providers/router.py +255 -0
- velune/providers/task_classifier.py +288 -0
- velune/py.typed +0 -0
- velune/repository/__init__.py +33 -0
- velune/repository/analyzer.py +127 -0
- velune/repository/ast_parser.py +822 -0
- velune/repository/blast_radius.py +298 -0
- velune/repository/boundary_classifier.py +295 -0
- velune/repository/cognition.py +316 -0
- velune/repository/grapher.py +179 -0
- velune/repository/import_graph.py +263 -0
- velune/repository/incremental_indexer.py +275 -0
- velune/repository/index_state.py +96 -0
- velune/repository/indexer.py +243 -0
- velune/repository/module.py +17 -0
- velune/repository/parser.py +474 -0
- velune/repository/project_type.py +300 -0
- velune/repository/rename_journal.py +287 -0
- velune/repository/scanner.py +193 -0
- velune/repository/schemas.py +102 -0
- velune/repository/symbol_registry.py +365 -0
- velune/repository/tracker.py +252 -0
- velune/retrieval/__init__.py +27 -0
- velune/retrieval/cache.py +110 -0
- velune/retrieval/fast_path.py +391 -0
- velune/retrieval/graph.py +124 -0
- velune/retrieval/hybrid.py +271 -0
- velune/retrieval/keyword.py +131 -0
- velune/retrieval/module.py +26 -0
- velune/retrieval/pipeline.py +303 -0
- velune/retrieval/reranker.py +102 -0
- velune/retrieval/schemas.py +59 -0
- velune/retrieval/slow_path.py +364 -0
- velune/retrieval/vector.py +203 -0
- velune/telemetry/__init__.py +59 -0
- velune/telemetry/cognition.py +267 -0
- velune/telemetry/cost_estimator.py +92 -0
- velune/telemetry/debug.py +304 -0
- velune/telemetry/doctor.py +244 -0
- velune/telemetry/logging.py +286 -0
- velune/telemetry/spans.py +277 -0
- velune/telemetry/token_tracker.py +140 -0
- velune/telemetry/usage_tracker.py +340 -0
- velune/tools/__init__.py +41 -0
- velune/tools/base/registry.py +87 -0
- velune/tools/base/tool.py +63 -0
- velune/tools/code/navigate.py +116 -0
- velune/tools/code/search.py +123 -0
- velune/tools/filesystem/read.py +75 -0
- velune/tools/filesystem/search.py +136 -0
- velune/tools/filesystem/write.py +163 -0
- velune/tools/git/history.py +177 -0
- velune/tools/git/operations.py +122 -0
- velune/tools/git/state.py +121 -0
- velune/tools/module.py +81 -0
- velune/tools/terminal/execute.py +72 -0
- velune/tools/terminal/history.py +47 -0
- velune/tools/web/fetch.py +55 -0
- velune/tools/web/validator.py +122 -0
- velune_cli-0.9.0.dist-info/METADATA +518 -0
- velune_cli-0.9.0.dist-info/RECORD +279 -0
- velune_cli-0.9.0.dist-info/WHEEL +4 -0
- velune_cli-0.9.0.dist-info/entry_points.txt +2 -0
- velune_cli-0.9.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Token counter with model-aware encoding support.
|
|
2
|
+
|
|
3
|
+
Supports accurate token counting for OpenAI-family models via tiktoken,
|
|
4
|
+
with conservative fallback for other model families.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from velune.core.types.model import ModelDescriptor
|
|
13
|
+
from velune.models.family import ModelFamily, detect_family
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("velune.context.token_counter")
|
|
16
|
+
|
|
17
|
+
# Attempt to load tiktoken for accurate OpenAI-family token counting
|
|
18
|
+
try:
|
|
19
|
+
import tiktoken
|
|
20
|
+
|
|
21
|
+
HAS_TIKTOKEN = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
HAS_TIKTOKEN = False
|
|
24
|
+
logger.warning("tiktoken not available; using heuristic token estimation")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TokenCounter:
|
|
28
|
+
"""Token counting with model-specific encoding support."""
|
|
29
|
+
|
|
30
|
+
# Tiktoken encoding registry for OpenAI-family models
|
|
31
|
+
_ENCODING_CACHE: dict[str, Any] = {}
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def count(text: str, model: ModelDescriptor) -> int:
|
|
35
|
+
"""Count tokens in text for the given model.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
text: Text to count tokens in
|
|
39
|
+
model: ModelDescriptor with model_id and family information
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Estimated or exact token count
|
|
43
|
+
"""
|
|
44
|
+
if not text:
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
# Detect model family if not specified
|
|
48
|
+
family = detect_family(model.model_id)
|
|
49
|
+
|
|
50
|
+
# OpenAI-family models: use tiktoken for accuracy
|
|
51
|
+
if family in (ModelFamily.CLAUDE, ModelFamily.GPT):
|
|
52
|
+
return TokenCounter._count_openai_family(text, model.model_id)
|
|
53
|
+
|
|
54
|
+
# Other families: conservative heuristic
|
|
55
|
+
return TokenCounter._count_heuristic(text)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def count_messages(messages: list[dict[str, str]], model: ModelDescriptor) -> int:
|
|
59
|
+
"""Count tokens in a list of messages.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
messages: List of message dicts with 'role' and 'content' keys
|
|
63
|
+
model: ModelDescriptor for encoding selection
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Total token count including overhead for message structure
|
|
67
|
+
"""
|
|
68
|
+
if not messages:
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
# Message structure overhead: ~4 tokens per message for role markers
|
|
72
|
+
structure_overhead = len(messages) * 4
|
|
73
|
+
|
|
74
|
+
# Count tokens in all message content
|
|
75
|
+
content_tokens = sum(TokenCounter.count(msg.get("content", ""), model) for msg in messages)
|
|
76
|
+
|
|
77
|
+
return structure_overhead + content_tokens
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _count_openai_family(text: str, model_id: str) -> int:
|
|
81
|
+
"""Count tokens using tiktoken for OpenAI-family models.
|
|
82
|
+
|
|
83
|
+
Falls back to heuristic if tiktoken unavailable.
|
|
84
|
+
"""
|
|
85
|
+
if not HAS_TIKTOKEN:
|
|
86
|
+
return TokenCounter._count_heuristic(text)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
# Determine appropriate encoding
|
|
90
|
+
encoding_name = TokenCounter._select_encoding(model_id)
|
|
91
|
+
if encoding_name not in TokenCounter._ENCODING_CACHE:
|
|
92
|
+
TokenCounter._ENCODING_CACHE[encoding_name] = tiktoken.get_encoding(encoding_name)
|
|
93
|
+
encoding = TokenCounter._ENCODING_CACHE[encoding_name]
|
|
94
|
+
|
|
95
|
+
# Count tokens, allowing special characters
|
|
96
|
+
return len(encoding.encode(text, disallowed_special=()))
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.debug(f"tiktoken counting failed for {model_id}: {e}; falling back to heuristic")
|
|
99
|
+
return TokenCounter._count_heuristic(text)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _count_heuristic(text: str) -> int:
|
|
103
|
+
"""Conservative token estimation for non-OpenAI models.
|
|
104
|
+
|
|
105
|
+
Assumes 1 token ≈ 1.35 words (English language average).
|
|
106
|
+
Used as fallback when tiktoken unavailable or for non-OpenAI models.
|
|
107
|
+
"""
|
|
108
|
+
if not text:
|
|
109
|
+
return 0
|
|
110
|
+
word_count = len(text.split())
|
|
111
|
+
return max(1, int(word_count * 1.35))
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _select_encoding(model_id: str) -> str:
|
|
115
|
+
"""Select appropriate tiktoken encoding based on model.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
model_id: Model identifier (e.g., "gpt-4-turbo", "gpt-3.5-turbo")
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Tiktoken encoding name (e.g., "cl100k_base", "o200k_base")
|
|
122
|
+
"""
|
|
123
|
+
model_lower = model_id.lower()
|
|
124
|
+
|
|
125
|
+
# o1 and newer models: o200k_base
|
|
126
|
+
if "o1" in model_lower or "o200k" in model_lower:
|
|
127
|
+
return "o200k_base"
|
|
128
|
+
|
|
129
|
+
# GPT-4-turbo and newer: cl100k_base
|
|
130
|
+
if "gpt-4" in model_lower or "gpt-4-turbo" in model_lower:
|
|
131
|
+
return "cl100k_base"
|
|
132
|
+
|
|
133
|
+
# GPT-3.5 and other models: cl100k_base
|
|
134
|
+
return "cl100k_base"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from velune.context.window import estimate_tokens
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContextUtilizationTracker:
|
|
7
|
+
"""Tracks token consumption dynamically and reports utilization percentages."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, max_tokens: int = 8192) -> None:
|
|
10
|
+
self.max_tokens = max_tokens
|
|
11
|
+
self.used_tokens = 0
|
|
12
|
+
|
|
13
|
+
def update(self, conversation: list[dict] | str) -> None:
|
|
14
|
+
"""Update the used token count based on active conversation list or raw string."""
|
|
15
|
+
if isinstance(conversation, str):
|
|
16
|
+
self.used_tokens = estimate_tokens(conversation)
|
|
17
|
+
elif isinstance(conversation, list):
|
|
18
|
+
text = " ".join(m.get("content", "") for m in conversation if isinstance(m, dict))
|
|
19
|
+
self.used_tokens = estimate_tokens(text)
|
|
20
|
+
else:
|
|
21
|
+
self.used_tokens = 0
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def percentage(self) -> float:
|
|
25
|
+
"""Returns the percentage of utilization, from 0.0 to 100.0."""
|
|
26
|
+
if self.max_tokens <= 0:
|
|
27
|
+
return 0.0
|
|
28
|
+
return min((self.used_tokens / self.max_tokens) * 100.0, 100.0)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def formatted_badge(self) -> str:
|
|
32
|
+
"""Returns a string in the format [ctx:NN%]."""
|
|
33
|
+
return f"[ctx:{self.percentage:.0f}%]"
|
velune/context/window.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Context window and token tracking utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("velune.context.window")
|
|
8
|
+
|
|
9
|
+
# Attempt to load tiktoken, fallback to heuristic if unavailable
|
|
10
|
+
try:
|
|
11
|
+
import tiktoken
|
|
12
|
+
|
|
13
|
+
_encoding = tiktoken.get_encoding("cl100k_base")
|
|
14
|
+
HAS_TIKTOKEN = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
HAS_TIKTOKEN = False
|
|
17
|
+
_encoding = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def estimate_tokens(text: str) -> int:
|
|
21
|
+
"""Accurately count or estimate tokens in a given text block."""
|
|
22
|
+
if not text:
|
|
23
|
+
return 0
|
|
24
|
+
if HAS_TIKTOKEN and _encoding is not None:
|
|
25
|
+
try:
|
|
26
|
+
return len(_encoding.encode(text, disallowed_special=()))
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
# Fallback heuristic: 1 token is roughly 4 characters
|
|
30
|
+
return len(text) // 4 + (1 if len(text) % 4 > 0 else 0)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ContextWindowTracker:
|
|
34
|
+
"""Tracks token consumption dynamically against a maximum budget capacity."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, max_tokens: int = 8192) -> None:
|
|
37
|
+
self.max_tokens = max_tokens
|
|
38
|
+
self.current_tokens = 0
|
|
39
|
+
self.segments: dict[str, int] = {}
|
|
40
|
+
|
|
41
|
+
def reserve(self, segment_name: str, text: str) -> int:
|
|
42
|
+
"""Reserve token budget for a specific context segment."""
|
|
43
|
+
tokens = estimate_tokens(text)
|
|
44
|
+
self.segments[segment_name] = tokens
|
|
45
|
+
self._recalculate()
|
|
46
|
+
return tokens
|
|
47
|
+
|
|
48
|
+
def release(self, segment_name: str) -> None:
|
|
49
|
+
"""Release reservation for a context segment."""
|
|
50
|
+
self.segments.pop(segment_name, None)
|
|
51
|
+
self._recalculate()
|
|
52
|
+
|
|
53
|
+
def get_remaining(self) -> int:
|
|
54
|
+
"""Get remaining available token budget."""
|
|
55
|
+
return max(0, self.max_tokens - self.current_tokens)
|
|
56
|
+
|
|
57
|
+
def is_overflowed(self) -> bool:
|
|
58
|
+
"""Check if current reservations exceed max tokens."""
|
|
59
|
+
return self.current_tokens > self.max_tokens
|
|
60
|
+
|
|
61
|
+
def _recalculate(self) -> None:
|
|
62
|
+
self.current_tokens = sum(self.segments.values())
|
|
63
|
+
logger.debug("Recalculated tokens: %d/%d", self.current_tokens, self.max_tokens)
|
velune/core/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Foundational primitives and contracts."""
|
|
2
|
+
|
|
3
|
+
from velune.core.errors import *
|
|
4
|
+
from velune.core.types import *
|
|
5
|
+
from velune.kernel.config import (
|
|
6
|
+
ConfigLoader,
|
|
7
|
+
ContextConfig,
|
|
8
|
+
ExecutionConfig,
|
|
9
|
+
MemoryConfig,
|
|
10
|
+
ProjectConfig,
|
|
11
|
+
ProviderEntry,
|
|
12
|
+
ProvidersConfig,
|
|
13
|
+
RetrievalConfig,
|
|
14
|
+
TelemetryConfig,
|
|
15
|
+
VeluneConfig,
|
|
16
|
+
WorkspaceConfig,
|
|
17
|
+
get_default_config,
|
|
18
|
+
)
|
|
19
|
+
from velune.kernel.registry import ServiceContainer, get_container, inject
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# Types
|
|
23
|
+
"AgentRole",
|
|
24
|
+
"AgentMessage",
|
|
25
|
+
"AgentMessageType",
|
|
26
|
+
"AgentResult",
|
|
27
|
+
"ContextPriority",
|
|
28
|
+
"ContextChunk",
|
|
29
|
+
"ContextWindow",
|
|
30
|
+
"InferenceRequest",
|
|
31
|
+
"StreamChunk",
|
|
32
|
+
"InferenceResponse",
|
|
33
|
+
"MemoryType",
|
|
34
|
+
"MemoryRecord",
|
|
35
|
+
"MemoryQuery",
|
|
36
|
+
"CapabilityLevel",
|
|
37
|
+
"ModelCapability",
|
|
38
|
+
"ModelDescriptor",
|
|
39
|
+
"ProviderConfig",
|
|
40
|
+
"ProviderCapabilities",
|
|
41
|
+
"FileNode",
|
|
42
|
+
"SymbolNode",
|
|
43
|
+
"DependencyEdge",
|
|
44
|
+
"TaskStatus",
|
|
45
|
+
"Task",
|
|
46
|
+
"TaskStep",
|
|
47
|
+
"TaskPlan",
|
|
48
|
+
"TaskResult",
|
|
49
|
+
"WorkspaceState",
|
|
50
|
+
"WorkspaceEvent",
|
|
51
|
+
# Config
|
|
52
|
+
"VeluneConfig",
|
|
53
|
+
"ProjectConfig",
|
|
54
|
+
"WorkspaceConfig",
|
|
55
|
+
"ContextConfig",
|
|
56
|
+
"MemoryConfig",
|
|
57
|
+
"RetrievalConfig",
|
|
58
|
+
"ExecutionConfig",
|
|
59
|
+
"ProviderEntry",
|
|
60
|
+
"ProvidersConfig",
|
|
61
|
+
"TelemetryConfig",
|
|
62
|
+
"ConfigLoader",
|
|
63
|
+
"get_default_config",
|
|
64
|
+
# Errors
|
|
65
|
+
"ProviderError",
|
|
66
|
+
"ProviderNotFoundError",
|
|
67
|
+
"ProviderConnectionError",
|
|
68
|
+
"ProviderAuthenticationError",
|
|
69
|
+
"ModelNotFoundError",
|
|
70
|
+
"InferenceError",
|
|
71
|
+
"OrchestrationError",
|
|
72
|
+
"AgentExecutionError",
|
|
73
|
+
"PipelineExecutionError",
|
|
74
|
+
"StateTransitionError",
|
|
75
|
+
"CheckpointError",
|
|
76
|
+
"VeluneMemoryError",
|
|
77
|
+
"VeluneMemoryStoreError",
|
|
78
|
+
"VeluneMemoryRetrievalError",
|
|
79
|
+
"VeluneMemoryConsolidationError",
|
|
80
|
+
"ExecutionError",
|
|
81
|
+
"SandboxError",
|
|
82
|
+
"SnapshotError",
|
|
83
|
+
"RollbackError",
|
|
84
|
+
"ValidationError",
|
|
85
|
+
# Registry
|
|
86
|
+
"ServiceContainer",
|
|
87
|
+
"inject",
|
|
88
|
+
"get_container",
|
|
89
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Configuration management re-exported from velune.kernel.config."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from velune.kernel.config import (
|
|
6
|
+
ConfigLoader,
|
|
7
|
+
ConfigService,
|
|
8
|
+
ContextConfig,
|
|
9
|
+
ExecutionConfig,
|
|
10
|
+
MCPConfig,
|
|
11
|
+
MemoryConfig,
|
|
12
|
+
ProjectConfig,
|
|
13
|
+
ProviderEntry,
|
|
14
|
+
ProvidersConfig,
|
|
15
|
+
RetrievalConfig,
|
|
16
|
+
TelemetryConfig,
|
|
17
|
+
VeluneConfig,
|
|
18
|
+
WorkspaceConfig,
|
|
19
|
+
get_default_config,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"VeluneConfig",
|
|
24
|
+
"ProjectConfig",
|
|
25
|
+
"WorkspaceConfig",
|
|
26
|
+
"ContextConfig",
|
|
27
|
+
"MemoryConfig",
|
|
28
|
+
"RetrievalConfig",
|
|
29
|
+
"ExecutionConfig",
|
|
30
|
+
"ProviderEntry",
|
|
31
|
+
"ProvidersConfig",
|
|
32
|
+
"TelemetryConfig",
|
|
33
|
+
"MCPConfig",
|
|
34
|
+
"ConfigLoader",
|
|
35
|
+
"ConfigService",
|
|
36
|
+
"get_default_config",
|
|
37
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Typed error hierarchy."""
|
|
2
|
+
|
|
3
|
+
from velune.core.errors.catalog import (
|
|
4
|
+
APIKeyMissingError,
|
|
5
|
+
ContextWindowExceededError,
|
|
6
|
+
IndexingFailedError,
|
|
7
|
+
InsufficientVRAMError,
|
|
8
|
+
NoModelsAvailableError,
|
|
9
|
+
OllamaNotRunningError,
|
|
10
|
+
ProviderUnavailableError,
|
|
11
|
+
RateLimitError,
|
|
12
|
+
SSRFAttemptError,
|
|
13
|
+
VeluneError,
|
|
14
|
+
WorkspaceNotInitializedError,
|
|
15
|
+
)
|
|
16
|
+
from velune.core.errors.catalog import (
|
|
17
|
+
ModelNotFoundError as ModelNotFoundVeluneError,
|
|
18
|
+
)
|
|
19
|
+
from velune.core.errors.catalog import (
|
|
20
|
+
PathTraversalError as PathTraversalVeluneError,
|
|
21
|
+
)
|
|
22
|
+
from velune.core.errors.execution import (
|
|
23
|
+
ExecutionError,
|
|
24
|
+
RollbackError,
|
|
25
|
+
SandboxError,
|
|
26
|
+
SnapshotError,
|
|
27
|
+
ValidationError,
|
|
28
|
+
)
|
|
29
|
+
from velune.core.errors.memory import (
|
|
30
|
+
VeluneMemoryConsolidationError,
|
|
31
|
+
VeluneMemoryError,
|
|
32
|
+
VeluneMemoryRetrievalError,
|
|
33
|
+
VeluneMemoryStoreError,
|
|
34
|
+
)
|
|
35
|
+
from velune.core.errors.orchestration import (
|
|
36
|
+
AgentExecutionError,
|
|
37
|
+
CheckpointError,
|
|
38
|
+
OrchestrationError,
|
|
39
|
+
PipelineExecutionError,
|
|
40
|
+
StateTransitionError,
|
|
41
|
+
)
|
|
42
|
+
from velune.core.errors.provider import (
|
|
43
|
+
InferenceError,
|
|
44
|
+
ModelNotFoundError,
|
|
45
|
+
ProviderAuthenticationError,
|
|
46
|
+
ProviderConnectionError,
|
|
47
|
+
ProviderError,
|
|
48
|
+
ProviderNotFoundError,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
# User-facing catalog errors
|
|
53
|
+
"VeluneError",
|
|
54
|
+
"OllamaNotRunningError",
|
|
55
|
+
"ModelNotFoundVeluneError",
|
|
56
|
+
"NoModelsAvailableError",
|
|
57
|
+
"APIKeyMissingError",
|
|
58
|
+
"WorkspaceNotInitializedError",
|
|
59
|
+
"ProviderUnavailableError",
|
|
60
|
+
"ContextWindowExceededError",
|
|
61
|
+
"RateLimitError",
|
|
62
|
+
"InsufficientVRAMError",
|
|
63
|
+
"PathTraversalVeluneError",
|
|
64
|
+
"SSRFAttemptError",
|
|
65
|
+
"IndexingFailedError",
|
|
66
|
+
# Internal provider errors
|
|
67
|
+
"ProviderError",
|
|
68
|
+
"ProviderNotFoundError",
|
|
69
|
+
"ProviderConnectionError",
|
|
70
|
+
"ProviderAuthenticationError",
|
|
71
|
+
"ModelNotFoundError",
|
|
72
|
+
"InferenceError",
|
|
73
|
+
# Internal orchestration errors
|
|
74
|
+
"OrchestrationError",
|
|
75
|
+
"AgentExecutionError",
|
|
76
|
+
"PipelineExecutionError",
|
|
77
|
+
"StateTransitionError",
|
|
78
|
+
"CheckpointError",
|
|
79
|
+
# Internal memory errors
|
|
80
|
+
"VeluneMemoryError",
|
|
81
|
+
"VeluneMemoryStoreError",
|
|
82
|
+
"VeluneMemoryRetrievalError",
|
|
83
|
+
"VeluneMemoryConsolidationError",
|
|
84
|
+
# Internal execution errors
|
|
85
|
+
"ExecutionError",
|
|
86
|
+
"SandboxError",
|
|
87
|
+
"SnapshotError",
|
|
88
|
+
"RollbackError",
|
|
89
|
+
"ValidationError",
|
|
90
|
+
]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Structured user-facing error catalog.
|
|
2
|
+
|
|
3
|
+
Every error type carries a short title, a cause explanation, and actionable
|
|
4
|
+
fix steps. The CLI renders these via ``velune.cli.rendering.error_panel``
|
|
5
|
+
instead of printing raw exception text.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VeluneError(Exception):
|
|
12
|
+
"""Base class for all user-facing Velune errors with rich display metadata.
|
|
13
|
+
|
|
14
|
+
Subclasses declare ``title``, ``cause``, ``fix``, and optionally
|
|
15
|
+
``docs_url`` as class attributes. The ``__init__`` accepts an optional
|
|
16
|
+
*detail* string (e.g. the underlying exception message) and an optional
|
|
17
|
+
*cause_override* to substitute the class-level ``cause`` at runtime when
|
|
18
|
+
the root cause is known precisely.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
title: str = "An unexpected error occurred"
|
|
22
|
+
cause: str = "An internal error was encountered."
|
|
23
|
+
fix: list[str] = ["Run `velune doctor` to diagnose your environment."]
|
|
24
|
+
docs_url: str | None = None
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
detail: str | None = None,
|
|
29
|
+
*,
|
|
30
|
+
cause_override: str | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
self._detail = detail
|
|
33
|
+
self._cause_override = cause_override
|
|
34
|
+
super().__init__(detail or self.title)
|
|
35
|
+
|
|
36
|
+
def get_cause(self) -> str:
|
|
37
|
+
return self._cause_override or self.cause
|
|
38
|
+
|
|
39
|
+
def get_detail(self) -> str | None:
|
|
40
|
+
return self._detail
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Provider / model errors
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OllamaNotRunningError(VeluneError):
|
|
49
|
+
title = "Ollama is not running"
|
|
50
|
+
cause = "Velune tried to connect to Ollama at localhost:11434 but the connection was refused."
|
|
51
|
+
fix = [
|
|
52
|
+
"Run `ollama serve` in a separate terminal window",
|
|
53
|
+
"Then retry your command, or run `velune doctor` to verify all providers",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ModelNotFoundError(VeluneError):
|
|
58
|
+
title = "Model not found"
|
|
59
|
+
cause = "The requested model ID is not registered in the Velune model catalog."
|
|
60
|
+
fix = [
|
|
61
|
+
"Run `velune models scan` to discover available models",
|
|
62
|
+
"Check the model ID for typos",
|
|
63
|
+
"Run `velune models list` to see all registered models",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class NoModelsAvailableError(VeluneError):
|
|
68
|
+
title = "No models available"
|
|
69
|
+
cause = "No models are configured for any provider in this workspace."
|
|
70
|
+
fix = [
|
|
71
|
+
"Run `velune models scan` to discover local Ollama models",
|
|
72
|
+
"Configure an API key with `velune config set providers.anthropic.api_key <key>`",
|
|
73
|
+
"Run `velune doctor` to diagnose provider connectivity",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class APIKeyMissingError(VeluneError):
|
|
78
|
+
title = "API key missing"
|
|
79
|
+
cause = (
|
|
80
|
+
"A cloud provider is configured but no API key was found in the environment or keystore."
|
|
81
|
+
)
|
|
82
|
+
fix = [
|
|
83
|
+
"Set the API key with `velune config set providers.<name>.api_key <key>`",
|
|
84
|
+
"Or export it as an environment variable (e.g. ANTHROPIC_API_KEY=...)",
|
|
85
|
+
"Run `velune doctor` to see which providers are missing keys",
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ProviderUnavailableError(VeluneError):
|
|
90
|
+
title = "Provider unavailable"
|
|
91
|
+
cause = "The model provider failed its health check and cannot accept inference requests."
|
|
92
|
+
fix = [
|
|
93
|
+
"Run `velune doctor check` to identify which providers are unreachable",
|
|
94
|
+
"Verify the provider service is running (e.g. `ollama serve` for Ollama)",
|
|
95
|
+
"Check API key and network connectivity for cloud providers",
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class RateLimitError(VeluneError):
|
|
100
|
+
title = "Rate limit reached"
|
|
101
|
+
cause = "The cloud provider has temporarily limited requests from this API key."
|
|
102
|
+
fix = [
|
|
103
|
+
"Wait a moment and retry the command",
|
|
104
|
+
"Use a local model (Ollama) as a fallback to avoid rate limits",
|
|
105
|
+
"Check your provider's dashboard for quota and usage details",
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ContextWindowExceededError(VeluneError):
|
|
110
|
+
title = "Context window exceeded"
|
|
111
|
+
cause = "The combined prompt and context is larger than the model's maximum context length."
|
|
112
|
+
fix = [
|
|
113
|
+
"Use a model with a larger context window (`velune models list`)",
|
|
114
|
+
"Reduce the amount of repository context by narrowing the file selection",
|
|
115
|
+
"Split the task into smaller, focused sub-tasks",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class InsufficientVRAMError(VeluneError):
|
|
120
|
+
title = "Insufficient VRAM"
|
|
121
|
+
cause = "The selected local model requires more GPU memory than is currently available."
|
|
122
|
+
fix = [
|
|
123
|
+
"Close other GPU-intensive applications to free VRAM",
|
|
124
|
+
"Use a smaller quantized version of the model (e.g. q4_K_M instead of q8_0)",
|
|
125
|
+
"Run `velune doctor` to view your hardware profile and compatible models",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Workspace / configuration errors
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class WorkspaceNotInitializedError(VeluneError):
|
|
135
|
+
title = "Workspace not initialized"
|
|
136
|
+
cause = "This directory does not contain a Velune workspace (velune.toml not found)."
|
|
137
|
+
fix = [
|
|
138
|
+
"Run `velune workspace init` to initialize this directory as a Velune workspace",
|
|
139
|
+
"Or change to a directory that already contains a velune.toml",
|
|
140
|
+
"Use `--workspace <path>` to point to an existing workspace",
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
# Security errors
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class PathTraversalError(VeluneError):
|
|
150
|
+
title = "Path traversal blocked"
|
|
151
|
+
cause = (
|
|
152
|
+
"A file path resolved to a location outside the workspace root. "
|
|
153
|
+
"This is a security violation."
|
|
154
|
+
)
|
|
155
|
+
fix = [
|
|
156
|
+
"Ensure all file paths are relative to the workspace root",
|
|
157
|
+
"Do not use `..` sequences or absolute paths that escape the workspace",
|
|
158
|
+
"Run `velune workspace info` to confirm your current workspace root",
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class SSRFAttemptError(VeluneError):
|
|
163
|
+
title = "Internal network request blocked"
|
|
164
|
+
cause = (
|
|
165
|
+
"A web tool attempted to reach an internal or metadata network address "
|
|
166
|
+
"(e.g. cloud provider IMDS, RFC 1918 private ranges). "
|
|
167
|
+
"This request was blocked to prevent SSRF attacks."
|
|
168
|
+
)
|
|
169
|
+
fix = [
|
|
170
|
+
"Only fetch publicly accessible URLs",
|
|
171
|
+
"Avoid internal network addresses (169.254.x.x, 10.x.x.x, 192.168.x.x, etc.)",
|
|
172
|
+
"If this was unexpected, inspect workspace content for injected URLs",
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# Repository / indexing errors
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class IndexingFailedError(VeluneError):
|
|
182
|
+
title = "Repository indexing failed"
|
|
183
|
+
cause = "Velune encountered an error while indexing the repository AST and structure."
|
|
184
|
+
fix = [
|
|
185
|
+
"Run `velune workspace reindex` to force a fresh index",
|
|
186
|
+
"Check file permissions in the workspace directory",
|
|
187
|
+
"Run `velune doctor` to verify the workspace is correctly configured",
|
|
188
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Execution-related errors."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ExecutionError(Exception):
|
|
5
|
+
"""Base exception for execution errors."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SandboxError(ExecutionError):
|
|
11
|
+
"""Raised when sandbox operation fails."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SnapshotError(ExecutionError):
|
|
17
|
+
"""Raised when snapshot operation fails."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RollbackError(ExecutionError):
|
|
23
|
+
"""Raised when rollback operation fails."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ValidationError(ExecutionError):
|
|
29
|
+
"""Raised when validation fails."""
|
|
30
|
+
|
|
31
|
+
pass
|