flowseeker 0.1.7
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.
- package/CHANGELOG.md +111 -0
- package/COMPATIBILITY.md +281 -0
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/dist/adapters/frameworkEdges.js +586 -0
- package/dist/agent/approvalPolicy.js +81 -0
- package/dist/agent/commandRunner.js +166 -0
- package/dist/agent/flowCommandRunner.js +124 -0
- package/dist/agent/mcpToolRunner.js +167 -0
- package/dist/auth/githubAuth.js +71 -0
- package/dist/auth/modelList.js +127 -0
- package/dist/auth/oauthHandler.js +377 -0
- package/dist/chat/nativeChatParticipant.js +616 -0
- package/dist/cli/mcpServer.js +383 -0
- package/dist/cli/runEvaluation.js +789 -0
- package/dist/cli/runReplay.js +481 -0
- package/dist/commands/checkHostCompatibility.js +149 -0
- package/dist/commands/copyAgentPrompt.js +52 -0
- package/dist/commands/copyContext.js +54 -0
- package/dist/commands/explainSelectionRelevance.js +57 -0
- package/dist/commands/findRelevantContext.js +127 -0
- package/dist/commands/openEvidence.js +49 -0
- package/dist/commands/openMcpConfig.js +81 -0
- package/dist/commands/openRelatedTests.js +54 -0
- package/dist/commands/rebuildIndex.js +45 -0
- package/dist/commands/runEvaluationSuite.js +323 -0
- package/dist/commands/runReplaySuite.js +228 -0
- package/dist/config/defaultConfig.js +72 -0
- package/dist/config/loadConfig.js +84 -0
- package/dist/config/loadConfigFromPath.js +60 -0
- package/dist/extension.js +513 -0
- package/dist/gateway/agentPrompts.js +176 -0
- package/dist/gateway/aiGateway.js +1255 -0
- package/dist/gateway/aiProviders.js +901 -0
- package/dist/gateway/contextExpansion.js +331 -0
- package/dist/gateway/editProposalStore.js +238 -0
- package/dist/gateway/planProposalStore.js +28 -0
- package/dist/index/cacheStore.js +51 -0
- package/dist/index/chunker.js +45 -0
- package/dist/index/dependencyExtractor.js +107 -0
- package/dist/index/fileDiscovery.js +177 -0
- package/dist/index/structuredExtractor.js +256 -0
- package/dist/index/workspaceIndex.js +518 -0
- package/dist/mcp/mcpConfig.js +154 -0
- package/dist/mcp/mcpProvider.js +109 -0
- package/dist/mcp/mcpTools.js +215 -0
- package/dist/pipeline/agentPrompt.js +79 -0
- package/dist/pipeline/agentPromptHeadless.js +85 -0
- package/dist/pipeline/contextBlueprint.js +346 -0
- package/dist/pipeline/contextPack.js +80 -0
- package/dist/pipeline/diffPreview.js +79 -0
- package/dist/pipeline/evaluationMetrics.js +154 -0
- package/dist/pipeline/evidenceGraph.js +389 -0
- package/dist/pipeline/feedback.js +215 -0
- package/dist/pipeline/fileGroups.js +84 -0
- package/dist/pipeline/fileScanner.js +866 -0
- package/dist/pipeline/nodeScan.js +219 -0
- package/dist/pipeline/ranker.js +563 -0
- package/dist/pipeline/responseLanguage.js +39 -0
- package/dist/pipeline/retrievalPlan.js +163 -0
- package/dist/pipeline/runHeadless.js +54 -0
- package/dist/pipeline/runPipeline.js +114 -0
- package/dist/pipeline/solvePacket.js +382 -0
- package/dist/pipeline/subsystem.js +257 -0
- package/dist/pipeline/taskUnderstanding.js +453 -0
- package/dist/pipeline/tokenSavings.js +146 -0
- package/dist/pipeline/universalScan.js +216 -0
- package/dist/pipeline/verifyAfterApply.js +233 -0
- package/dist/runtime/capabilities.js +71 -0
- package/dist/runtime/hostDetect.js +98 -0
- package/dist/runtime/hostTier.js +68 -0
- package/dist/runtime/statusBar.js +80 -0
- package/dist/skills/skillRegistry.js +208 -0
- package/dist/types.js +3 -0
- package/dist/ui/chatViewProvider.js +3899 -0
- package/dist/ui/resultTreeProvider.js +174 -0
- package/dist/usage/quotaTracker.js +358 -0
- package/dist/utils/async.js +30 -0
- package/dist/utils/logger.js +64 -0
- package/dist/utils/text.js +364 -0
- package/dist/utils/updateChecker.js +140 -0
- package/package.json +561 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ResultTreeProvider = exports.ResultNode = void 0;
|
|
37
|
+
const vscode = __importStar(require("vscode"));
|
|
38
|
+
class ResultNode {
|
|
39
|
+
constructor(type, label, role, unit) {
|
|
40
|
+
this.type = type;
|
|
41
|
+
this.label = label;
|
|
42
|
+
this.role = role;
|
|
43
|
+
this.unit = unit;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.ResultNode = ResultNode;
|
|
47
|
+
const roleLabels = [
|
|
48
|
+
{ role: "edit_candidate", label: "Most Likely Edit Areas" },
|
|
49
|
+
{ role: "read_context", label: "Read Before Editing" },
|
|
50
|
+
{ role: "impact", label: "Impact And Risk" },
|
|
51
|
+
{ role: "test", label: "Tests To Run Or Update" },
|
|
52
|
+
{ role: "verify", label: "Verify" },
|
|
53
|
+
{ role: "unknown", label: "Uncertain Candidates" }
|
|
54
|
+
];
|
|
55
|
+
class ResultTreeProvider {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.changeEmitter = new vscode.EventEmitter();
|
|
58
|
+
this.onDidChangeTreeData = this.changeEmitter.event;
|
|
59
|
+
this.checkedIds = new Set();
|
|
60
|
+
}
|
|
61
|
+
setResult(result) {
|
|
62
|
+
this.result = result;
|
|
63
|
+
this.checkedIds = new Set(result.units.filter((unit) => unit.role !== "unknown").map((unit) => unit.id));
|
|
64
|
+
this.changeEmitter.fire();
|
|
65
|
+
}
|
|
66
|
+
getResult() {
|
|
67
|
+
return this.result;
|
|
68
|
+
}
|
|
69
|
+
setChecked(unitId, checked) {
|
|
70
|
+
if (checked) {
|
|
71
|
+
this.checkedIds.add(unitId);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.checkedIds.delete(unitId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
getSelectedUnits() {
|
|
78
|
+
if (!this.result) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const selected = this.result.units.filter((unit) => this.checkedIds.has(unit.id));
|
|
82
|
+
return selected.length > 0 ? selected : this.result.units;
|
|
83
|
+
}
|
|
84
|
+
findUnitsForFile(filePath) {
|
|
85
|
+
return this.result?.units.filter((unit) => unit.file === filePath) ?? [];
|
|
86
|
+
}
|
|
87
|
+
getUnitsByRole(role) {
|
|
88
|
+
return this.result?.units.filter((unit) => unit.role === role) ?? [];
|
|
89
|
+
}
|
|
90
|
+
getTreeItem(element) {
|
|
91
|
+
if (element.type === "empty") {
|
|
92
|
+
const item = new vscode.TreeItem(element.label, vscode.TreeItemCollapsibleState.None);
|
|
93
|
+
item.iconPath = new vscode.ThemeIcon("search");
|
|
94
|
+
return item;
|
|
95
|
+
}
|
|
96
|
+
if (element.type === "group") {
|
|
97
|
+
const count = this.result?.units.filter((unit) => unit.role === element.role).length ?? 0;
|
|
98
|
+
const item = new vscode.TreeItem(`${element.label} (${count})`, vscode.TreeItemCollapsibleState.Expanded);
|
|
99
|
+
item.contextValue = "flowseekerGroup";
|
|
100
|
+
item.iconPath = iconForRole(element.role);
|
|
101
|
+
return item;
|
|
102
|
+
}
|
|
103
|
+
const unit = element.unit;
|
|
104
|
+
const range = unit?.range ? `:${unit.range.startLine}-${unit.range.endLine}` : "";
|
|
105
|
+
const item = new vscode.TreeItem(`${unit?.relativePath ?? element.label}${range}`, vscode.TreeItemCollapsibleState.None);
|
|
106
|
+
item.description = unit ? `${unit.kind} ${unit.tier} ${unit.confidence.toFixed(2)}` : undefined;
|
|
107
|
+
item.tooltip = unit ? tooltipForUnit(unit) : undefined;
|
|
108
|
+
item.contextValue = "flowseekerEvidence";
|
|
109
|
+
item.iconPath = unit ? iconForRole(unit.role) : undefined;
|
|
110
|
+
item.command = unit
|
|
111
|
+
? {
|
|
112
|
+
command: "flowseeker.openEvidence",
|
|
113
|
+
title: "Open Evidence",
|
|
114
|
+
arguments: [unit]
|
|
115
|
+
}
|
|
116
|
+
: undefined;
|
|
117
|
+
if (unit) {
|
|
118
|
+
item.checkboxState = this.checkedIds.has(unit.id) ? vscode.TreeItemCheckboxState.Checked : vscode.TreeItemCheckboxState.Unchecked;
|
|
119
|
+
}
|
|
120
|
+
return item;
|
|
121
|
+
}
|
|
122
|
+
getChildren(element) {
|
|
123
|
+
if (!this.result) {
|
|
124
|
+
return [new ResultNode("empty", "Run FlowSeeker: Find Relevant Context")];
|
|
125
|
+
}
|
|
126
|
+
if (!element) {
|
|
127
|
+
return roleLabels
|
|
128
|
+
.filter(({ role }) => this.result?.units.some((unit) => unit.role === role))
|
|
129
|
+
.map(({ role, label }) => new ResultNode("group", label, role));
|
|
130
|
+
}
|
|
131
|
+
if (element.type === "group" && element.role) {
|
|
132
|
+
return this.result.units
|
|
133
|
+
.filter((unit) => unit.role === element.role)
|
|
134
|
+
.map((unit) => new ResultNode("evidence", unit.relativePath, element.role, unit));
|
|
135
|
+
}
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.ResultTreeProvider = ResultTreeProvider;
|
|
140
|
+
function tooltipForUnit(unit) {
|
|
141
|
+
const lines = [
|
|
142
|
+
`**${unit.relativePath}**`,
|
|
143
|
+
"",
|
|
144
|
+
`Kind: ${unit.kind}`,
|
|
145
|
+
`Role: ${unit.role}`,
|
|
146
|
+
`Score: ${unit.score.toFixed(1)}`,
|
|
147
|
+
`Confidence: ${unit.confidence.toFixed(2)}`,
|
|
148
|
+
`Retrieval passes: ${unit.retrievalPasses.length > 0 ? unit.retrievalPasses.join(", ") : "seed"}`,
|
|
149
|
+
`Imports: ${unit.imports.length > 0 ? unit.imports.slice(0, 5).join(", ") : "none"}`,
|
|
150
|
+
"",
|
|
151
|
+
"Reasons:",
|
|
152
|
+
...unit.reasons.map((reason) => `- ${reason}`)
|
|
153
|
+
];
|
|
154
|
+
return new vscode.MarkdownString(lines.join("\n"));
|
|
155
|
+
}
|
|
156
|
+
function iconForRole(role) {
|
|
157
|
+
switch (role) {
|
|
158
|
+
case "edit_candidate":
|
|
159
|
+
return new vscode.ThemeIcon("edit");
|
|
160
|
+
case "read_context":
|
|
161
|
+
return new vscode.ThemeIcon("book");
|
|
162
|
+
case "impact":
|
|
163
|
+
return new vscode.ThemeIcon("references");
|
|
164
|
+
case "test":
|
|
165
|
+
return new vscode.ThemeIcon("beaker");
|
|
166
|
+
case "verify":
|
|
167
|
+
return new vscode.ThemeIcon("checklist");
|
|
168
|
+
case "unknown":
|
|
169
|
+
return new vscode.ThemeIcon("question");
|
|
170
|
+
default:
|
|
171
|
+
return new vscode.ThemeIcon("folder");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=resultTreeProvider.js.map
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.QuotaTracker = void 0;
|
|
37
|
+
exports.formatUsageSummary = formatUsageSummary;
|
|
38
|
+
const vscode = __importStar(require("vscode"));
|
|
39
|
+
const PRICING = {
|
|
40
|
+
"gpt-5.2": { inputPer1M: 2.50, outputPer1M: 10.00 },
|
|
41
|
+
"gpt-5.5": { inputPer1M: 2.50, outputPer1M: 10.00 },
|
|
42
|
+
"gpt-5": { inputPer1M: 2.50, outputPer1M: 10.00 },
|
|
43
|
+
"gpt-4o": { inputPer1M: 2.50, outputPer1M: 10.00 },
|
|
44
|
+
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.60 },
|
|
45
|
+
"gpt-4.1": { inputPer1M: 2.00, outputPer1M: 8.00 },
|
|
46
|
+
"gpt-4.1-mini": { inputPer1M: 0.40, outputPer1M: 1.60 },
|
|
47
|
+
"gpt-4.1-nano": { inputPer1M: 0.10, outputPer1M: 0.40 },
|
|
48
|
+
"o4-mini": { inputPer1M: 1.10, outputPer1M: 4.40 },
|
|
49
|
+
"claude-sonnet-4-5": { inputPer1M: 3.00, outputPer1M: 15.00 },
|
|
50
|
+
"claude-sonnet-4": { inputPer1M: 3.00, outputPer1M: 15.00 },
|
|
51
|
+
"claude-opus-4": { inputPer1M: 15.00, outputPer1M: 75.00 },
|
|
52
|
+
"claude-haiku-4-5": { inputPer1M: 0.80, outputPer1M: 4.00 },
|
|
53
|
+
"gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.60 },
|
|
54
|
+
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10.00 },
|
|
55
|
+
"grok-4": { inputPer1M: 2.00, outputPer1M: 8.00 },
|
|
56
|
+
"deepseek-chat": { inputPer1M: 0.27, outputPer1M: 1.10 },
|
|
57
|
+
"deepseek-reasoner": { inputPer1M: 0.55, outputPer1M: 2.19 },
|
|
58
|
+
"mistral-large-latest": { inputPer1M: 2.00, outputPer1M: 6.00 },
|
|
59
|
+
"llama-3.3-70b": { inputPer1M: 0.59, outputPer1M: 0.79 },
|
|
60
|
+
};
|
|
61
|
+
const DEFAULT_MONTHLY_COST = 10;
|
|
62
|
+
const QUOTA_LIMITS_KEY = "flowseeker.usage.limits";
|
|
63
|
+
const BUDGET_KEY = "flowseeker.usage.budget";
|
|
64
|
+
const LIVE_QUOTA_KEY = "flowseeker.usage.liveQuota.v1";
|
|
65
|
+
function getPricing(model) {
|
|
66
|
+
const lower = model.toLowerCase();
|
|
67
|
+
for (const [key, tier] of Object.entries(PRICING)) {
|
|
68
|
+
const lowerKey = key.toLowerCase();
|
|
69
|
+
if (lower === lowerKey || lower.includes(lowerKey) || lowerKey.includes(lower)) {
|
|
70
|
+
return tier;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { inputPer1M: 1.00, outputPer1M: 4.00 };
|
|
74
|
+
}
|
|
75
|
+
function calculateCost(model, inputTokens, outputTokens) {
|
|
76
|
+
const pricing = getPricing(model);
|
|
77
|
+
return (inputTokens / 1000000) * pricing.inputPer1M + (outputTokens / 1000000) * pricing.outputPer1M;
|
|
78
|
+
}
|
|
79
|
+
function usageStateKey() {
|
|
80
|
+
return "flowseeker.usage.records.v1";
|
|
81
|
+
}
|
|
82
|
+
function dailyResetMs() {
|
|
83
|
+
const now = new Date();
|
|
84
|
+
const tomorrow = new Date(now);
|
|
85
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
86
|
+
tomorrow.setHours(0, 0, 0, 0);
|
|
87
|
+
return tomorrow.getTime();
|
|
88
|
+
}
|
|
89
|
+
function monthlyResetMs() {
|
|
90
|
+
const now = new Date();
|
|
91
|
+
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
92
|
+
nextMonth.setHours(0, 0, 0, 0);
|
|
93
|
+
return nextMonth.getTime();
|
|
94
|
+
}
|
|
95
|
+
class QuotaTracker {
|
|
96
|
+
constructor(context) {
|
|
97
|
+
this.context = context;
|
|
98
|
+
this.records = [];
|
|
99
|
+
this.limits = {};
|
|
100
|
+
this.liveQuota = new Map();
|
|
101
|
+
this.onChangeCallbacks = [];
|
|
102
|
+
this.load();
|
|
103
|
+
}
|
|
104
|
+
onDidChange(callback) {
|
|
105
|
+
this.onChangeCallbacks.push(callback);
|
|
106
|
+
return { dispose: () => { this.onChangeCallbacks = this.onChangeCallbacks.filter((cb) => cb !== callback); } };
|
|
107
|
+
}
|
|
108
|
+
notifyChange() {
|
|
109
|
+
for (const cb of this.onChangeCallbacks) {
|
|
110
|
+
try {
|
|
111
|
+
cb();
|
|
112
|
+
}
|
|
113
|
+
catch { /* ignore */ }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
record(provider, model, inputTokens, outputTokens, providerQuota) {
|
|
117
|
+
const cost = calculateCost(model, inputTokens, outputTokens);
|
|
118
|
+
this.records.push({
|
|
119
|
+
provider,
|
|
120
|
+
model,
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
inputTokens,
|
|
123
|
+
outputTokens,
|
|
124
|
+
costUSD: cost
|
|
125
|
+
});
|
|
126
|
+
if (this.records.length > 2000) {
|
|
127
|
+
this.records = this.records.slice(-2000);
|
|
128
|
+
}
|
|
129
|
+
// Store latest live quota from provider
|
|
130
|
+
if (providerQuota && hasQuotaSignal(providerQuota)) {
|
|
131
|
+
this.liveQuota.set(`${provider}|${model}`, providerQuota);
|
|
132
|
+
this.saveLiveQuota();
|
|
133
|
+
}
|
|
134
|
+
this.save();
|
|
135
|
+
this.notifyChange();
|
|
136
|
+
void this.checkBudget();
|
|
137
|
+
}
|
|
138
|
+
getSummary() {
|
|
139
|
+
const todayStart = new Date();
|
|
140
|
+
todayStart.setHours(0, 0, 0, 0);
|
|
141
|
+
const monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1);
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const modelMap = new Map();
|
|
144
|
+
let totalInput = 0;
|
|
145
|
+
let totalOutput = 0;
|
|
146
|
+
let totalCost = 0;
|
|
147
|
+
let todayCost = 0;
|
|
148
|
+
for (const r of this.records) {
|
|
149
|
+
totalInput += r.inputTokens;
|
|
150
|
+
totalOutput += r.outputTokens;
|
|
151
|
+
totalCost += r.costUSD;
|
|
152
|
+
const key = `${r.provider}|${r.model}`;
|
|
153
|
+
let entry = modelMap.get(key);
|
|
154
|
+
if (!entry) {
|
|
155
|
+
entry = {
|
|
156
|
+
provider: r.provider,
|
|
157
|
+
model: r.model,
|
|
158
|
+
dailyTokens: 0,
|
|
159
|
+
monthlyCost: 0,
|
|
160
|
+
requestsToday: 0
|
|
161
|
+
};
|
|
162
|
+
modelMap.set(key, entry);
|
|
163
|
+
}
|
|
164
|
+
if (r.timestamp >= todayStart.getTime()) {
|
|
165
|
+
entry.dailyTokens += r.inputTokens + r.outputTokens;
|
|
166
|
+
entry.requestsToday++;
|
|
167
|
+
todayCost += r.costUSD;
|
|
168
|
+
}
|
|
169
|
+
if (r.timestamp >= monthStart.getTime()) {
|
|
170
|
+
entry.monthlyCost += r.costUSD;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const quotaKeys = new Set([...modelMap.keys(), ...this.liveQuota.keys()]);
|
|
174
|
+
const models = [];
|
|
175
|
+
for (const key of quotaKeys) {
|
|
176
|
+
const entry = modelMap.get(key) ?? createEmptyEntryFromKey(key);
|
|
177
|
+
if (!entry) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const userLimit = this.getLimit(entry.provider, entry.model);
|
|
181
|
+
const liveQuota = this.liveQuota.get(key);
|
|
182
|
+
const hasLiveQuota = liveQuota && hasQuotaSignal(liveQuota);
|
|
183
|
+
const hasLiveTokenLimit = typeof liveQuota?.tokensLimit === "number";
|
|
184
|
+
// Prefer provider quota when available. If a provider only exposes request
|
|
185
|
+
// limits, keep token quota unknown instead of showing a fake token cap.
|
|
186
|
+
const dailyLimit = hasLiveTokenLimit
|
|
187
|
+
? liveQuota.tokensLimit
|
|
188
|
+
: userLimit.dailyTokens ?? -1;
|
|
189
|
+
const monthlyLimit = userLimit.monthlyCost ?? DEFAULT_MONTHLY_COST;
|
|
190
|
+
const quotaSource = hasLiveQuota
|
|
191
|
+
? "live"
|
|
192
|
+
: userLimit.dailyTokens
|
|
193
|
+
? "manual"
|
|
194
|
+
: "unknown";
|
|
195
|
+
// Calculate tokens used from live quota (remaining) if available
|
|
196
|
+
const tokensUsed = hasLiveTokenLimit && liveQuota?.tokensRemaining !== undefined
|
|
197
|
+
? dailyLimit - liveQuota.tokensRemaining
|
|
198
|
+
: entry.dailyTokens;
|
|
199
|
+
const dailyPct = dailyLimit > 0 ? Math.min(100, Math.round((tokensUsed / dailyLimit) * 100)) : -1;
|
|
200
|
+
models.push({
|
|
201
|
+
provider: entry.provider,
|
|
202
|
+
model: entry.model,
|
|
203
|
+
dailyTokensUsed: tokensUsed,
|
|
204
|
+
dailyTokenLimit: dailyLimit,
|
|
205
|
+
dailyPct,
|
|
206
|
+
quotaSource,
|
|
207
|
+
requestsLimit: liveQuota?.requestsLimit,
|
|
208
|
+
requestsRemaining: liveQuota?.requestsRemaining,
|
|
209
|
+
monthlyCostUsed: Math.round(entry.monthlyCost * 10000) / 10000,
|
|
210
|
+
monthlyCostLimit: monthlyLimit,
|
|
211
|
+
monthlyPct: Math.min(100, Math.round((entry.monthlyCost / monthlyLimit) * 100)),
|
|
212
|
+
requestsToday: entry.requestsToday,
|
|
213
|
+
dailyResetMs: dailyResetMs(),
|
|
214
|
+
monthlyResetMs: monthlyResetMs()
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
models.sort((a, b) => b.monthlyCostUsed - a.monthlyCostUsed);
|
|
218
|
+
const weekAgo = now - 7 * 24 * 60 * 60 * 1000;
|
|
219
|
+
let weekCost = 0;
|
|
220
|
+
for (const r of this.records) {
|
|
221
|
+
if (r.timestamp >= weekAgo) {
|
|
222
|
+
weekCost += r.costUSD;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
totalRequests: this.records.length,
|
|
227
|
+
totalInputTokens: totalInput,
|
|
228
|
+
totalOutputTokens: totalOutput,
|
|
229
|
+
totalCostUSD: Math.round(totalCost * 10000) / 10000,
|
|
230
|
+
todayCostUSD: Math.round(todayCost * 10000) / 10000,
|
|
231
|
+
thisWeekCostUSD: Math.round(weekCost * 10000) / 10000,
|
|
232
|
+
models
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
async setLimit(provider, model, limit) {
|
|
236
|
+
const key = `${provider}|${model}`;
|
|
237
|
+
this.limits[key] = limit;
|
|
238
|
+
await this.saveLimits();
|
|
239
|
+
}
|
|
240
|
+
getLimit(provider, model) {
|
|
241
|
+
const key = `${provider}|${model}`;
|
|
242
|
+
return this.limits[key] ?? {};
|
|
243
|
+
}
|
|
244
|
+
async setBudget(limitUSD) {
|
|
245
|
+
await this.context.globalState.update(BUDGET_KEY, limitUSD);
|
|
246
|
+
}
|
|
247
|
+
getBudget() {
|
|
248
|
+
return this.context.globalState.get(BUDGET_KEY, 0);
|
|
249
|
+
}
|
|
250
|
+
async clearHistory() {
|
|
251
|
+
this.records = [];
|
|
252
|
+
await this.context.globalState.update(usageStateKey(), undefined);
|
|
253
|
+
}
|
|
254
|
+
load() {
|
|
255
|
+
const stored = this.context.globalState.get(usageStateKey(), []);
|
|
256
|
+
this.records = Array.isArray(stored) ? stored : [];
|
|
257
|
+
this.limits = this.context.globalState.get(QUOTA_LIMITS_KEY, {});
|
|
258
|
+
const storedLiveQuota = this.context.globalState.get(LIVE_QUOTA_KEY, {});
|
|
259
|
+
this.liveQuota = new Map(Object.entries(storedLiveQuota).filter((entry) => hasQuotaSignal(entry[1])));
|
|
260
|
+
}
|
|
261
|
+
save() {
|
|
262
|
+
void this.context.globalState.update(usageStateKey(), this.records);
|
|
263
|
+
}
|
|
264
|
+
async saveLimits() {
|
|
265
|
+
await this.context.globalState.update(QUOTA_LIMITS_KEY, this.limits);
|
|
266
|
+
}
|
|
267
|
+
saveLiveQuota() {
|
|
268
|
+
void this.context.globalState.update(LIVE_QUOTA_KEY, Object.fromEntries(this.liveQuota));
|
|
269
|
+
}
|
|
270
|
+
async checkBudget() {
|
|
271
|
+
const budget = this.getBudget();
|
|
272
|
+
if (budget <= 0) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const summary = this.getSummary();
|
|
276
|
+
const pctUsed = summary.todayCostUSD / budget;
|
|
277
|
+
if (pctUsed >= 0.75 && pctUsed < 1.0) {
|
|
278
|
+
void vscode.window.showWarningMessage(`FlowSeeker: ${Math.round(pctUsed * 100)}% of daily budget used ($${summary.todayCostUSD.toFixed(2)} / $${budget.toFixed(2)})`, "Dismiss");
|
|
279
|
+
}
|
|
280
|
+
else if (pctUsed >= 1.0) {
|
|
281
|
+
void vscode.window.showErrorMessage(`FlowSeeker: Daily budget exceeded ($${summary.todayCostUSD.toFixed(2)} / $${budget.toFixed(2)}).`, "Set new budget", "Dismiss").then((choice) => {
|
|
282
|
+
if (choice === "Set new budget") {
|
|
283
|
+
void vscode.commands.executeCommand("flowseeker.setBudget");
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
exports.QuotaTracker = QuotaTracker;
|
|
290
|
+
function hasQuotaSignal(value) {
|
|
291
|
+
return Boolean(value &&
|
|
292
|
+
(value.tokensLimit !== undefined ||
|
|
293
|
+
value.tokensRemaining !== undefined ||
|
|
294
|
+
value.requestsLimit !== undefined ||
|
|
295
|
+
value.requestsRemaining !== undefined));
|
|
296
|
+
}
|
|
297
|
+
function createEmptyEntryFromKey(key) {
|
|
298
|
+
const separator = key.indexOf("|");
|
|
299
|
+
if (separator <= 0) {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
provider: key.slice(0, separator),
|
|
304
|
+
model: key.slice(separator + 1),
|
|
305
|
+
dailyTokens: 0,
|
|
306
|
+
monthlyCost: 0,
|
|
307
|
+
requestsToday: 0
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function formatUsageSummary(summary) {
|
|
311
|
+
const lines = [
|
|
312
|
+
`**Today:** $${summary.todayCostUSD.toFixed(3)} | **Week:** $${summary.thisWeekCostUSD.toFixed(3)} | **Total:** $${summary.totalCostUSD.toFixed(3)}`,
|
|
313
|
+
`**Requests:** ${summary.totalRequests} | **Tokens:** ${formatTokenCount(summary.totalInputTokens + summary.totalOutputTokens)}`,
|
|
314
|
+
"",
|
|
315
|
+
];
|
|
316
|
+
if (summary.models.length > 0) {
|
|
317
|
+
lines.push("| Provider/Model | Today Tokens | Daily % | Month Cost | Month % |");
|
|
318
|
+
lines.push("|---|---|---|---|---|");
|
|
319
|
+
for (const m of summary.models.slice(0, 10)) {
|
|
320
|
+
const dailyLabel = m.dailyTokenLimit > 0
|
|
321
|
+
? `${formatTokenCount(m.dailyTokensUsed)} / ${formatTokenCount(m.dailyTokenLimit)}`
|
|
322
|
+
: `${formatTokenCount(m.dailyTokensUsed)} / token limit unknown`;
|
|
323
|
+
const dailyPctLabel = m.dailyPct >= 0
|
|
324
|
+
? `${progressBar(m.dailyPct)} ${m.dailyPct}%`
|
|
325
|
+
: formatRequestQuota(m);
|
|
326
|
+
lines.push(`| ${m.provider} / ${m.model} | ${dailyLabel} | ${dailyPctLabel} | $${m.monthlyCostUsed.toFixed(2)} / $${m.monthlyCostLimit} | ${progressBar(m.monthlyPct)} ${m.monthlyPct}% |`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return lines.join("\n");
|
|
330
|
+
}
|
|
331
|
+
function formatRequestQuota(model) {
|
|
332
|
+
if (model.requestsLimit !== undefined && model.requestsRemaining !== undefined) {
|
|
333
|
+
const used = Math.max(0, model.requestsLimit - model.requestsRemaining);
|
|
334
|
+
return `${formatTokenCount(used)} / ${formatTokenCount(model.requestsLimit)} requests`;
|
|
335
|
+
}
|
|
336
|
+
if (model.requestsRemaining !== undefined) {
|
|
337
|
+
return `${formatTokenCount(model.requestsRemaining)} requests remaining`;
|
|
338
|
+
}
|
|
339
|
+
if (model.requestsLimit !== undefined) {
|
|
340
|
+
return `${formatTokenCount(model.requestsLimit)} request limit`;
|
|
341
|
+
}
|
|
342
|
+
return model.quotaSource === "live" ? "live quota" : "unknown";
|
|
343
|
+
}
|
|
344
|
+
function progressBar(pct) {
|
|
345
|
+
const filled = Math.round(pct / 10);
|
|
346
|
+
const empty = 10 - filled;
|
|
347
|
+
return `[${"#".repeat(filled)}${".".repeat(empty)}]`;
|
|
348
|
+
}
|
|
349
|
+
function formatTokenCount(n) {
|
|
350
|
+
if (n >= 1000000) {
|
|
351
|
+
return `${(n / 1000000).toFixed(1)}M`;
|
|
352
|
+
}
|
|
353
|
+
if (n >= 1000) {
|
|
354
|
+
return `${(n / 1000).toFixed(0)}K`;
|
|
355
|
+
}
|
|
356
|
+
return String(n);
|
|
357
|
+
}
|
|
358
|
+
//# sourceMappingURL=quotaTracker.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withTimeout = withTimeout;
|
|
4
|
+
exports.checkCancelled = checkCancelled;
|
|
5
|
+
async function withTimeout(promise, ms, label) {
|
|
6
|
+
if (ms <= 0) {
|
|
7
|
+
return promise;
|
|
8
|
+
}
|
|
9
|
+
let timeoutId;
|
|
10
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
11
|
+
timeoutId = setTimeout(() => {
|
|
12
|
+
reject(new Error(`Timed out after ${ms}ms: ${label}`));
|
|
13
|
+
}, ms);
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
const result = await Promise.race([promise, timeout]);
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
if (timeoutId) {
|
|
21
|
+
clearTimeout(timeoutId);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function checkCancelled(token) {
|
|
26
|
+
if (token?.isCancellationRequested) {
|
|
27
|
+
throw new Error("Cancelled");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=async.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.initializeLogger = initializeLogger;
|
|
37
|
+
exports.showLog = showLog;
|
|
38
|
+
exports.logInfo = logInfo;
|
|
39
|
+
exports.logWarn = logWarn;
|
|
40
|
+
exports.logError = logError;
|
|
41
|
+
const vscode = __importStar(require("vscode"));
|
|
42
|
+
let channel;
|
|
43
|
+
function initializeLogger(context) {
|
|
44
|
+
channel = vscode.window.createOutputChannel("FlowSeeker");
|
|
45
|
+
context.subscriptions.push(channel);
|
|
46
|
+
}
|
|
47
|
+
function showLog() {
|
|
48
|
+
channel?.show(true);
|
|
49
|
+
}
|
|
50
|
+
function logInfo(message) {
|
|
51
|
+
write("INFO", message);
|
|
52
|
+
}
|
|
53
|
+
function logWarn(message) {
|
|
54
|
+
write("WARN", message);
|
|
55
|
+
}
|
|
56
|
+
function logError(message, error) {
|
|
57
|
+
const suffix = error instanceof Error ? `\n${error.stack ?? error.message}` : error ? `\n${String(error)}` : "";
|
|
58
|
+
write("ERROR", `${message}${suffix}`);
|
|
59
|
+
}
|
|
60
|
+
function write(level, message) {
|
|
61
|
+
const timestamp = new Date().toISOString();
|
|
62
|
+
channel?.appendLine(`[${level}] ${timestamp} - ${message}`);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=logger.js.map
|