#pragma once // Step 390: CostEstimator — Token cost estimation and optimization suggestions // // Before executing a workflow, estimate the total cost in tokens and suggest // optimizations (narrowing context, using templates, batching related tasks). #include "WorkflowState.h" #include "RoutingEngine.h" #include "RoutingRules.h" #include #include #include #include #include // --- OptimizationSuggestion --- struct OptimizationSuggestion { std::string description; int tokensSaved = 0; std::vector itemIds; std::string action; // "narrow-context" | "use-template" | "batch" | "skip-review" json toJson() const { return json{{"description", description}, {"tokensSaved", tokensSaved}, {"itemIds", itemIds}, {"action", action}}; } static OptimizationSuggestion fromJson(const json& j) { OptimizationSuggestion s; if (j.contains("description")) s.description = j["description"].get(); if (j.contains("tokensSaved")) s.tokensSaved = j["tokensSaved"].get(); if (j.contains("itemIds") && j["itemIds"].is_array()) { for (const auto& id : j["itemIds"]) s.itemIds.push_back(id.get()); } if (j.contains("action")) s.action = j["action"].get(); return s; } }; // --- CostEstimate --- struct CostEstimate { int totalContextTokens = 0; int totalOutputTokens = 0; std::map byWorkerType; // tokens per worker type int deterministicTasks = 0; float estimatedCost = 0.0f; // rough USD estimate std::vector optimizationSuggestions; json toJson() const { json sugArr = json::array(); for (const auto& s : optimizationSuggestions) sugArr.push_back(s.toJson()); return json{ {"totalContextTokens", totalContextTokens}, {"totalOutputTokens", totalOutputTokens}, {"byWorkerType", byWorkerType}, {"deterministicTasks", deterministicTasks}, {"estimatedCost", estimatedCost}, {"optimizationSuggestions", sugArr} }; } static CostEstimate fromJson(const json& j) { CostEstimate e; if (j.contains("totalContextTokens")) e.totalContextTokens = j["totalContextTokens"].get(); if (j.contains("totalOutputTokens")) e.totalOutputTokens = j["totalOutputTokens"].get(); if (j.contains("byWorkerType") && j["byWorkerType"].is_object()) { for (auto& [k, v] : j["byWorkerType"].items()) e.byWorkerType[k] = v.get(); } if (j.contains("deterministicTasks")) e.deterministicTasks = j["deterministicTasks"].get(); if (j.contains("estimatedCost")) e.estimatedCost = j["estimatedCost"].get(); if (j.contains("optimizationSuggestions") && j["optimizationSuggestions"].is_array()) { for (const auto& s : j["optimizationSuggestions"]) e.optimizationSuggestions.push_back(OptimizationSuggestion::fromJson(s)); } return e; } }; // --- CostEstimator --- // Collect all items from a TaskQueue across all statuses inline std::vector collectAllItems(const TaskQueue& q) { std::vector all; for (const auto& status : {WI_PENDING, WI_READY, WI_ASSIGNED, WI_IN_PROGRESS, WI_REVIEW, WI_COMPLETE, WI_REJECTED}) { auto items = q.getByStatus(status); all.insert(all.end(), items.begin(), items.end()); } return all; } class CostEstimator { public: // Estimate cost for entire workflow using the routing engine CostEstimate estimate(const WorkflowState& workflow, const RoutingEngine& routing) const { CostEstimate est; auto allItems = collectAllItems(workflow.queue); for (const auto& item : allItems) { if (item.status == WI_COMPLETE) continue; RoutingDecision decision = routing.route(item); int contextTokens = decision.contextBudgetTokens; int outputTokens = estimateOutputTokens(decision); est.totalContextTokens += contextTokens; est.totalOutputTokens += outputTokens; est.byWorkerType[decision.workerType] += contextTokens + outputTokens; if (decision.workerType == "deterministic" || decision.workerType == "template") { est.deterministicTasks++; } } // Rough cost: $3/M input, $15/M output (Claude-class pricing) est.estimatedCost = (est.totalContextTokens * 3.0f / 1000000.0f) + (est.totalOutputTokens * 15.0f / 1000000.0f); est.optimizationSuggestions = suggest(workflow, routing); return est; } // Generate optimization suggestions std::vector suggest(const WorkflowState& workflow, const RoutingEngine& routing) const { std::vector suggestions; auto allItems = collectAllItems(workflow.queue); // Suggestion 1: Getter/setter functions that could use template worker { std::vector getterIds; int savedTokens = 0; for (const auto& item : allItems) { if (item.status == WI_COMPLETE) continue; if (isGetterSetterPattern(item.nodeName)) { auto decision = routing.route(item); if (decision.workerType != "template" && decision.workerType != "deterministic") { getterIds.push_back(item.id); savedTokens += decision.contextBudgetTokens; } } } if (!getterIds.empty()) { OptimizationSuggestion s; s.description = std::to_string(getterIds.size()) + " getter/setter functions could use template worker"; s.tokensSaved = savedTokens; s.itemIds = getterIds; s.action = "use-template"; suggestions.push_back(s); } } // Suggestion 2: File/project-width tasks that could be narrowed to local { std::vector narrowIds; int savedTokens = 0; for (const auto& item : allItems) { if (item.status == WI_COMPLETE) continue; std::string width = item.contextWidth.empty() ? "local" : item.contextWidth; if (width == "file" || width == "project") { // Simple functions with local names could be narrowed if (isGetterSetterPattern(item.nodeName) || item.nodeName.find("simple") != std::string::npos) { narrowIds.push_back(item.id); int current = estimateContextBudget(width); int narrowed = estimateContextBudget("local"); savedTokens += (current - narrowed); } } } if (!narrowIds.empty()) { OptimizationSuggestion s; s.description = std::to_string(narrowIds.size()) + " tasks could be narrowed to local context"; s.tokensSaved = savedTokens; s.itemIds = narrowIds; s.action = "narrow-context"; suggestions.push_back(s); } } // Suggestion 3: Batch related tasks (same buffer, same context width) { std::map> groups; for (const auto& item : allItems) { if (item.status == WI_COMPLETE) continue; std::string width = item.contextWidth.empty() ? "local" : item.contextWidth; if (width == "project" || width == "cross-project") { std::string key = item.bufferId + ":" + width; groups[key].push_back(item.id); } } for (const auto& [key, ids] : groups) { if (ids.size() >= 2) { std::string width = key.substr(key.rfind(':') + 1); int sharedCost = estimateContextBudget(width); int saved = sharedCost * (static_cast(ids.size()) - 1); OptimizationSuggestion s; s.description = std::to_string(ids.size()) + " related tasks could share " + width + " context"; s.tokensSaved = saved; s.itemIds = ids; s.action = "batch"; suggestions.push_back(s); } } } return suggestions; } private: static int estimateOutputTokens(const RoutingDecision& decision) { // Deterministic/template workers produce no LLM output tokens if (decision.workerType == "deterministic" || decision.workerType == "template") return 0; // Human workers: zero automated token cost if (decision.workerType == "human") return 0; // SLM: smaller output if (decision.workerType == "slm") return decision.contextBudgetTokens / 2; // LLM: proportional to context return decision.contextBudgetTokens; } };