htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/cigs/cost.py
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CostCalculator for CIGS (Computational Imperative Guidance System).
|
|
3
|
+
|
|
4
|
+
Provides token cost prediction and actual cost tracking for tool operations.
|
|
5
|
+
Implements cost estimation heuristics based on tool type and operation complexity.
|
|
6
|
+
|
|
7
|
+
Design Reference:
|
|
8
|
+
- CIGS Design Doc: .htmlgraph/spikes/computational-imperative-guidance-system-design.md
|
|
9
|
+
- Part 5.2: Cost Efficiency Score
|
|
10
|
+
- Part 5.3: Cost estimation rules
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .models import (
|
|
14
|
+
CostMetrics,
|
|
15
|
+
OperationClassification,
|
|
16
|
+
TokenCost,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CostCalculator:
|
|
21
|
+
"""Calculate token costs for tool operations and delegations."""
|
|
22
|
+
|
|
23
|
+
# Cost estimation heuristics (tokens per operation)
|
|
24
|
+
# Based on typical token consumption patterns
|
|
25
|
+
COST_ESTIMATES = {
|
|
26
|
+
"Read": 5000, # Per file read
|
|
27
|
+
"Grep": 3000, # Per search result batch
|
|
28
|
+
"Glob": 2000, # Per glob pattern
|
|
29
|
+
"Edit": 4000, # Per edit operation
|
|
30
|
+
"Write": 4000, # Per write operation
|
|
31
|
+
"NotebookEdit": 4500, # Notebook cells are complex
|
|
32
|
+
"Delete": 2000, # File deletion
|
|
33
|
+
"Bash": { # Variable based on command type
|
|
34
|
+
"default": 2000,
|
|
35
|
+
"git": 1500,
|
|
36
|
+
"pytest": 5000,
|
|
37
|
+
"npm": 5000,
|
|
38
|
+
},
|
|
39
|
+
"Task": 500, # Orchestrator Task call (minimal context)
|
|
40
|
+
"AskUserQuestion": 1000, # User interaction
|
|
41
|
+
"TodoWrite": 500, # Tracking operations
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Subagent delegation costs (optimal path)
|
|
45
|
+
SUBAGENT_COSTS = {
|
|
46
|
+
"spawn_gemini": 500, # Explorer subagent cost
|
|
47
|
+
"spawn_codex": 800, # Coder subagent cost
|
|
48
|
+
"spawn_copilot": 600, # Git subagent cost
|
|
49
|
+
"Task": 500, # Orchestrator Task
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
"""Initialize CostCalculator."""
|
|
54
|
+
self.tool_history: list[str] = [] # Track recent tool usage for patterns
|
|
55
|
+
|
|
56
|
+
def predict_cost(self, tool: str, params: dict) -> int:
|
|
57
|
+
"""
|
|
58
|
+
Predict token cost for direct tool execution.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
tool: Tool name (Read, Grep, Bash, etc.)
|
|
62
|
+
params: Tool parameters
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Estimated token cost
|
|
66
|
+
"""
|
|
67
|
+
if tool not in self.COST_ESTIMATES:
|
|
68
|
+
return 2000 # Default estimate for unknown tools
|
|
69
|
+
|
|
70
|
+
estimate = self.COST_ESTIMATES[tool]
|
|
71
|
+
|
|
72
|
+
# Handle variable estimates
|
|
73
|
+
if isinstance(estimate, dict):
|
|
74
|
+
return self._estimate_variable_cost(tool, params, estimate)
|
|
75
|
+
|
|
76
|
+
# Apply modifiers based on operation complexity
|
|
77
|
+
assert isinstance(estimate, int)
|
|
78
|
+
return self._apply_complexity_modifiers(tool, params, estimate)
|
|
79
|
+
|
|
80
|
+
def _estimate_variable_cost(
|
|
81
|
+
self, tool: str, params: dict, estimates: dict[str, int]
|
|
82
|
+
) -> int:
|
|
83
|
+
"""Estimate cost for tools with variable pricing."""
|
|
84
|
+
if tool == "Bash":
|
|
85
|
+
command = params.get("command", "")
|
|
86
|
+
|
|
87
|
+
# Git operations
|
|
88
|
+
if any(cmd in command for cmd in ["git add", "git commit", "git push"]):
|
|
89
|
+
return int(estimates.get("git", estimates.get("default", 2000)))
|
|
90
|
+
|
|
91
|
+
# Test operations
|
|
92
|
+
if any(cmd in command for cmd in ["pytest", "uv run pytest"]):
|
|
93
|
+
return int(estimates.get("pytest", estimates.get("default", 5000)))
|
|
94
|
+
|
|
95
|
+
if "npm test" in command or "yarn test" in command:
|
|
96
|
+
return int(estimates.get("npm", estimates.get("default", 5000)))
|
|
97
|
+
|
|
98
|
+
# Default bash
|
|
99
|
+
return int(estimates.get("default", 2000))
|
|
100
|
+
|
|
101
|
+
return int(estimates.get("default", 2000))
|
|
102
|
+
|
|
103
|
+
def _apply_complexity_modifiers(
|
|
104
|
+
self, tool: str, params: dict, base_cost: int | dict[str, int]
|
|
105
|
+
) -> int:
|
|
106
|
+
"""Apply complexity modifiers to base cost estimate."""
|
|
107
|
+
# Handle dict base_cost (shouldn't happen but for safety)
|
|
108
|
+
if isinstance(base_cost, dict):
|
|
109
|
+
base_cost = base_cost.get("default", 2000)
|
|
110
|
+
|
|
111
|
+
modified_cost: float = float(base_cost)
|
|
112
|
+
|
|
113
|
+
if tool == "Read":
|
|
114
|
+
# Multiple files increase cost
|
|
115
|
+
if isinstance(params.get("file_path"), list):
|
|
116
|
+
modified_cost = base_cost * len(params["file_path"])
|
|
117
|
+
# Large offset/limits indicate large reads
|
|
118
|
+
elif params.get("limit", 2000) > 5000:
|
|
119
|
+
modified_cost = base_cost * 2
|
|
120
|
+
|
|
121
|
+
elif tool == "Grep":
|
|
122
|
+
# Complex regex patterns increase cost
|
|
123
|
+
pattern = params.get("pattern", "")
|
|
124
|
+
if len(pattern) > 100: # Complex pattern
|
|
125
|
+
modified_cost = base_cost * 1.5
|
|
126
|
+
# Multiline matching is more expensive
|
|
127
|
+
if params.get("multiline", False):
|
|
128
|
+
modified_cost = base_cost * 1.3
|
|
129
|
+
|
|
130
|
+
elif tool in ["Edit", "Write"]:
|
|
131
|
+
# Multiple edits or large content
|
|
132
|
+
if isinstance(params.get("file_path"), list):
|
|
133
|
+
modified_cost = base_cost * len(params["file_path"])
|
|
134
|
+
# Large content increases cost
|
|
135
|
+
content = params.get("content", params.get("new_string", ""))
|
|
136
|
+
if len(content) > 10000:
|
|
137
|
+
modified_cost = base_cost * 1.5
|
|
138
|
+
|
|
139
|
+
return int(modified_cost)
|
|
140
|
+
|
|
141
|
+
def optimal_cost(self, classification: OperationClassification) -> int:
|
|
142
|
+
"""
|
|
143
|
+
Calculate optimal cost with proper delegation.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
classification: OperationClassification with tool and category info
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Estimated token cost with optimal delegation strategy
|
|
150
|
+
"""
|
|
151
|
+
tool = classification.tool
|
|
152
|
+
|
|
153
|
+
# Orchestrator tools already optimal
|
|
154
|
+
if tool in ["Task", "AskUserQuestion", "TodoWrite"]:
|
|
155
|
+
estimate = self.COST_ESTIMATES.get(tool, 500)
|
|
156
|
+
return int(estimate) if isinstance(estimate, int) else 500
|
|
157
|
+
|
|
158
|
+
# Map to subagent based on category
|
|
159
|
+
if classification.category == "exploration":
|
|
160
|
+
return int(self.SUBAGENT_COSTS["spawn_gemini"])
|
|
161
|
+
elif classification.category == "implementation":
|
|
162
|
+
return int(self.SUBAGENT_COSTS["spawn_codex"])
|
|
163
|
+
elif classification.category == "git":
|
|
164
|
+
return int(self.SUBAGENT_COSTS["spawn_copilot"])
|
|
165
|
+
elif classification.category == "testing":
|
|
166
|
+
return int(self.SUBAGENT_COSTS["Task"])
|
|
167
|
+
|
|
168
|
+
# Default to Task for unknown categories
|
|
169
|
+
return int(self.SUBAGENT_COSTS["Task"])
|
|
170
|
+
|
|
171
|
+
def calculate_actual_cost(self, tool: str, result: dict) -> TokenCost:
|
|
172
|
+
"""
|
|
173
|
+
Calculate actual cost after tool execution.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
tool: Tool that was executed
|
|
177
|
+
result: Result dictionary from tool execution
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
TokenCost with actual metrics
|
|
181
|
+
"""
|
|
182
|
+
# Get predicted cost for this tool
|
|
183
|
+
predicted_tokens = self._extract_predicted_cost(tool, result)
|
|
184
|
+
|
|
185
|
+
# Extract actual cost if available in result
|
|
186
|
+
actual_tokens = self._extract_actual_cost(tool, result)
|
|
187
|
+
|
|
188
|
+
# If no actual cost in result, use predicted
|
|
189
|
+
if actual_tokens is None:
|
|
190
|
+
actual_tokens = predicted_tokens
|
|
191
|
+
|
|
192
|
+
# Determine subagent cost based on tool type
|
|
193
|
+
subagent_tokens = self._get_subagent_cost(tool)
|
|
194
|
+
|
|
195
|
+
# Calculate orchestrator overhead
|
|
196
|
+
orchestrator_tokens = self._estimate_orchestrator_overhead(tool, result)
|
|
197
|
+
|
|
198
|
+
# Calculate savings
|
|
199
|
+
estimated_savings = max(
|
|
200
|
+
0, actual_tokens - subagent_tokens - orchestrator_tokens
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return TokenCost(
|
|
204
|
+
total_tokens=actual_tokens,
|
|
205
|
+
orchestrator_tokens=orchestrator_tokens,
|
|
206
|
+
subagent_tokens=subagent_tokens,
|
|
207
|
+
estimated_savings=estimated_savings,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _extract_predicted_cost(self, tool: str, result: dict) -> int:
|
|
211
|
+
"""Extract or estimate predicted cost from result."""
|
|
212
|
+
# Check if result contains cost metadata
|
|
213
|
+
if "predicted_cost" in result:
|
|
214
|
+
return int(result["predicted_cost"])
|
|
215
|
+
|
|
216
|
+
if "metadata" in result and "predicted_cost" in result["metadata"]:
|
|
217
|
+
return int(result["metadata"]["predicted_cost"])
|
|
218
|
+
|
|
219
|
+
# Fall back to default estimate
|
|
220
|
+
estimate = self.COST_ESTIMATES.get(tool, 2000)
|
|
221
|
+
return int(estimate) if isinstance(estimate, int) else 2000
|
|
222
|
+
|
|
223
|
+
def _extract_actual_cost(self, tool: str, result: dict) -> int | None:
|
|
224
|
+
"""Extract actual cost from execution result if available."""
|
|
225
|
+
# Check standard cost fields
|
|
226
|
+
if "actual_cost" in result:
|
|
227
|
+
return int(result["actual_cost"])
|
|
228
|
+
|
|
229
|
+
if "cost" in result:
|
|
230
|
+
return int(result["cost"])
|
|
231
|
+
|
|
232
|
+
if "metadata" in result and "cost" in result["metadata"]:
|
|
233
|
+
return int(result["metadata"]["cost"])
|
|
234
|
+
|
|
235
|
+
if "tokens" in result:
|
|
236
|
+
return int(result["tokens"])
|
|
237
|
+
|
|
238
|
+
# Try to estimate from output size for Read operations
|
|
239
|
+
if tool == "Read" and "output" in result:
|
|
240
|
+
output = result["output"]
|
|
241
|
+
if isinstance(output, str):
|
|
242
|
+
# Rough estimate: ~4 tokens per line
|
|
243
|
+
lines = len(output.split("\n"))
|
|
244
|
+
return int(lines * 4)
|
|
245
|
+
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
def _get_subagent_cost(self, tool: str) -> int:
|
|
249
|
+
"""Get cost if this operation were delegated to subagent."""
|
|
250
|
+
if tool in ["Task", "AskUserQuestion", "TodoWrite"]:
|
|
251
|
+
return 0 # Already delegated
|
|
252
|
+
|
|
253
|
+
if tool in ["Read", "Grep", "Glob"]:
|
|
254
|
+
return self.SUBAGENT_COSTS["spawn_gemini"]
|
|
255
|
+
|
|
256
|
+
if tool in ["Edit", "Write", "NotebookEdit", "Delete"]:
|
|
257
|
+
return self.SUBAGENT_COSTS["spawn_codex"]
|
|
258
|
+
|
|
259
|
+
if tool == "Bash":
|
|
260
|
+
# Might be git or test - estimate higher
|
|
261
|
+
return self.SUBAGENT_COSTS["spawn_copilot"] # or Task
|
|
262
|
+
|
|
263
|
+
# Default
|
|
264
|
+
return self.SUBAGENT_COSTS["Task"]
|
|
265
|
+
|
|
266
|
+
def _estimate_orchestrator_overhead(self, tool: str, result: dict) -> int:
|
|
267
|
+
"""Estimate orchestrator context overhead."""
|
|
268
|
+
# Orchestrator overhead is minimal for delegated operations
|
|
269
|
+
if tool in ["Task", "AskUserQuestion"]:
|
|
270
|
+
return 200 # Small overhead for delegation call
|
|
271
|
+
|
|
272
|
+
# Direct execution contributes full cost to orchestrator context
|
|
273
|
+
if tool == "Read":
|
|
274
|
+
# Context cost is proportional to file size
|
|
275
|
+
if "output" in result:
|
|
276
|
+
output = result["output"]
|
|
277
|
+
if isinstance(output, str):
|
|
278
|
+
# ~4 tokens per line
|
|
279
|
+
return int(len(output.split("\n")) * 4)
|
|
280
|
+
return 5000
|
|
281
|
+
|
|
282
|
+
# Other tools
|
|
283
|
+
return 1000 # Placeholder for other tools
|
|
284
|
+
|
|
285
|
+
def calculate_waste(self, actual_cost: int, optimal_cost: int) -> dict:
|
|
286
|
+
"""
|
|
287
|
+
Calculate waste metrics comparing actual vs optimal cost.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
actual_cost: Actual tokens consumed
|
|
291
|
+
optimal_cost: Tokens with optimal delegation
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Dictionary with waste metrics
|
|
295
|
+
"""
|
|
296
|
+
waste_tokens = max(0, actual_cost - optimal_cost)
|
|
297
|
+
|
|
298
|
+
if actual_cost == 0:
|
|
299
|
+
waste_percentage = 0.0
|
|
300
|
+
efficiency_score = 100.0
|
|
301
|
+
else:
|
|
302
|
+
waste_percentage = (waste_tokens / actual_cost) * 100
|
|
303
|
+
efficiency_score = (optimal_cost / actual_cost) * 100
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
"waste_tokens": waste_tokens,
|
|
307
|
+
"waste_percentage": waste_percentage,
|
|
308
|
+
"efficiency_score": efficiency_score,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
def aggregate_session_costs(
|
|
312
|
+
self,
|
|
313
|
+
operations: list[tuple[str, dict, dict]],
|
|
314
|
+
violations_count: int = 0,
|
|
315
|
+
) -> CostMetrics:
|
|
316
|
+
"""
|
|
317
|
+
Aggregate cost metrics for a session.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
operations: List of (tool, params, result) tuples
|
|
321
|
+
violations_count: Number of violations in session
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
CostMetrics with aggregated session costs
|
|
325
|
+
"""
|
|
326
|
+
total_tokens = 0
|
|
327
|
+
optimal_tokens = 0
|
|
328
|
+
orchestrator_tokens = 0
|
|
329
|
+
subagent_tokens = 0
|
|
330
|
+
|
|
331
|
+
for tool, params, result in operations:
|
|
332
|
+
# Predict cost
|
|
333
|
+
predicted = self.predict_cost(tool, params)
|
|
334
|
+
total_tokens += predicted
|
|
335
|
+
|
|
336
|
+
# Calculate actual if available
|
|
337
|
+
cost_record = self.calculate_actual_cost(tool, result)
|
|
338
|
+
total_tokens = max(total_tokens, cost_record.total_tokens)
|
|
339
|
+
|
|
340
|
+
# Accumulate subagent costs
|
|
341
|
+
subagent_tokens += cost_record.subagent_tokens
|
|
342
|
+
orchestrator_tokens += cost_record.orchestrator_tokens
|
|
343
|
+
|
|
344
|
+
# Accumulate optimal costs
|
|
345
|
+
# For delegated operations, optimal = subagent cost
|
|
346
|
+
# For direct operations that should be delegated, optimal = subagent cost
|
|
347
|
+
if tool in ["Task", "AskUserQuestion"]:
|
|
348
|
+
estimate = self.COST_ESTIMATES.get(tool, 500)
|
|
349
|
+
optimal_tokens += int(estimate) if isinstance(estimate, int) else 500
|
|
350
|
+
else:
|
|
351
|
+
optimal_tokens += self._get_subagent_cost(tool)
|
|
352
|
+
|
|
353
|
+
waste_tokens = max(0, total_tokens - optimal_tokens)
|
|
354
|
+
|
|
355
|
+
metrics = CostMetrics(
|
|
356
|
+
total_tokens=total_tokens,
|
|
357
|
+
optimal_tokens=optimal_tokens,
|
|
358
|
+
orchestrator_tokens=orchestrator_tokens,
|
|
359
|
+
subagent_tokens=subagent_tokens,
|
|
360
|
+
waste_tokens=waste_tokens,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
return metrics
|
|
364
|
+
|
|
365
|
+
def classify_operation(
|
|
366
|
+
self,
|
|
367
|
+
tool: str,
|
|
368
|
+
params: dict,
|
|
369
|
+
is_exploration_sequence: bool = False,
|
|
370
|
+
tool_history: list[str] | None = None,
|
|
371
|
+
) -> OperationClassification:
|
|
372
|
+
"""
|
|
373
|
+
Classify a tool operation for cost and delegation analysis.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
tool: Tool name
|
|
377
|
+
params: Tool parameters
|
|
378
|
+
is_exploration_sequence: Whether this is part of exploration sequence
|
|
379
|
+
tool_history: Recent tool usage history
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
OperationClassification with cost and delegation info
|
|
383
|
+
"""
|
|
384
|
+
# Categorize tool
|
|
385
|
+
category = self._categorize_tool(tool)
|
|
386
|
+
|
|
387
|
+
# Determine if delegation is recommended
|
|
388
|
+
should_delegate = self._should_delegate(tool, is_exploration_sequence)
|
|
389
|
+
|
|
390
|
+
# Get delegation suggestion
|
|
391
|
+
suggestion = self._get_delegation_suggestion(tool, category)
|
|
392
|
+
|
|
393
|
+
# Predict costs
|
|
394
|
+
predicted_cost = self.predict_cost(tool, params)
|
|
395
|
+
|
|
396
|
+
# Calculate optimal cost for this category
|
|
397
|
+
dummy_classification = OperationClassification(
|
|
398
|
+
tool=tool,
|
|
399
|
+
category="", # Will be set below
|
|
400
|
+
should_delegate=should_delegate,
|
|
401
|
+
reason="",
|
|
402
|
+
predicted_cost=predicted_cost,
|
|
403
|
+
optimal_cost=0,
|
|
404
|
+
is_exploration_sequence=is_exploration_sequence,
|
|
405
|
+
suggested_delegation=suggestion,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
optimal_cost = self.optimal_cost(dummy_classification)
|
|
409
|
+
|
|
410
|
+
# Calculate waste percentage
|
|
411
|
+
waste_tokens = max(0, predicted_cost - optimal_cost)
|
|
412
|
+
waste_pct = (waste_tokens / predicted_cost * 100) if predicted_cost > 0 else 0.0
|
|
413
|
+
|
|
414
|
+
return OperationClassification(
|
|
415
|
+
tool=tool,
|
|
416
|
+
category=category,
|
|
417
|
+
should_delegate=should_delegate,
|
|
418
|
+
reason=self._get_delegation_reason(tool, category, is_exploration_sequence),
|
|
419
|
+
predicted_cost=predicted_cost,
|
|
420
|
+
optimal_cost=optimal_cost,
|
|
421
|
+
is_exploration_sequence=is_exploration_sequence,
|
|
422
|
+
suggested_delegation=suggestion,
|
|
423
|
+
waste_percentage=waste_pct,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def _categorize_tool(self, tool: str) -> str:
|
|
427
|
+
"""Categorize tool into operational type."""
|
|
428
|
+
if tool in ["Read", "Grep", "Glob"]:
|
|
429
|
+
return "exploration"
|
|
430
|
+
elif tool in ["Edit", "Write", "NotebookEdit", "Delete"]:
|
|
431
|
+
return "implementation"
|
|
432
|
+
elif tool in ["Task", "AskUserQuestion", "TodoWrite"]:
|
|
433
|
+
return "orchestration"
|
|
434
|
+
elif tool == "Bash":
|
|
435
|
+
return "execution"
|
|
436
|
+
else:
|
|
437
|
+
return "unknown"
|
|
438
|
+
|
|
439
|
+
def _should_delegate(self, tool: str, is_sequence: bool) -> bool:
|
|
440
|
+
"""Determine if operation should be delegated."""
|
|
441
|
+
# Orchestrator tools are already delegated
|
|
442
|
+
if tool in ["Task", "AskUserQuestion", "TodoWrite"]:
|
|
443
|
+
return False
|
|
444
|
+
|
|
445
|
+
# Exploration sequences should be delegated
|
|
446
|
+
if is_sequence and tool in ["Read", "Grep", "Glob"]:
|
|
447
|
+
return True
|
|
448
|
+
|
|
449
|
+
# Single direct operations are allowed but not recommended
|
|
450
|
+
# CIGS messaging will recommend delegation
|
|
451
|
+
return False
|
|
452
|
+
|
|
453
|
+
def _get_delegation_suggestion(self, tool: str, category: str) -> str:
|
|
454
|
+
"""Get suggested delegation for this tool."""
|
|
455
|
+
suggestions = {
|
|
456
|
+
"exploration": "spawn_gemini(prompt='Search and analyze codebase')",
|
|
457
|
+
"implementation": "spawn_codex(prompt='Implement with full testing')",
|
|
458
|
+
"execution": "Task(prompt='Execute and report results')",
|
|
459
|
+
"testing": "Task(prompt='Run tests and report')",
|
|
460
|
+
}
|
|
461
|
+
return suggestions.get(category, "Task(prompt='Delegate this operation')")
|
|
462
|
+
|
|
463
|
+
def _get_delegation_reason(
|
|
464
|
+
self, tool: str, category: str, is_sequence: bool
|
|
465
|
+
) -> str:
|
|
466
|
+
"""Get reason for delegation recommendation."""
|
|
467
|
+
if is_sequence:
|
|
468
|
+
return f"Multiple {category} operations detected (research work should be delegated)"
|
|
469
|
+
|
|
470
|
+
reasons = {
|
|
471
|
+
"exploration": "Exploration operations have unpredictable scope",
|
|
472
|
+
"implementation": "Implementation requires iteration and testing",
|
|
473
|
+
"execution": "Consider delegating execution for better isolation",
|
|
474
|
+
}
|
|
475
|
+
return reasons.get(category, "Delegation preserves your strategic context")
|