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,1255 @@
|
|
|
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.AiGatewayClient = void 0;
|
|
37
|
+
exports.parseAgentResponse = parseAgentResponse;
|
|
38
|
+
const vscode = __importStar(require("vscode"));
|
|
39
|
+
const fileGroups_1 = require("../pipeline/fileGroups");
|
|
40
|
+
const responseLanguage_1 = require("../pipeline/responseLanguage");
|
|
41
|
+
const agentPrompts_1 = require("./agentPrompts");
|
|
42
|
+
const aiProviders_1 = require("./aiProviders");
|
|
43
|
+
const oauthHandler_1 = require("../auth/oauthHandler");
|
|
44
|
+
const githubAuth_1 = require("../auth/githubAuth");
|
|
45
|
+
const modelList_1 = require("../auth/modelList");
|
|
46
|
+
const tokenSecretKey = "flowseeker.aiGateway.token";
|
|
47
|
+
const endpointStateKey = "flowseeker.aiGateway.endpoint";
|
|
48
|
+
const activeProviderStateKey = "flowseeker.ai.provider";
|
|
49
|
+
const modelStateKey = "flowseeker.ai.model";
|
|
50
|
+
const baseUrlStateKey = "flowseeker.ai.baseUrl";
|
|
51
|
+
const providerProfilesStateKey = "flowseeker.ai.providerProfiles.v1";
|
|
52
|
+
const activeProviderProfileStateKey = "flowseeker.ai.activeProviderProfileId.v1";
|
|
53
|
+
const maxProviderProfiles = 24;
|
|
54
|
+
class AiGatewayClient {
|
|
55
|
+
constructor(context, quotaTracker) {
|
|
56
|
+
this.context = context;
|
|
57
|
+
this.quotaTracker = quotaTracker;
|
|
58
|
+
}
|
|
59
|
+
async getConnection() {
|
|
60
|
+
const activeProfile = await this.getActiveProfile();
|
|
61
|
+
if (activeProfile) {
|
|
62
|
+
return this.getProfileConnection(activeProfile);
|
|
63
|
+
}
|
|
64
|
+
const provider = this.getActiveProvider();
|
|
65
|
+
if (provider) {
|
|
66
|
+
return this.getProviderConnection(provider);
|
|
67
|
+
}
|
|
68
|
+
const endpoint = this.context.globalState.get(endpointStateKey);
|
|
69
|
+
const token = await this.context.secrets.get(tokenSecretKey);
|
|
70
|
+
return {
|
|
71
|
+
endpoint,
|
|
72
|
+
baseUrl: endpoint,
|
|
73
|
+
connected: Boolean(endpoint && token),
|
|
74
|
+
mode: endpoint && token ? "customGateway" : undefined,
|
|
75
|
+
provider: endpoint && token ? "customGateway" : undefined,
|
|
76
|
+
providerLabel: endpoint && token ? "Custom Gateway" : undefined,
|
|
77
|
+
model: endpoint && token ? "configured-by-gateway" : undefined,
|
|
78
|
+
requiresApiKey: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async listProviderProfiles() {
|
|
82
|
+
const profiles = await this.getProfiles();
|
|
83
|
+
const activeProfile = await this.getActiveProfile(profiles);
|
|
84
|
+
return {
|
|
85
|
+
activeProfileId: activeProfile?.id,
|
|
86
|
+
profiles: await Promise.all(profiles.map((profile) => this.hydrateProfile(profile)))
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async setActiveProviderProfile(profileId) {
|
|
90
|
+
const profiles = await this.getProfiles();
|
|
91
|
+
const profile = profiles.find((item) => item.id === profileId);
|
|
92
|
+
if (!profile) {
|
|
93
|
+
throw new Error("AI provider profile was not found.");
|
|
94
|
+
}
|
|
95
|
+
await this.activateProfile(profile);
|
|
96
|
+
return this.getProfileConnection(profile);
|
|
97
|
+
}
|
|
98
|
+
async removeProviderProfile(profileId) {
|
|
99
|
+
const profiles = await this.getProfiles();
|
|
100
|
+
const activeProfile = await this.getActiveProfile(profiles);
|
|
101
|
+
const targetId = profileId ?? activeProfile?.id;
|
|
102
|
+
if (!targetId) {
|
|
103
|
+
await this.clearLegacyActiveState();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const nextProfiles = profiles.filter((profile) => profile.id !== targetId);
|
|
107
|
+
await this.context.secrets.delete(profileSecretKey(targetId));
|
|
108
|
+
await this.saveProfiles(nextProfiles);
|
|
109
|
+
if (activeProfile?.id === targetId) {
|
|
110
|
+
const nextActive = nextProfiles[0];
|
|
111
|
+
if (nextActive) {
|
|
112
|
+
await this.activateProfile(nextActive);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
await this.context.globalState.update(activeProviderProfileStateKey, undefined);
|
|
116
|
+
await this.clearLegacyActiveState();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async configureProvider(input) {
|
|
121
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(input.provider);
|
|
122
|
+
const cleanBaseUrl = (input.baseUrl ?? definition.defaultBaseUrl).trim();
|
|
123
|
+
const cleanModel = (input.model ?? definition.defaultModel).trim();
|
|
124
|
+
const cleanKey = input.apiKey?.trim();
|
|
125
|
+
if (input.provider === "customGateway" || input.provider === "customAnthropic") {
|
|
126
|
+
if (!cleanModel) {
|
|
127
|
+
throw new Error(`${definition.label} requires a model.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!cleanBaseUrl) {
|
|
131
|
+
throw new Error(`${definition.label} base URL is required.`);
|
|
132
|
+
}
|
|
133
|
+
if (!cleanModel) {
|
|
134
|
+
throw new Error(`${definition.label} model is required.`);
|
|
135
|
+
}
|
|
136
|
+
const profileId = createProviderProfileId(input.provider, cleanModel, cleanBaseUrl);
|
|
137
|
+
const existingProfileKey = await this.context.secrets.get(profileSecretKey(profileId));
|
|
138
|
+
const existingProviderKey = await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(input.provider));
|
|
139
|
+
const isOAuth = definition.authMethod === "oauth";
|
|
140
|
+
if (!isOAuth && definition.requiresApiKey && !cleanKey && !existingProfileKey && !existingProviderKey) {
|
|
141
|
+
throw new Error(`${definition.label} API key is required.`);
|
|
142
|
+
}
|
|
143
|
+
const profile = {
|
|
144
|
+
id: profileId,
|
|
145
|
+
provider: input.provider,
|
|
146
|
+
model: cleanModel,
|
|
147
|
+
baseUrl: cleanBaseUrl,
|
|
148
|
+
updatedAt: new Date().toISOString()
|
|
149
|
+
};
|
|
150
|
+
await this.upsertProfile(profile);
|
|
151
|
+
const keyForProfile = cleanKey || existingProfileKey || existingProviderKey;
|
|
152
|
+
if (keyForProfile) {
|
|
153
|
+
await this.context.secrets.store(profileSecretKey(profile.id), keyForProfile);
|
|
154
|
+
}
|
|
155
|
+
if (cleanKey) {
|
|
156
|
+
await this.context.secrets.store((0, aiProviders_1.providerSecretKey)(input.provider), cleanKey);
|
|
157
|
+
if (input.provider === "customGateway") {
|
|
158
|
+
await this.context.secrets.store(tokenSecretKey, cleanKey);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
await this.activateProfile(profile);
|
|
162
|
+
}
|
|
163
|
+
async connect(endpoint, token, model = "configured-by-gateway") {
|
|
164
|
+
const cleanEndpoint = endpoint.trim();
|
|
165
|
+
const cleanToken = token.trim();
|
|
166
|
+
if (!cleanEndpoint) {
|
|
167
|
+
throw new Error("AI Gateway endpoint is required.");
|
|
168
|
+
}
|
|
169
|
+
if (!cleanToken) {
|
|
170
|
+
throw new Error("AI Gateway token is required.");
|
|
171
|
+
}
|
|
172
|
+
await this.configureProvider({
|
|
173
|
+
provider: "customGateway",
|
|
174
|
+
baseUrl: cleanEndpoint,
|
|
175
|
+
model: model.trim() || "configured-by-gateway",
|
|
176
|
+
apiKey: cleanToken
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async disconnect() {
|
|
180
|
+
const activeProfile = await this.getActiveProfile();
|
|
181
|
+
if (activeProfile) {
|
|
182
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(activeProfile.provider);
|
|
183
|
+
if (definition.authMethod === "oauth") {
|
|
184
|
+
await (0, oauthHandler_1.clearTokens)(this.context, activeProfile.provider);
|
|
185
|
+
(0, modelList_1.clearCachedModels)(this.context, activeProfile.provider);
|
|
186
|
+
}
|
|
187
|
+
await this.removeProviderProfile(activeProfile.id);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const activeProvider = this.getActiveProvider();
|
|
191
|
+
if (activeProvider) {
|
|
192
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(activeProvider);
|
|
193
|
+
if (definition.authMethod === "oauth") {
|
|
194
|
+
await (0, oauthHandler_1.clearTokens)(this.context, activeProvider);
|
|
195
|
+
(0, modelList_1.clearCachedModels)(this.context, activeProvider);
|
|
196
|
+
}
|
|
197
|
+
await this.context.secrets.delete((0, aiProviders_1.providerSecretKey)(activeProvider));
|
|
198
|
+
}
|
|
199
|
+
await this.clearLegacyActiveState();
|
|
200
|
+
}
|
|
201
|
+
async ask(result, mode) {
|
|
202
|
+
const activeProvider = this.getActiveProvider();
|
|
203
|
+
if (activeProvider && activeProvider !== "customGateway") {
|
|
204
|
+
const prompt = mode === "auto" ? (0, agentPrompts_1.createPlanPrompt)(result) : (0, agentPrompts_1.createGuidancePrompt)(result);
|
|
205
|
+
const { text, responseHeaders } = await this.askDirectProvider(prompt, false, activeProvider);
|
|
206
|
+
const response = mode === "auto" ? { message: text.trim() || "The selected provider returned an empty plan." } : parseAgentResponse(text);
|
|
207
|
+
this.recordAskUsage(prompt, text, responseHeaders);
|
|
208
|
+
return response;
|
|
209
|
+
}
|
|
210
|
+
const response = await this.askWithInstruction(result, mode);
|
|
211
|
+
const prompt = (0, agentPrompts_1.createGuidancePrompt)(result);
|
|
212
|
+
this.recordAskUsage(prompt, response.message, {});
|
|
213
|
+
return response;
|
|
214
|
+
}
|
|
215
|
+
async askDirectChat(task) {
|
|
216
|
+
const provider = this.getActiveProvider();
|
|
217
|
+
if (!provider) {
|
|
218
|
+
throw new Error("No AI provider is connected. Connect one in the sidebar before using Chat mode.");
|
|
219
|
+
}
|
|
220
|
+
if (provider === "customGateway") {
|
|
221
|
+
const endpoint = this.context.globalState.get(endpointStateKey) || this.context.globalState.get(baseUrlStateKey);
|
|
222
|
+
const token = await this.context.secrets.get(tokenSecretKey);
|
|
223
|
+
const model = this.context.globalState.get(modelStateKey) || "configured-by-gateway";
|
|
224
|
+
if (!endpoint || !token) {
|
|
225
|
+
throw new Error("AI Gateway is not connected.");
|
|
226
|
+
}
|
|
227
|
+
if (!this.shouldUseCustomOpenAICompatible(model)) {
|
|
228
|
+
throw new Error("Chat mode requires a Custom OpenAI-Compatible model name for Custom Gateway profiles.");
|
|
229
|
+
}
|
|
230
|
+
const { text, responseHeaders } = await (0, aiProviders_1.invokeCustomOpenAICompatibleProvider)({
|
|
231
|
+
provider: "customGateway",
|
|
232
|
+
model,
|
|
233
|
+
baseUrl: endpoint,
|
|
234
|
+
apiKey: token,
|
|
235
|
+
prompt: task
|
|
236
|
+
});
|
|
237
|
+
this.recordGatewayUsage("customGateway", model, task, text, responseHeaders);
|
|
238
|
+
return { message: text.trim() || "(empty response)" };
|
|
239
|
+
}
|
|
240
|
+
const { text, responseHeaders } = await this.askDirectProvider(task, false, provider);
|
|
241
|
+
this.recordAskUsage(task, text, responseHeaders);
|
|
242
|
+
return { message: text.trim() || "(empty response)" };
|
|
243
|
+
}
|
|
244
|
+
async askDirectChatStream(task, onToken, signal, activeProvider) {
|
|
245
|
+
const provider = activeProvider ?? this.getActiveProvider();
|
|
246
|
+
if (!provider) {
|
|
247
|
+
throw new Error("No AI provider is connected. Connect one in the sidebar before using Chat mode.");
|
|
248
|
+
}
|
|
249
|
+
if (provider === "customGateway") {
|
|
250
|
+
const endpoint = this.context.globalState.get(endpointStateKey) || this.context.globalState.get(baseUrlStateKey);
|
|
251
|
+
const token = await this.context.secrets.get(tokenSecretKey);
|
|
252
|
+
const model = this.context.globalState.get(modelStateKey) || "configured-by-gateway";
|
|
253
|
+
if (!endpoint || !token) {
|
|
254
|
+
throw new Error("AI Gateway is not connected.");
|
|
255
|
+
}
|
|
256
|
+
if (!this.shouldUseCustomOpenAICompatible(model)) {
|
|
257
|
+
throw new Error("Chat mode requires a Custom OpenAI-Compatible model name for Custom Gateway profiles.");
|
|
258
|
+
}
|
|
259
|
+
const { text, responseHeaders } = await (0, aiProviders_1.invokeCustomOpenAICompatibleProviderStream)({
|
|
260
|
+
provider: "customGateway",
|
|
261
|
+
model,
|
|
262
|
+
baseUrl: endpoint,
|
|
263
|
+
apiKey: token,
|
|
264
|
+
prompt: task,
|
|
265
|
+
onToken,
|
|
266
|
+
signal
|
|
267
|
+
});
|
|
268
|
+
this.recordGatewayUsage("customGateway", model, task, text, responseHeaders);
|
|
269
|
+
return { message: text.trim() || "(empty response)" };
|
|
270
|
+
}
|
|
271
|
+
const { text, responseHeaders } = await this.askDirectProviderStream(task, onToken, signal, provider);
|
|
272
|
+
this.recordAskUsage(task, text, responseHeaders);
|
|
273
|
+
return { message: text.trim() || "(empty response)" };
|
|
274
|
+
}
|
|
275
|
+
async askStream(result, mode, onToken, signal) {
|
|
276
|
+
if (mode === "auto") {
|
|
277
|
+
return this.ask(result, mode);
|
|
278
|
+
}
|
|
279
|
+
const prompt = (0, agentPrompts_1.createGuidancePrompt)(result);
|
|
280
|
+
const activeProvider = this.getActiveProvider();
|
|
281
|
+
if (activeProvider && activeProvider !== "customGateway") {
|
|
282
|
+
const { text, responseHeaders } = await this.askDirectProviderStream(prompt, onToken, signal, activeProvider);
|
|
283
|
+
const response = parseAgentResponse(text);
|
|
284
|
+
this.recordAskUsage(prompt, text, responseHeaders);
|
|
285
|
+
return response;
|
|
286
|
+
}
|
|
287
|
+
const endpoint = this.context.globalState.get(endpointStateKey);
|
|
288
|
+
const token = await this.context.secrets.get(tokenSecretKey);
|
|
289
|
+
const model = this.context.globalState.get(modelStateKey) || "configured-by-gateway";
|
|
290
|
+
if (!endpoint || !token) {
|
|
291
|
+
throw new Error("AI Gateway is not connected.");
|
|
292
|
+
}
|
|
293
|
+
if (this.shouldUseCustomOpenAICompatible(model)) {
|
|
294
|
+
const { text, responseHeaders } = await (0, aiProviders_1.invokeCustomOpenAICompatibleProviderStream)({
|
|
295
|
+
provider: "customGateway",
|
|
296
|
+
model,
|
|
297
|
+
baseUrl: endpoint,
|
|
298
|
+
apiKey: token,
|
|
299
|
+
prompt,
|
|
300
|
+
onToken,
|
|
301
|
+
signal
|
|
302
|
+
});
|
|
303
|
+
this.recordGatewayUsage("customGateway", model, prompt, text, responseHeaders);
|
|
304
|
+
return parseAgentResponse(text);
|
|
305
|
+
}
|
|
306
|
+
const response = await this.askWithInstruction(result, mode);
|
|
307
|
+
onToken(response.message);
|
|
308
|
+
this.recordAskUsage(prompt, response.message, {});
|
|
309
|
+
return response;
|
|
310
|
+
}
|
|
311
|
+
async askWithInstruction(result, mode, instruction, extra) {
|
|
312
|
+
const activeProvider = this.getActiveProvider();
|
|
313
|
+
if (activeProvider && activeProvider !== "customGateway") {
|
|
314
|
+
const prompt = instruction
|
|
315
|
+
? (0, agentPrompts_1.createPromptWithInstruction)(result, instruction)
|
|
316
|
+
: mode === "auto"
|
|
317
|
+
? (0, agentPrompts_1.createPlanPrompt)(result)
|
|
318
|
+
: (0, agentPrompts_1.createGuidancePrompt)(result);
|
|
319
|
+
const jsonMode = extra?.stage === "approved_edits";
|
|
320
|
+
const { text } = await this.askDirectProvider(prompt, jsonMode, activeProvider);
|
|
321
|
+
return parseAgentResponse(text);
|
|
322
|
+
}
|
|
323
|
+
const endpoint = this.context.globalState.get(endpointStateKey);
|
|
324
|
+
const token = await this.context.secrets.get(tokenSecretKey);
|
|
325
|
+
const model = this.context.globalState.get(modelStateKey) || "configured-by-gateway";
|
|
326
|
+
if (!endpoint || !token) {
|
|
327
|
+
throw new Error("AI Gateway is not connected.");
|
|
328
|
+
}
|
|
329
|
+
if (this.shouldUseCustomOpenAICompatible(model)) {
|
|
330
|
+
const prompt = instruction
|
|
331
|
+
? (0, agentPrompts_1.createPromptWithInstruction)(result, instruction)
|
|
332
|
+
: mode === "auto"
|
|
333
|
+
? (0, agentPrompts_1.createPlanPrompt)(result)
|
|
334
|
+
: (0, agentPrompts_1.createGuidancePrompt)(result);
|
|
335
|
+
const jsonMode = extra?.stage === "approved_edits";
|
|
336
|
+
const { text, responseHeaders } = await (0, aiProviders_1.invokeCustomOpenAICompatibleProvider)({
|
|
337
|
+
provider: "customGateway",
|
|
338
|
+
model,
|
|
339
|
+
baseUrl: endpoint,
|
|
340
|
+
apiKey: token,
|
|
341
|
+
prompt,
|
|
342
|
+
jsonMode
|
|
343
|
+
});
|
|
344
|
+
this.recordGatewayUsage("customGateway", model, prompt, text, responseHeaders);
|
|
345
|
+
return parseAgentResponse(text);
|
|
346
|
+
}
|
|
347
|
+
const response = await fetch(endpoint, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: {
|
|
350
|
+
"content-type": "application/json",
|
|
351
|
+
authorization: `Bearer ${token}`
|
|
352
|
+
},
|
|
353
|
+
body: JSON.stringify(toGatewayPayload(result, mode, instruction, extra))
|
|
354
|
+
});
|
|
355
|
+
const responseHeaders = {};
|
|
356
|
+
response.headers.forEach((value, key) => { responseHeaders[key.toLowerCase()] = value; });
|
|
357
|
+
const text = await response.text();
|
|
358
|
+
if (!response.ok) {
|
|
359
|
+
throw new Error(`AI Gateway returned ${response.status}: ${text.slice(0, 500)}`);
|
|
360
|
+
}
|
|
361
|
+
if (looksLikeHtml(text)) {
|
|
362
|
+
throw new Error("AI Gateway returned an HTML page, not an agent response. Use the API base URL, usually ending in /v1, not the dashboard/home page URL. For AI Box/new-api, choose Custom OpenAI-Compatible and enter the model name.");
|
|
363
|
+
}
|
|
364
|
+
this.recordGatewayUsage("customGateway", model, JSON.stringify(toGatewayPayload(result, mode, instruction, extra)), text, responseHeaders);
|
|
365
|
+
return parseAgentResponse(text);
|
|
366
|
+
}
|
|
367
|
+
async testProvider(input) {
|
|
368
|
+
if (input) {
|
|
369
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(input.provider);
|
|
370
|
+
const baseUrl = (input.baseUrl ?? definition.defaultBaseUrl).trim();
|
|
371
|
+
const model = (input.model ?? definition.defaultModel).trim();
|
|
372
|
+
const profileId = createProviderProfileId(input.provider, model, baseUrl);
|
|
373
|
+
const apiKey = definition.authMethod === "oauth"
|
|
374
|
+
? (await this.getOAuthAccessToken(input.provider)) ?? undefined
|
|
375
|
+
: input.apiKey?.trim() ||
|
|
376
|
+
(await this.context.secrets.get(profileSecretKey(profileId))) ||
|
|
377
|
+
(await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(input.provider)));
|
|
378
|
+
if (definition.protocol === "local-agent-cli") {
|
|
379
|
+
const cmd = input.apiKey?.trim() || definition.label;
|
|
380
|
+
throw new Error(`Local CLI provider "${definition.label}" cannot be tested with a text generation probe.\n\n` +
|
|
381
|
+
`**Setup:** Make sure \`${cmd}\` is installed and logged in.\n` +
|
|
382
|
+
`**Command field:** Set the CLI path or command in the provider configuration.\n` +
|
|
383
|
+
`**Verification:** Run \`${cmd} --version\` or \`${cmd} --help\` in a terminal to confirm the CLI is available.`);
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
if (input.provider === "customGateway") {
|
|
387
|
+
const prompt = "Reply with exactly: OK";
|
|
388
|
+
const { text, responseHeaders } = await (0, aiProviders_1.invokeCustomOpenAICompatibleProvider)({
|
|
389
|
+
provider: "customGateway",
|
|
390
|
+
model,
|
|
391
|
+
baseUrl,
|
|
392
|
+
apiKey,
|
|
393
|
+
prompt
|
|
394
|
+
});
|
|
395
|
+
this.recordGatewayUsage("customGateway", model, prompt, text, responseHeaders);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
const prompt = "Reply with exactly: OK";
|
|
399
|
+
const { text, responseHeaders } = await (0, aiProviders_1.invokeDirectProvider)({
|
|
400
|
+
provider: input.provider,
|
|
401
|
+
model,
|
|
402
|
+
baseUrl,
|
|
403
|
+
apiKey,
|
|
404
|
+
prompt,
|
|
405
|
+
cwd: workspaceRootPath()
|
|
406
|
+
});
|
|
407
|
+
this.recordProviderUsage(input.provider, model, prompt, text, responseHeaders);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
throw new Error(classifyProviderError(error, definition.label));
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
endpoint: baseUrl,
|
|
415
|
+
baseUrl,
|
|
416
|
+
connected: true,
|
|
417
|
+
mode: input.provider === "customGateway" ? "customGateway" : "direct",
|
|
418
|
+
provider: input.provider,
|
|
419
|
+
providerLabel: definition.label,
|
|
420
|
+
model,
|
|
421
|
+
requiresApiKey: definition.requiresApiKey
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const connection = await this.getConnection();
|
|
425
|
+
if (!connection.connected || !connection.provider) {
|
|
426
|
+
throw new Error("No AI provider is connected.");
|
|
427
|
+
}
|
|
428
|
+
if (connection.provider === "customGateway") {
|
|
429
|
+
await this.testCustomGateway();
|
|
430
|
+
return connection;
|
|
431
|
+
}
|
|
432
|
+
await this.askDirectProvider("Reply with exactly: OK");
|
|
433
|
+
return connection;
|
|
434
|
+
}
|
|
435
|
+
async handleOAuthCallback(providerId, _code) {
|
|
436
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(providerId);
|
|
437
|
+
if (definition.authMethod !== "oauth" || !definition.oauthConfig) {
|
|
438
|
+
throw new Error(`Provider ${providerId} does not support OAuth.`);
|
|
439
|
+
}
|
|
440
|
+
const hasTokens = await (0, oauthHandler_1.hasValidOAuthSession)(this.context, providerId);
|
|
441
|
+
if (!hasTokens) {
|
|
442
|
+
throw new Error("OAuth tokens were not stored. Please try signing in again.");
|
|
443
|
+
}
|
|
444
|
+
const models = await this.listModels(providerId);
|
|
445
|
+
const defaultModel = models[0]?.id || definition.defaultModel;
|
|
446
|
+
await this.configureProvider({
|
|
447
|
+
provider: providerId,
|
|
448
|
+
model: defaultModel,
|
|
449
|
+
baseUrl: definition.defaultBaseUrl,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
async listModels(providerId) {
|
|
453
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(providerId);
|
|
454
|
+
if (!definition.supportsModelList) {
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
const activeProfile = await this.getActiveProfile();
|
|
458
|
+
const baseUrl = activeProfile?.provider === providerId
|
|
459
|
+
? activeProfile.baseUrl
|
|
460
|
+
: this.context.globalState.get(baseUrlStateKey) || definition.defaultBaseUrl;
|
|
461
|
+
const token = definition.authMethod === "oauth"
|
|
462
|
+
? (await this.getOAuthAccessToken(providerId)) ?? undefined
|
|
463
|
+
: activeProfile?.provider === providerId
|
|
464
|
+
? await this.context.secrets.get(profileSecretKey(activeProfile.id)) || await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(providerId))
|
|
465
|
+
: await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(providerId));
|
|
466
|
+
return this.fetchModels(providerId, baseUrl, token);
|
|
467
|
+
}
|
|
468
|
+
async listModelsForInput(input) {
|
|
469
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(input.provider);
|
|
470
|
+
if (!definition.supportsModelList) {
|
|
471
|
+
return [];
|
|
472
|
+
}
|
|
473
|
+
const baseUrl = (input.baseUrl ?? definition.defaultBaseUrl).trim();
|
|
474
|
+
const profileId = createProviderProfileId(input.provider, input.model ?? definition.defaultModel, baseUrl);
|
|
475
|
+
const token = definition.authMethod === "oauth"
|
|
476
|
+
? (await this.getOAuthAccessToken(input.provider)) ?? undefined
|
|
477
|
+
: input.apiKey?.trim() ||
|
|
478
|
+
(await this.context.secrets.get(profileSecretKey(profileId))) ||
|
|
479
|
+
(await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(input.provider)));
|
|
480
|
+
return this.fetchModels(input.provider, baseUrl, token);
|
|
481
|
+
}
|
|
482
|
+
async fetchModels(providerId, baseUrl, token) {
|
|
483
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(providerId);
|
|
484
|
+
if (!definition.supportsModelList) {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
if (definition.requiresApiKey && !token) {
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
490
|
+
const cached = (0, modelList_1.getCachedModels)(this.context, providerId, baseUrl);
|
|
491
|
+
if (cached) {
|
|
492
|
+
return cached;
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
const models = await (0, modelList_1.fetchModelList)(providerId, baseUrl || definition.defaultBaseUrl, token);
|
|
496
|
+
await (0, modelList_1.cacheModels)(this.context, providerId, baseUrl || definition.defaultBaseUrl, models);
|
|
497
|
+
return models;
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
return [];
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async getOAuthAccessToken(providerId) {
|
|
504
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(providerId);
|
|
505
|
+
if (definition.authMethod !== "oauth" || !definition.oauthConfig) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
// GitHub Models: try stored token first, then refresh via VS Code session
|
|
509
|
+
if (providerId === "githubModels") {
|
|
510
|
+
const stored = await (0, oauthHandler_1.getValidAccessToken)(this.context, {
|
|
511
|
+
providerId,
|
|
512
|
+
authorizeUrl: "",
|
|
513
|
+
tokenUrl: "",
|
|
514
|
+
clientId: "",
|
|
515
|
+
scopes: [],
|
|
516
|
+
redirectUri: "",
|
|
517
|
+
});
|
|
518
|
+
if (stored) {
|
|
519
|
+
return stored;
|
|
520
|
+
}
|
|
521
|
+
const session = await (0, githubAuth_1.getSilentGitHubSession)();
|
|
522
|
+
if (session) {
|
|
523
|
+
await this.context.secrets.store("flowseeker.oauth.githubModels.accessToken", session.accessToken);
|
|
524
|
+
return session.accessToken;
|
|
525
|
+
}
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
const config = {
|
|
529
|
+
providerId,
|
|
530
|
+
authorizeUrl: definition.oauthConfig.authorizeUrl,
|
|
531
|
+
tokenUrl: definition.oauthConfig.tokenUrl,
|
|
532
|
+
clientId: definition.oauthConfig.clientId,
|
|
533
|
+
scopes: definition.oauthConfig.scopes,
|
|
534
|
+
redirectUri: "",
|
|
535
|
+
};
|
|
536
|
+
return (0, oauthHandler_1.getValidAccessToken)(this.context, config);
|
|
537
|
+
}
|
|
538
|
+
async recordAskUsage(promptText, responseText, responseHeaders) {
|
|
539
|
+
if (!this.quotaTracker)
|
|
540
|
+
return;
|
|
541
|
+
const provider = this.getActiveProvider();
|
|
542
|
+
if (!provider)
|
|
543
|
+
return;
|
|
544
|
+
const connection = await this.getConnection();
|
|
545
|
+
const model = connection.model || "";
|
|
546
|
+
if (!model)
|
|
547
|
+
return;
|
|
548
|
+
const inputTokens = extractTokens(responseHeaders, "prompt_tokens", promptText.length);
|
|
549
|
+
const outputTokens = extractTokens(responseHeaders, "completion_tokens", responseText.length);
|
|
550
|
+
const providerQuota = extractProviderQuota(provider, responseHeaders);
|
|
551
|
+
this.quotaTracker.record(provider, model, inputTokens, outputTokens, providerQuota);
|
|
552
|
+
}
|
|
553
|
+
recordProviderUsage(provider, model, promptText, responseText, responseHeaders) {
|
|
554
|
+
if (!this.quotaTracker)
|
|
555
|
+
return;
|
|
556
|
+
const inputTokens = extractTokens(responseHeaders, "prompt_tokens", promptText.length);
|
|
557
|
+
const outputTokens = extractTokens(responseHeaders, "completion_tokens", responseText.length);
|
|
558
|
+
const providerQuota = extractProviderQuota(provider, responseHeaders);
|
|
559
|
+
this.quotaTracker.record(provider, model, inputTokens, outputTokens, providerQuota);
|
|
560
|
+
}
|
|
561
|
+
recordGatewayUsage(provider, model, promptText, responseText, responseHeaders) {
|
|
562
|
+
if (!this.quotaTracker)
|
|
563
|
+
return;
|
|
564
|
+
const inputTokens = Math.ceil(promptText.length / 4);
|
|
565
|
+
const outputTokens = Math.ceil(responseText.length / 4);
|
|
566
|
+
const providerQuota = extractProviderQuota(provider, responseHeaders);
|
|
567
|
+
this.quotaTracker.record(provider, model, inputTokens, outputTokens, providerQuota);
|
|
568
|
+
}
|
|
569
|
+
getActiveProvider() {
|
|
570
|
+
const configured = this.context.globalState.get(activeProviderStateKey);
|
|
571
|
+
if ((0, aiProviders_1.isAiProviderId)(configured)) {
|
|
572
|
+
return configured;
|
|
573
|
+
}
|
|
574
|
+
const config = vscode.workspace.getConfiguration("flowseeker");
|
|
575
|
+
const useDirectProvider = config.get("ai.useDirectProvider", false);
|
|
576
|
+
const configProvider = config.get("ai.provider", "");
|
|
577
|
+
return useDirectProvider && (0, aiProviders_1.isAiProviderId)(configProvider) && configProvider !== "customGateway" ? configProvider : undefined;
|
|
578
|
+
}
|
|
579
|
+
async getProviderConnection(provider) {
|
|
580
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(provider);
|
|
581
|
+
const config = vscode.workspace.getConfiguration("flowseeker");
|
|
582
|
+
const baseUrl = this.context.globalState.get(baseUrlStateKey) || config.get("ai.baseUrl", "") || definition.defaultBaseUrl;
|
|
583
|
+
const model = this.context.globalState.get(modelStateKey) || config.get("ai.model", "") || definition.defaultModel;
|
|
584
|
+
const isOAuth = definition.authMethod === "oauth";
|
|
585
|
+
const oauthConnected = isOAuth ? await (0, oauthHandler_1.hasValidOAuthSession)(this.context, provider) : false;
|
|
586
|
+
const token = isOAuth ? undefined : await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(provider));
|
|
587
|
+
const connected = isOAuth
|
|
588
|
+
? oauthConnected
|
|
589
|
+
: provider === "customGateway"
|
|
590
|
+
? Boolean(baseUrl && token)
|
|
591
|
+
: Boolean(baseUrl && model && (!definition.requiresApiKey || token));
|
|
592
|
+
return {
|
|
593
|
+
endpoint: baseUrl,
|
|
594
|
+
baseUrl,
|
|
595
|
+
connected,
|
|
596
|
+
mode: provider === "customGateway" ? "customGateway" : "direct",
|
|
597
|
+
provider,
|
|
598
|
+
providerLabel: definition.label,
|
|
599
|
+
model,
|
|
600
|
+
requiresApiKey: isOAuth ? false : definition.requiresApiKey
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
async getProfiles() {
|
|
604
|
+
const stored = this.getStoredProfiles();
|
|
605
|
+
if (stored.length > 0) {
|
|
606
|
+
return stored;
|
|
607
|
+
}
|
|
608
|
+
const legacyProfile = await this.createLegacyProfileSnapshot();
|
|
609
|
+
if (!legacyProfile) {
|
|
610
|
+
return [];
|
|
611
|
+
}
|
|
612
|
+
await this.saveProfiles([legacyProfile]);
|
|
613
|
+
await this.context.globalState.update(activeProviderProfileStateKey, legacyProfile.id);
|
|
614
|
+
return [legacyProfile];
|
|
615
|
+
}
|
|
616
|
+
getStoredProfiles() {
|
|
617
|
+
const profiles = this.context.globalState.get(providerProfilesStateKey, []);
|
|
618
|
+
return profiles
|
|
619
|
+
.filter(isStoredProfile)
|
|
620
|
+
.slice(0, maxProviderProfiles);
|
|
621
|
+
}
|
|
622
|
+
async saveProfiles(profiles) {
|
|
623
|
+
const normalized = profiles
|
|
624
|
+
.filter(isStoredProfile)
|
|
625
|
+
.sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))
|
|
626
|
+
.slice(0, maxProviderProfiles);
|
|
627
|
+
await this.context.globalState.update(providerProfilesStateKey, normalized);
|
|
628
|
+
}
|
|
629
|
+
async upsertProfile(profile) {
|
|
630
|
+
const profiles = await this.getProfiles();
|
|
631
|
+
const next = [profile, ...profiles.filter((item) => item.id !== profile.id)];
|
|
632
|
+
await this.saveProfiles(next);
|
|
633
|
+
}
|
|
634
|
+
async getActiveProfile(profiles) {
|
|
635
|
+
const available = profiles ?? await this.getProfiles();
|
|
636
|
+
if (available.length === 0) {
|
|
637
|
+
return undefined;
|
|
638
|
+
}
|
|
639
|
+
const activeProfileId = this.context.globalState.get(activeProviderProfileStateKey);
|
|
640
|
+
return available.find((profile) => profile.id === activeProfileId) ?? available[0];
|
|
641
|
+
}
|
|
642
|
+
async activateProfile(profile) {
|
|
643
|
+
await this.context.globalState.update(activeProviderProfileStateKey, profile.id);
|
|
644
|
+
await this.context.globalState.update(activeProviderStateKey, profile.provider);
|
|
645
|
+
await this.context.globalState.update(modelStateKey, profile.model);
|
|
646
|
+
await this.context.globalState.update(baseUrlStateKey, profile.baseUrl);
|
|
647
|
+
await this.context.globalState.update(endpointStateKey, profile.provider === "customGateway" ? profile.baseUrl : undefined);
|
|
648
|
+
const token = await this.context.secrets.get(profileSecretKey(profile.id)) ||
|
|
649
|
+
await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(profile.provider));
|
|
650
|
+
if (token) {
|
|
651
|
+
await this.context.secrets.store((0, aiProviders_1.providerSecretKey)(profile.provider), token);
|
|
652
|
+
if (profile.provider === "customGateway") {
|
|
653
|
+
await this.context.secrets.store(tokenSecretKey, token);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async clearLegacyActiveState() {
|
|
658
|
+
await this.context.globalState.update(activeProviderStateKey, undefined);
|
|
659
|
+
await this.context.globalState.update(modelStateKey, undefined);
|
|
660
|
+
await this.context.globalState.update(baseUrlStateKey, undefined);
|
|
661
|
+
await this.context.globalState.update(endpointStateKey, undefined);
|
|
662
|
+
await this.context.secrets.delete(tokenSecretKey);
|
|
663
|
+
}
|
|
664
|
+
async hydrateProfile(profile) {
|
|
665
|
+
const connection = await this.getProfileConnection(profile);
|
|
666
|
+
return {
|
|
667
|
+
id: profile.id,
|
|
668
|
+
provider: profile.provider,
|
|
669
|
+
providerLabel: connection.providerLabel ?? (0, aiProviders_1.getProviderDefinition)(profile.provider).label,
|
|
670
|
+
model: profile.model,
|
|
671
|
+
baseUrl: profile.baseUrl,
|
|
672
|
+
requiresApiKey: connection.requiresApiKey ?? true,
|
|
673
|
+
connected: connection.connected,
|
|
674
|
+
updatedAt: profile.updatedAt
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
async getProfileConnection(profile) {
|
|
678
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(profile.provider);
|
|
679
|
+
const token = await this.context.secrets.get(profileSecretKey(profile.id)) ||
|
|
680
|
+
await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(profile.provider));
|
|
681
|
+
const oauthConnected = definition.authMethod === "oauth" ? await (0, oauthHandler_1.hasValidOAuthSession)(this.context, profile.provider) : false;
|
|
682
|
+
const connected = definition.authMethod === "oauth"
|
|
683
|
+
? Boolean(profile.baseUrl && profile.model && oauthConnected)
|
|
684
|
+
: Boolean(profile.baseUrl && profile.model && (!definition.requiresApiKey || token));
|
|
685
|
+
return {
|
|
686
|
+
profileId: profile.id,
|
|
687
|
+
endpoint: profile.baseUrl,
|
|
688
|
+
baseUrl: profile.baseUrl,
|
|
689
|
+
connected,
|
|
690
|
+
mode: profile.provider === "customGateway" ? "customGateway" : "direct",
|
|
691
|
+
provider: profile.provider,
|
|
692
|
+
providerLabel: definition.label,
|
|
693
|
+
model: profile.model,
|
|
694
|
+
requiresApiKey: definition.authMethod === "oauth" ? false : definition.requiresApiKey
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
async createLegacyProfileSnapshot() {
|
|
698
|
+
const provider = this.getActiveProvider();
|
|
699
|
+
const configuredEndpoint = this.context.globalState.get(endpointStateKey);
|
|
700
|
+
const legacyProvider = provider ?? (configuredEndpoint ? "customGateway" : undefined);
|
|
701
|
+
if (!legacyProvider) {
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
704
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(legacyProvider);
|
|
705
|
+
const baseUrl = this.context.globalState.get(baseUrlStateKey) || configuredEndpoint || definition.defaultBaseUrl;
|
|
706
|
+
const model = this.context.globalState.get(modelStateKey) || definition.defaultModel || "configured-by-gateway";
|
|
707
|
+
if (!baseUrl || !model) {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
id: createProviderProfileId(legacyProvider, model, baseUrl),
|
|
712
|
+
provider: legacyProvider,
|
|
713
|
+
model,
|
|
714
|
+
baseUrl,
|
|
715
|
+
updatedAt: new Date().toISOString()
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
isDirectProviderActive() {
|
|
719
|
+
const provider = this.getActiveProvider();
|
|
720
|
+
return Boolean(provider && provider !== "customGateway");
|
|
721
|
+
}
|
|
722
|
+
async askDirectProvider(prompt, jsonMode = false, activeProvider) {
|
|
723
|
+
const provider = activeProvider ?? this.getActiveProvider();
|
|
724
|
+
if (!provider || provider === "customGateway") {
|
|
725
|
+
throw new Error("No direct AI provider is connected.");
|
|
726
|
+
}
|
|
727
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(provider);
|
|
728
|
+
const connection = await this.getProviderConnection(provider);
|
|
729
|
+
if (!connection.connected || !connection.model || !connection.baseUrl) {
|
|
730
|
+
throw new Error(`${connection.providerLabel ?? "AI provider"} is not connected.`);
|
|
731
|
+
}
|
|
732
|
+
const apiKey = definition.authMethod === "oauth"
|
|
733
|
+
? await this.getOAuthAccessToken(provider) ?? undefined
|
|
734
|
+
: await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(provider));
|
|
735
|
+
const { text, responseHeaders } = await (0, aiProviders_1.invokeDirectProvider)({
|
|
736
|
+
provider,
|
|
737
|
+
model: connection.model,
|
|
738
|
+
baseUrl: connection.baseUrl,
|
|
739
|
+
apiKey,
|
|
740
|
+
prompt,
|
|
741
|
+
cwd: workspaceRootPath(),
|
|
742
|
+
jsonMode
|
|
743
|
+
});
|
|
744
|
+
return { text, responseHeaders };
|
|
745
|
+
}
|
|
746
|
+
async askDirectProviderStream(prompt, onToken, signal, activeProvider) {
|
|
747
|
+
const provider = activeProvider ?? this.getActiveProvider();
|
|
748
|
+
if (!provider || provider === "customGateway") {
|
|
749
|
+
throw new Error("No direct AI provider is connected.");
|
|
750
|
+
}
|
|
751
|
+
const definition = (0, aiProviders_1.getProviderDefinition)(provider);
|
|
752
|
+
const connection = await this.getProviderConnection(provider);
|
|
753
|
+
if (!connection.connected || !connection.model || !connection.baseUrl) {
|
|
754
|
+
throw new Error(`${connection.providerLabel ?? "AI provider"} is not connected.`);
|
|
755
|
+
}
|
|
756
|
+
const apiKey = definition.authMethod === "oauth"
|
|
757
|
+
? await this.getOAuthAccessToken(provider) ?? undefined
|
|
758
|
+
: await this.context.secrets.get((0, aiProviders_1.providerSecretKey)(provider));
|
|
759
|
+
return (0, aiProviders_1.invokeDirectProviderStream)({
|
|
760
|
+
provider,
|
|
761
|
+
model: connection.model,
|
|
762
|
+
baseUrl: connection.baseUrl,
|
|
763
|
+
apiKey,
|
|
764
|
+
prompt,
|
|
765
|
+
cwd: workspaceRootPath(),
|
|
766
|
+
onToken,
|
|
767
|
+
signal
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
async testCustomGateway() {
|
|
771
|
+
const endpoint = this.context.globalState.get(endpointStateKey) || this.context.globalState.get(baseUrlStateKey);
|
|
772
|
+
const token = await this.context.secrets.get(tokenSecretKey);
|
|
773
|
+
const model = this.context.globalState.get(modelStateKey) || "configured-by-gateway";
|
|
774
|
+
if (endpoint && token && this.shouldUseCustomOpenAICompatible(model)) {
|
|
775
|
+
await (0, aiProviders_1.invokeCustomOpenAICompatibleProvider)({
|
|
776
|
+
provider: "customGateway",
|
|
777
|
+
model,
|
|
778
|
+
baseUrl: endpoint,
|
|
779
|
+
apiKey: token,
|
|
780
|
+
prompt: "Reply with exactly: OK"
|
|
781
|
+
});
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
await this.testCustomGatewayInput(endpoint, token);
|
|
785
|
+
}
|
|
786
|
+
async testCustomGatewayInput(endpoint, token) {
|
|
787
|
+
if (!endpoint || !token) {
|
|
788
|
+
throw new Error("AI Gateway is not connected.");
|
|
789
|
+
}
|
|
790
|
+
const response = await fetch(endpoint, {
|
|
791
|
+
method: "POST",
|
|
792
|
+
headers: {
|
|
793
|
+
"content-type": "application/json",
|
|
794
|
+
authorization: `Bearer ${token}`
|
|
795
|
+
},
|
|
796
|
+
body: JSON.stringify({
|
|
797
|
+
product: "FlowSeeker",
|
|
798
|
+
mode: "guide",
|
|
799
|
+
instruction: "Reply with OK.",
|
|
800
|
+
task: "Connection test",
|
|
801
|
+
profile: {},
|
|
802
|
+
stats: {},
|
|
803
|
+
topFiles: [],
|
|
804
|
+
contextPack: "Connection test."
|
|
805
|
+
})
|
|
806
|
+
});
|
|
807
|
+
const text = await response.text();
|
|
808
|
+
if (!response.ok) {
|
|
809
|
+
throw new Error(`AI Gateway returned ${response.status}: ${text.slice(0, 500)}`);
|
|
810
|
+
}
|
|
811
|
+
if (looksLikeHtml(text)) {
|
|
812
|
+
throw new Error("AI Gateway returned an HTML page, not an agent response. Use the API base URL, usually ending in /v1, not the dashboard/home page URL. For AI Box/new-api, choose Custom OpenAI-Compatible and enter the model name.");
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
shouldUseCustomOpenAICompatible(model) {
|
|
816
|
+
const cleanModel = model?.trim();
|
|
817
|
+
return Boolean(cleanModel && cleanModel !== "configured-by-gateway");
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
exports.AiGatewayClient = AiGatewayClient;
|
|
821
|
+
function profileSecretKey(profileId) {
|
|
822
|
+
return `flowseeker.aiProviderProfile.${profileId}.apiKey`;
|
|
823
|
+
}
|
|
824
|
+
function createProviderProfileId(provider, model, baseUrl) {
|
|
825
|
+
return `${provider}-${hashString(`${provider}|${model.trim()}|${baseUrl.trim()}`)}`;
|
|
826
|
+
}
|
|
827
|
+
function hashString(value) {
|
|
828
|
+
let hash = 5381;
|
|
829
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
830
|
+
hash = ((hash << 5) + hash) ^ value.charCodeAt(index);
|
|
831
|
+
}
|
|
832
|
+
return (hash >>> 0).toString(36);
|
|
833
|
+
}
|
|
834
|
+
function isStoredProfile(value) {
|
|
835
|
+
return Boolean(value &&
|
|
836
|
+
typeof value.id === "string" &&
|
|
837
|
+
(0, aiProviders_1.isAiProviderId)(value.provider) &&
|
|
838
|
+
typeof value.model === "string" &&
|
|
839
|
+
typeof value.baseUrl === "string" &&
|
|
840
|
+
typeof value.updatedAt === "string");
|
|
841
|
+
}
|
|
842
|
+
function workspaceRootPath() {
|
|
843
|
+
return vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
844
|
+
}
|
|
845
|
+
function classifyProviderError(error, providerLabel) {
|
|
846
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
847
|
+
const statusMatch = message.match(/\b(4\d\d|5\d\d)\b/);
|
|
848
|
+
const status = statusMatch ? parseInt(statusMatch[1], 10) : 0;
|
|
849
|
+
if (status === 401 || status === 403) {
|
|
850
|
+
return [
|
|
851
|
+
`${providerLabel} rejected the connection: invalid or expired credentials (HTTP ${status}).`,
|
|
852
|
+
"",
|
|
853
|
+
"**Fix:**",
|
|
854
|
+
"- Re-enter your API key in the provider configuration.",
|
|
855
|
+
"- If using OAuth, sign in again to refresh the token.",
|
|
856
|
+
"- Verify the key is active in your provider dashboard."
|
|
857
|
+
].join("\n");
|
|
858
|
+
}
|
|
859
|
+
if (status === 429) {
|
|
860
|
+
if (/quota|billing/i.test(message)) {
|
|
861
|
+
return [
|
|
862
|
+
`${providerLabel} quota or billing is exhausted (HTTP 429).`,
|
|
863
|
+
"",
|
|
864
|
+
"**Fix:**",
|
|
865
|
+
"- Add billing or raise the spending limit in your provider account.",
|
|
866
|
+
"- Use a different API key or project.",
|
|
867
|
+
"- Try again after the quota reset period."
|
|
868
|
+
].join("\n");
|
|
869
|
+
}
|
|
870
|
+
return [
|
|
871
|
+
`${providerLabel} rate-limited the request (HTTP 429).`,
|
|
872
|
+
"",
|
|
873
|
+
"**Fix:**",
|
|
874
|
+
"- Wait before sending another request.",
|
|
875
|
+
"- Use a different model or API key.",
|
|
876
|
+
"- Reduce request frequency."
|
|
877
|
+
].join("\n");
|
|
878
|
+
}
|
|
879
|
+
if (status >= 500) {
|
|
880
|
+
return [
|
|
881
|
+
`${providerLabel} returned a server error (HTTP ${status}).`,
|
|
882
|
+
"",
|
|
883
|
+
"This is a provider-side issue. Check the provider status page or try again later.",
|
|
884
|
+
`Details: ${message}`
|
|
885
|
+
].join("\n");
|
|
886
|
+
}
|
|
887
|
+
if (/ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOENT/i.test(message)) {
|
|
888
|
+
return [
|
|
889
|
+
`Cannot reach ${providerLabel}.`,
|
|
890
|
+
"",
|
|
891
|
+
"**Fix:**",
|
|
892
|
+
"- Verify the base URL is correct.",
|
|
893
|
+
"- Check your network connection.",
|
|
894
|
+
"- If using a local provider (Ollama, local server), make sure it is running.",
|
|
895
|
+
`Details: ${message}`
|
|
896
|
+
].join("\n");
|
|
897
|
+
}
|
|
898
|
+
if (/not found|not installed|command not found/i.test(message)) {
|
|
899
|
+
return [
|
|
900
|
+
`${providerLabel} CLI is not available.`,
|
|
901
|
+
"",
|
|
902
|
+
"**Fix:**",
|
|
903
|
+
"- Install the CLI tool.",
|
|
904
|
+
"- Set the correct path in the Command field.",
|
|
905
|
+
"- Verify the CLI is in your system PATH.",
|
|
906
|
+
`Details: ${message}`
|
|
907
|
+
].join("\n");
|
|
908
|
+
}
|
|
909
|
+
return `${providerLabel} connection test failed: ${message}`;
|
|
910
|
+
}
|
|
911
|
+
function toGatewayPayload(result, mode, instructionOverride, extra = {}) {
|
|
912
|
+
const configuredLanguage = vscode.workspace.getConfiguration("flowseeker").get("ai.outputLanguage", "auto");
|
|
913
|
+
const languageInstruction = (0, responseLanguage_1.buildResponseLanguageInstruction)(configuredLanguage, result.task);
|
|
914
|
+
const taskInstruction = instructionOverride ??
|
|
915
|
+
(mode === "auto"
|
|
916
|
+
? (0, agentPrompts_1.createPlanInstruction)()
|
|
917
|
+
: "Use the retrieved context to guide the developer. Explain likely edit files, reasoning, risks, and focused verification steps.");
|
|
918
|
+
const topFiles = (0, fileGroups_1.aggregateEvidenceFiles)(result.units).slice(0, 20).map((group) => ({
|
|
919
|
+
path: group.relativePath,
|
|
920
|
+
role: group.role,
|
|
921
|
+
score: group.score,
|
|
922
|
+
evidenceCount: group.unitCount,
|
|
923
|
+
kinds: group.kinds,
|
|
924
|
+
ranges: group.ranges.slice(0, 5),
|
|
925
|
+
reasons: group.reasons.slice(0, 5)
|
|
926
|
+
}));
|
|
927
|
+
return {
|
|
928
|
+
product: "FlowSeeker",
|
|
929
|
+
mode,
|
|
930
|
+
instruction: `${languageInstruction}\n${taskInstruction}`,
|
|
931
|
+
task: result.task,
|
|
932
|
+
profile: result.profile,
|
|
933
|
+
stats: result.stats,
|
|
934
|
+
topFiles,
|
|
935
|
+
contextPack: result.contextPack,
|
|
936
|
+
...extra
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function parseAgentResponse(text) {
|
|
940
|
+
const jsonText = extractJsonText(text);
|
|
941
|
+
try {
|
|
942
|
+
const json = JSON.parse(jsonText);
|
|
943
|
+
const message = firstString(json.message, json.answer, json.content) ?? JSON.stringify(json, null, 2);
|
|
944
|
+
const patch = firstString(json.patch, json.diff);
|
|
945
|
+
const edits = parseEdits(json.edits ?? json);
|
|
946
|
+
const commands = parseCommands(json.commands);
|
|
947
|
+
const mcpTools = parseMcpTools(json.mcpTools);
|
|
948
|
+
return { message, patch, edits, commands, mcpTools, raw: json };
|
|
949
|
+
}
|
|
950
|
+
catch {
|
|
951
|
+
return { message: text };
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function extractJsonText(text) {
|
|
955
|
+
const trimmed = text.trim();
|
|
956
|
+
// 1. Try markdown code fence with optional json tag
|
|
957
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
958
|
+
if (fenced?.[1]) {
|
|
959
|
+
return fenced[1].trim();
|
|
960
|
+
}
|
|
961
|
+
// 2. Try to find a top-level JSON object anywhere in the text
|
|
962
|
+
const objectMatch = extractTopLevelJson(trimmed);
|
|
963
|
+
if (objectMatch) {
|
|
964
|
+
return objectMatch;
|
|
965
|
+
}
|
|
966
|
+
// 3. If the text looks like it starts with JSON, return as-is
|
|
967
|
+
if (/^\s*\{/.test(trimmed)) {
|
|
968
|
+
return trimmed;
|
|
969
|
+
}
|
|
970
|
+
// 4. Fallback: return raw text (parseAgentResponse will treat it as a message)
|
|
971
|
+
return trimmed;
|
|
972
|
+
}
|
|
973
|
+
function extractTopLevelJson(text) {
|
|
974
|
+
// Find the first { and try to extract the matching JSON object
|
|
975
|
+
const start = text.indexOf("{");
|
|
976
|
+
if (start === -1)
|
|
977
|
+
return undefined;
|
|
978
|
+
let depth = 0;
|
|
979
|
+
let inString = false;
|
|
980
|
+
let escaped = false;
|
|
981
|
+
for (let i = start; i < text.length; i++) {
|
|
982
|
+
const char = text[i];
|
|
983
|
+
if (escaped) {
|
|
984
|
+
escaped = false;
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (char === "\\" && inString) {
|
|
988
|
+
escaped = true;
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
if (char === '"' && !escaped) {
|
|
992
|
+
inString = !inString;
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (inString)
|
|
996
|
+
continue;
|
|
997
|
+
if (char === "{") {
|
|
998
|
+
depth++;
|
|
999
|
+
}
|
|
1000
|
+
else if (char === "}") {
|
|
1001
|
+
depth--;
|
|
1002
|
+
if (depth === 0) {
|
|
1003
|
+
const candidate = text.slice(start, i + 1);
|
|
1004
|
+
// Validate it's parseable JSON
|
|
1005
|
+
try {
|
|
1006
|
+
JSON.parse(candidate);
|
|
1007
|
+
return candidate;
|
|
1008
|
+
}
|
|
1009
|
+
catch {
|
|
1010
|
+
// Continue searching for another {
|
|
1011
|
+
const nextStart = text.indexOf("{", start + 1);
|
|
1012
|
+
if (nextStart === -1)
|
|
1013
|
+
return undefined;
|
|
1014
|
+
// Reset and try from next position (simplified: just return undefined)
|
|
1015
|
+
return undefined;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return undefined;
|
|
1021
|
+
}
|
|
1022
|
+
function firstString(...values) {
|
|
1023
|
+
return values.find((value) => typeof value === "string" && value.trim().length > 0);
|
|
1024
|
+
}
|
|
1025
|
+
function parseEdits(value) {
|
|
1026
|
+
const items = Array.isArray(value)
|
|
1027
|
+
? value
|
|
1028
|
+
: isObject(value) && (firstString(value.file, value.path) || Array.isArray(value.edits))
|
|
1029
|
+
? [value]
|
|
1030
|
+
: undefined;
|
|
1031
|
+
if (!items) {
|
|
1032
|
+
return undefined;
|
|
1033
|
+
}
|
|
1034
|
+
const edits = items.flatMap((item) => {
|
|
1035
|
+
if (!isObject(item)) {
|
|
1036
|
+
return [];
|
|
1037
|
+
}
|
|
1038
|
+
const file = firstString(item.file, item.path);
|
|
1039
|
+
const text = firstString(item.text, item.newText, item.replacement);
|
|
1040
|
+
if (!file || text === undefined) {
|
|
1041
|
+
return [];
|
|
1042
|
+
}
|
|
1043
|
+
const range = parseRange(item.range);
|
|
1044
|
+
const replaceWholeFile = item.replaceWholeFile === true || item.wholeFile === true;
|
|
1045
|
+
return [{ file, text, range, replaceWholeFile }];
|
|
1046
|
+
});
|
|
1047
|
+
return edits.length > 0 ? edits : undefined;
|
|
1048
|
+
}
|
|
1049
|
+
function parseRange(value) {
|
|
1050
|
+
if (!isObject(value)) {
|
|
1051
|
+
return undefined;
|
|
1052
|
+
}
|
|
1053
|
+
const startLine = numberValue(value.startLine);
|
|
1054
|
+
const endLine = numberValue(value.endLine);
|
|
1055
|
+
if (!startLine || !endLine) {
|
|
1056
|
+
return undefined;
|
|
1057
|
+
}
|
|
1058
|
+
return {
|
|
1059
|
+
startLine,
|
|
1060
|
+
startColumn: numberValue(value.startColumn) ?? numberValue(value.startCol),
|
|
1061
|
+
endLine,
|
|
1062
|
+
endColumn: numberValue(value.endColumn) ?? numberValue(value.endCol)
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
function parseCommands(value) {
|
|
1066
|
+
if (!Array.isArray(value)) {
|
|
1067
|
+
return undefined;
|
|
1068
|
+
}
|
|
1069
|
+
const commands = value.flatMap((item) => {
|
|
1070
|
+
if (!isObject(item)) {
|
|
1071
|
+
return [];
|
|
1072
|
+
}
|
|
1073
|
+
const command = firstString(item.command, item.cmd, item.script);
|
|
1074
|
+
if (!command) {
|
|
1075
|
+
return [];
|
|
1076
|
+
}
|
|
1077
|
+
return [{
|
|
1078
|
+
command,
|
|
1079
|
+
cwd: firstString(item.cwd, item.workingDirectory),
|
|
1080
|
+
rationale: firstString(item.rationale, item.reason, item.why)
|
|
1081
|
+
}];
|
|
1082
|
+
});
|
|
1083
|
+
return commands.length > 0 ? commands : undefined;
|
|
1084
|
+
}
|
|
1085
|
+
function parseMcpTools(value) {
|
|
1086
|
+
if (!Array.isArray(value)) {
|
|
1087
|
+
return undefined;
|
|
1088
|
+
}
|
|
1089
|
+
const tools = value.flatMap((item) => {
|
|
1090
|
+
if (!isObject(item)) {
|
|
1091
|
+
return [];
|
|
1092
|
+
}
|
|
1093
|
+
const tool = firstString(item.tool, item.name);
|
|
1094
|
+
if (!tool) {
|
|
1095
|
+
return [];
|
|
1096
|
+
}
|
|
1097
|
+
return [{
|
|
1098
|
+
tool,
|
|
1099
|
+
input: isObject(item.input) ? item.input : undefined,
|
|
1100
|
+
rationale: firstString(item.rationale, item.reason, item.why)
|
|
1101
|
+
}];
|
|
1102
|
+
});
|
|
1103
|
+
return tools.length > 0 ? tools : undefined;
|
|
1104
|
+
}
|
|
1105
|
+
function numberValue(value) {
|
|
1106
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 1 ? value : undefined;
|
|
1107
|
+
}
|
|
1108
|
+
function isObject(value) {
|
|
1109
|
+
return typeof value === "object" && value !== null;
|
|
1110
|
+
}
|
|
1111
|
+
function looksLikeHtml(text) {
|
|
1112
|
+
const trimmed = text.trim();
|
|
1113
|
+
return /^<!doctype html/i.test(trimmed) || /^<html[\s>]/i.test(trimmed);
|
|
1114
|
+
}
|
|
1115
|
+
function extractTokens(headers, key, fallbackLength) {
|
|
1116
|
+
// Try to get actual token count from response body (stored in a special header pattern)
|
|
1117
|
+
// OpenAI returns usage in response body, not headers. We estimate from response length as fallback.
|
|
1118
|
+
// Anthropic also returns usage in response body.
|
|
1119
|
+
// The headers only contain rate limit info, not token counts per request.
|
|
1120
|
+
return Math.ceil(fallbackLength / 4);
|
|
1121
|
+
}
|
|
1122
|
+
function extractProviderQuota(provider, headers) {
|
|
1123
|
+
const normalized = normalizeHeaders(headers);
|
|
1124
|
+
const tokenLimitKeys = provider === "anthropic" || provider === "customAnthropic"
|
|
1125
|
+
? [
|
|
1126
|
+
"anthropic-ratelimit-tokens-limit",
|
|
1127
|
+
"anthropic-ratelimit-input-tokens-limit",
|
|
1128
|
+
"anthropic-ratelimit-output-tokens-limit",
|
|
1129
|
+
"x-ratelimit-limit-tokens",
|
|
1130
|
+
"x-ratelimit-limit-tokens-1m"
|
|
1131
|
+
]
|
|
1132
|
+
: [
|
|
1133
|
+
"x-ratelimit-limit-tokens",
|
|
1134
|
+
"x-ratelimit-token-limit",
|
|
1135
|
+
"x-ratelimit-limit-tokens-1m",
|
|
1136
|
+
"x-ratelimit-limit-requests-tokens"
|
|
1137
|
+
];
|
|
1138
|
+
const tokenRemainingKeys = provider === "anthropic" || provider === "customAnthropic"
|
|
1139
|
+
? [
|
|
1140
|
+
"anthropic-ratelimit-tokens-remaining",
|
|
1141
|
+
"anthropic-ratelimit-input-tokens-remaining",
|
|
1142
|
+
"anthropic-ratelimit-output-tokens-remaining",
|
|
1143
|
+
"x-ratelimit-remaining-tokens",
|
|
1144
|
+
"x-ratelimit-remaining-tokens-1m"
|
|
1145
|
+
]
|
|
1146
|
+
: [
|
|
1147
|
+
"x-ratelimit-remaining-tokens",
|
|
1148
|
+
"x-ratelimit-token-remaining",
|
|
1149
|
+
"x-ratelimit-remaining-tokens-1m"
|
|
1150
|
+
];
|
|
1151
|
+
const requestLimitKeys = provider === "anthropic" || provider === "customAnthropic"
|
|
1152
|
+
? [
|
|
1153
|
+
"anthropic-ratelimit-requests-limit",
|
|
1154
|
+
"x-ratelimit-limit-requests",
|
|
1155
|
+
"x-ratelimit-limit"
|
|
1156
|
+
]
|
|
1157
|
+
: [
|
|
1158
|
+
"x-ratelimit-limit-requests",
|
|
1159
|
+
"x-ratelimit-request-limit",
|
|
1160
|
+
"x-ratelimit-limit",
|
|
1161
|
+
"x-ratelimit-limit-requests-1m"
|
|
1162
|
+
];
|
|
1163
|
+
const requestRemainingKeys = provider === "anthropic" || provider === "customAnthropic"
|
|
1164
|
+
? [
|
|
1165
|
+
"anthropic-ratelimit-requests-remaining",
|
|
1166
|
+
"x-ratelimit-remaining-requests",
|
|
1167
|
+
"x-ratelimit-remaining"
|
|
1168
|
+
]
|
|
1169
|
+
: [
|
|
1170
|
+
"x-ratelimit-remaining-requests",
|
|
1171
|
+
"x-ratelimit-request-remaining",
|
|
1172
|
+
"x-ratelimit-remaining",
|
|
1173
|
+
"x-ratelimit-remaining-requests-1m"
|
|
1174
|
+
];
|
|
1175
|
+
const tokensLimit = firstHeaderNumber(normalized, tokenLimitKeys);
|
|
1176
|
+
const tokensRemaining = firstHeaderNumber(normalized, tokenRemainingKeys);
|
|
1177
|
+
const requestsLimit = firstHeaderNumber(normalized, requestLimitKeys);
|
|
1178
|
+
const requestsRemaining = firstHeaderNumber(normalized, requestRemainingKeys);
|
|
1179
|
+
const resetSeconds = firstHeaderResetSeconds(normalized, [
|
|
1180
|
+
"x-ratelimit-reset-requests",
|
|
1181
|
+
"x-ratelimit-reset",
|
|
1182
|
+
"x-ratelimit-reset-requests-1m",
|
|
1183
|
+
"anthropic-ratelimit-requests-reset",
|
|
1184
|
+
"anthropic-ratelimit-tokens-reset"
|
|
1185
|
+
]);
|
|
1186
|
+
if (tokensLimit !== undefined ||
|
|
1187
|
+
tokensRemaining !== undefined ||
|
|
1188
|
+
requestsLimit !== undefined ||
|
|
1189
|
+
requestsRemaining !== undefined) {
|
|
1190
|
+
return { tokensLimit, tokensRemaining, requestsLimit, requestsRemaining, resetSeconds };
|
|
1191
|
+
}
|
|
1192
|
+
return undefined;
|
|
1193
|
+
}
|
|
1194
|
+
function normalizeHeaders(headers) {
|
|
1195
|
+
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
|
|
1196
|
+
}
|
|
1197
|
+
function firstHeaderNumber(headers, keys) {
|
|
1198
|
+
for (const key of keys) {
|
|
1199
|
+
const value = headerNumber(headers[key.toLowerCase()]);
|
|
1200
|
+
if (value !== undefined) {
|
|
1201
|
+
return value;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return undefined;
|
|
1205
|
+
}
|
|
1206
|
+
function headerNumber(value) {
|
|
1207
|
+
if (!value) {
|
|
1208
|
+
return undefined;
|
|
1209
|
+
}
|
|
1210
|
+
const first = value.split(",")[0].trim();
|
|
1211
|
+
const match = first.match(/-?\d+(?:\.\d+)?/);
|
|
1212
|
+
if (!match) {
|
|
1213
|
+
return undefined;
|
|
1214
|
+
}
|
|
1215
|
+
const n = Number(match[0]);
|
|
1216
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : undefined;
|
|
1217
|
+
}
|
|
1218
|
+
function firstHeaderResetSeconds(headers, keys) {
|
|
1219
|
+
for (const key of keys) {
|
|
1220
|
+
const seconds = parseResetSeconds(headers[key.toLowerCase()]);
|
|
1221
|
+
if (seconds !== undefined) {
|
|
1222
|
+
return seconds;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return undefined;
|
|
1226
|
+
}
|
|
1227
|
+
function parseResetSeconds(value) {
|
|
1228
|
+
if (!value) {
|
|
1229
|
+
return undefined;
|
|
1230
|
+
}
|
|
1231
|
+
const clean = value.split(",")[0].trim();
|
|
1232
|
+
if (!clean) {
|
|
1233
|
+
return undefined;
|
|
1234
|
+
}
|
|
1235
|
+
if (/^\d+(?:\.\d+)?$/.test(clean)) {
|
|
1236
|
+
const numeric = Number(clean);
|
|
1237
|
+
if (numeric > 1000000000) {
|
|
1238
|
+
return Math.max(0, Math.round(numeric - Date.now() / 1000));
|
|
1239
|
+
}
|
|
1240
|
+
return Math.max(0, Math.round(numeric));
|
|
1241
|
+
}
|
|
1242
|
+
const isoMs = Date.parse(clean);
|
|
1243
|
+
if (!Number.isNaN(isoMs)) {
|
|
1244
|
+
return Math.max(0, Math.round((isoMs - Date.now()) / 1000));
|
|
1245
|
+
}
|
|
1246
|
+
const duration = clean.match(/(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/i);
|
|
1247
|
+
if (duration && duration[0]) {
|
|
1248
|
+
const hours = Number(duration[1] ?? 0);
|
|
1249
|
+
const minutes = Number(duration[2] ?? 0);
|
|
1250
|
+
const seconds = Number(duration[3] ?? 0);
|
|
1251
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
1252
|
+
}
|
|
1253
|
+
return undefined;
|
|
1254
|
+
}
|
|
1255
|
+
//# sourceMappingURL=aiGateway.js.map
|