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.
Files changed (82) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/COMPATIBILITY.md +281 -0
  3. package/LICENSE +21 -0
  4. package/README.md +375 -0
  5. package/dist/adapters/frameworkEdges.js +586 -0
  6. package/dist/agent/approvalPolicy.js +81 -0
  7. package/dist/agent/commandRunner.js +166 -0
  8. package/dist/agent/flowCommandRunner.js +124 -0
  9. package/dist/agent/mcpToolRunner.js +167 -0
  10. package/dist/auth/githubAuth.js +71 -0
  11. package/dist/auth/modelList.js +127 -0
  12. package/dist/auth/oauthHandler.js +377 -0
  13. package/dist/chat/nativeChatParticipant.js +616 -0
  14. package/dist/cli/mcpServer.js +383 -0
  15. package/dist/cli/runEvaluation.js +789 -0
  16. package/dist/cli/runReplay.js +481 -0
  17. package/dist/commands/checkHostCompatibility.js +149 -0
  18. package/dist/commands/copyAgentPrompt.js +52 -0
  19. package/dist/commands/copyContext.js +54 -0
  20. package/dist/commands/explainSelectionRelevance.js +57 -0
  21. package/dist/commands/findRelevantContext.js +127 -0
  22. package/dist/commands/openEvidence.js +49 -0
  23. package/dist/commands/openMcpConfig.js +81 -0
  24. package/dist/commands/openRelatedTests.js +54 -0
  25. package/dist/commands/rebuildIndex.js +45 -0
  26. package/dist/commands/runEvaluationSuite.js +323 -0
  27. package/dist/commands/runReplaySuite.js +228 -0
  28. package/dist/config/defaultConfig.js +72 -0
  29. package/dist/config/loadConfig.js +84 -0
  30. package/dist/config/loadConfigFromPath.js +60 -0
  31. package/dist/extension.js +513 -0
  32. package/dist/gateway/agentPrompts.js +176 -0
  33. package/dist/gateway/aiGateway.js +1255 -0
  34. package/dist/gateway/aiProviders.js +901 -0
  35. package/dist/gateway/contextExpansion.js +331 -0
  36. package/dist/gateway/editProposalStore.js +238 -0
  37. package/dist/gateway/planProposalStore.js +28 -0
  38. package/dist/index/cacheStore.js +51 -0
  39. package/dist/index/chunker.js +45 -0
  40. package/dist/index/dependencyExtractor.js +107 -0
  41. package/dist/index/fileDiscovery.js +177 -0
  42. package/dist/index/structuredExtractor.js +256 -0
  43. package/dist/index/workspaceIndex.js +518 -0
  44. package/dist/mcp/mcpConfig.js +154 -0
  45. package/dist/mcp/mcpProvider.js +109 -0
  46. package/dist/mcp/mcpTools.js +215 -0
  47. package/dist/pipeline/agentPrompt.js +79 -0
  48. package/dist/pipeline/agentPromptHeadless.js +85 -0
  49. package/dist/pipeline/contextBlueprint.js +346 -0
  50. package/dist/pipeline/contextPack.js +80 -0
  51. package/dist/pipeline/diffPreview.js +79 -0
  52. package/dist/pipeline/evaluationMetrics.js +154 -0
  53. package/dist/pipeline/evidenceGraph.js +389 -0
  54. package/dist/pipeline/feedback.js +215 -0
  55. package/dist/pipeline/fileGroups.js +84 -0
  56. package/dist/pipeline/fileScanner.js +866 -0
  57. package/dist/pipeline/nodeScan.js +219 -0
  58. package/dist/pipeline/ranker.js +563 -0
  59. package/dist/pipeline/responseLanguage.js +39 -0
  60. package/dist/pipeline/retrievalPlan.js +163 -0
  61. package/dist/pipeline/runHeadless.js +54 -0
  62. package/dist/pipeline/runPipeline.js +114 -0
  63. package/dist/pipeline/solvePacket.js +382 -0
  64. package/dist/pipeline/subsystem.js +257 -0
  65. package/dist/pipeline/taskUnderstanding.js +453 -0
  66. package/dist/pipeline/tokenSavings.js +146 -0
  67. package/dist/pipeline/universalScan.js +216 -0
  68. package/dist/pipeline/verifyAfterApply.js +233 -0
  69. package/dist/runtime/capabilities.js +71 -0
  70. package/dist/runtime/hostDetect.js +98 -0
  71. package/dist/runtime/hostTier.js +68 -0
  72. package/dist/runtime/statusBar.js +80 -0
  73. package/dist/skills/skillRegistry.js +208 -0
  74. package/dist/types.js +3 -0
  75. package/dist/ui/chatViewProvider.js +3899 -0
  76. package/dist/ui/resultTreeProvider.js +174 -0
  77. package/dist/usage/quotaTracker.js +358 -0
  78. package/dist/utils/async.js +30 -0
  79. package/dist/utils/logger.js +64 -0
  80. package/dist/utils/text.js +364 -0
  81. package/dist/utils/updateChecker.js +140 -0
  82. package/package.json +561 -0
@@ -0,0 +1,901 @@
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.AI_PROVIDER_DEFINITIONS = void 0;
37
+ exports.getProviderDefinition = getProviderDefinition;
38
+ exports.isAiProviderId = isAiProviderId;
39
+ exports.providerSecretKey = providerSecretKey;
40
+ exports.invokeDirectProviderStream = invokeDirectProviderStream;
41
+ exports.invokeDirectProvider = invokeDirectProvider;
42
+ exports.invokeCustomOpenAICompatibleProvider = invokeCustomOpenAICompatibleProvider;
43
+ exports.invokeCustomOpenAICompatibleProviderStream = invokeCustomOpenAICompatibleProviderStream;
44
+ const child_process_1 = require("child_process");
45
+ const fs = __importStar(require("fs/promises"));
46
+ const os = __importStar(require("os"));
47
+ const path = __importStar(require("path"));
48
+ exports.AI_PROVIDER_DEFINITIONS = [
49
+ {
50
+ id: "openai",
51
+ label: "OpenAI",
52
+ protocol: "openai-responses",
53
+ authMethod: "api_key",
54
+ defaultBaseUrl: "https://api.openai.com/v1",
55
+ defaultModel: "gpt-5.2",
56
+ requiresApiKey: true,
57
+ apiKeyLabel: "OpenAI API key",
58
+ apiKeyHint: "sk-proj-...",
59
+ supportsModelList: true
60
+ },
61
+ {
62
+ id: "anthropic",
63
+ label: "Anthropic",
64
+ protocol: "anthropic-messages",
65
+ authMethod: "api_key",
66
+ defaultBaseUrl: "https://api.anthropic.com/v1",
67
+ defaultModel: "claude-sonnet-4-5",
68
+ requiresApiKey: true,
69
+ apiKeyLabel: "Anthropic API key",
70
+ apiKeyHint: "sk-ant-...",
71
+ supportsModelList: true
72
+ },
73
+ {
74
+ id: "gemini",
75
+ label: "Google Gemini",
76
+ protocol: "gemini-generate-content",
77
+ authMethod: "api_key",
78
+ defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
79
+ defaultModel: "gemini-3.5-flash",
80
+ requiresApiKey: true,
81
+ apiKeyLabel: "Gemini API key",
82
+ apiKeyHint: "AIza...",
83
+ supportsModelList: true
84
+ },
85
+ {
86
+ id: "ollama",
87
+ label: "Ollama",
88
+ protocol: "ollama-chat",
89
+ authMethod: "api_key",
90
+ defaultBaseUrl: "http://localhost:11434",
91
+ defaultModel: "llama3.2",
92
+ requiresApiKey: false,
93
+ apiKeyLabel: "No API key required",
94
+ supportsModelList: true
95
+ },
96
+ {
97
+ id: "openrouter",
98
+ label: "OpenRouter",
99
+ protocol: "openai-compatible-chat",
100
+ authMethod: "api_key",
101
+ defaultBaseUrl: "https://openrouter.ai/api/v1",
102
+ defaultModel: "openai/gpt-5.2",
103
+ requiresApiKey: true,
104
+ apiKeyLabel: "OpenRouter API key",
105
+ apiKeyHint: "sk-or-v1-...",
106
+ supportsModelList: true
107
+ },
108
+ {
109
+ id: "mistral",
110
+ label: "Mistral",
111
+ protocol: "openai-compatible-chat",
112
+ authMethod: "api_key",
113
+ defaultBaseUrl: "https://api.mistral.ai/v1",
114
+ defaultModel: "mistral-large-latest",
115
+ requiresApiKey: true,
116
+ apiKeyLabel: "Mistral API key",
117
+ apiKeyHint: "Mistral key",
118
+ supportsModelList: true
119
+ },
120
+ {
121
+ id: "groq",
122
+ label: "Groq",
123
+ protocol: "openai-compatible-chat",
124
+ authMethod: "api_key",
125
+ defaultBaseUrl: "https://api.groq.com/openai/v1",
126
+ defaultModel: "llama-3.3-70b-versatile",
127
+ requiresApiKey: true,
128
+ apiKeyLabel: "Groq API key",
129
+ apiKeyHint: "gsk_...",
130
+ supportsModelList: true
131
+ },
132
+ {
133
+ id: "xai",
134
+ label: "xAI",
135
+ protocol: "openai-compatible-chat",
136
+ authMethod: "api_key",
137
+ defaultBaseUrl: "https://api.x.ai/v1",
138
+ defaultModel: "grok-4",
139
+ requiresApiKey: true,
140
+ apiKeyLabel: "xAI API key",
141
+ apiKeyHint: "xai-...",
142
+ supportsModelList: true
143
+ },
144
+ {
145
+ id: "deepseek",
146
+ label: "DeepSeek",
147
+ protocol: "openai-compatible-chat",
148
+ authMethod: "api_key",
149
+ defaultBaseUrl: "https://api.deepseek.com",
150
+ defaultModel: "deepseek-chat",
151
+ requiresApiKey: true,
152
+ apiKeyLabel: "DeepSeek API key",
153
+ apiKeyHint: "sk-...",
154
+ supportsModelList: true
155
+ },
156
+ {
157
+ id: "codexCli",
158
+ label: "Codex CLI",
159
+ protocol: "local-agent-cli",
160
+ authMethod: "local_cli",
161
+ defaultBaseUrl: "codex",
162
+ defaultModel: "default",
163
+ requiresApiKey: false,
164
+ apiKeyLabel: "No API key required",
165
+ supportsModelList: false
166
+ },
167
+ {
168
+ id: "claudeCode",
169
+ label: "Claude Code",
170
+ protocol: "local-agent-cli",
171
+ authMethod: "local_cli",
172
+ defaultBaseUrl: "claude",
173
+ defaultModel: "default",
174
+ requiresApiKey: false,
175
+ apiKeyLabel: "No API key required",
176
+ supportsModelList: false
177
+ },
178
+ {
179
+ id: "customGateway",
180
+ label: "Custom OpenAI-Compatible",
181
+ protocol: "custom-gateway",
182
+ authMethod: "api_key",
183
+ defaultBaseUrl: "",
184
+ defaultModel: "",
185
+ requiresApiKey: true,
186
+ apiKeyLabel: "API key / token",
187
+ supportsModelList: false
188
+ },
189
+ {
190
+ id: "customAnthropic",
191
+ label: "Custom Anthropic-Compatible",
192
+ protocol: "anthropic-messages",
193
+ authMethod: "api_key",
194
+ defaultBaseUrl: "",
195
+ defaultModel: "claude-sonnet-4-5",
196
+ requiresApiKey: true,
197
+ apiKeyLabel: "API key / token",
198
+ supportsModelList: false
199
+ },
200
+ {
201
+ id: "githubModels",
202
+ label: "GitHub Models",
203
+ protocol: "openai-compatible-chat",
204
+ authMethod: "oauth",
205
+ defaultBaseUrl: "https://models.inference.ai.azure.com",
206
+ defaultModel: "gpt-4o",
207
+ requiresApiKey: false,
208
+ apiKeyLabel: "GitHub token",
209
+ supportsModelList: true,
210
+ oauthConfig: {
211
+ authorizeUrl: "",
212
+ tokenUrl: "",
213
+ modelsUrl: "https://models.inference.ai.azure.com/models",
214
+ clientId: "",
215
+ scopes: ["read:user"]
216
+ }
217
+ }
218
+ ];
219
+ function getProviderDefinition(provider) {
220
+ return exports.AI_PROVIDER_DEFINITIONS.find((definition) => definition.id === provider) ?? exports.AI_PROVIDER_DEFINITIONS[0];
221
+ }
222
+ function isAiProviderId(value) {
223
+ return typeof value === "string" && exports.AI_PROVIDER_DEFINITIONS.some((definition) => definition.id === value);
224
+ }
225
+ function providerSecretKey(provider) {
226
+ return provider === "customGateway" ? "flowseeker.aiGateway.token" : `flowseeker.aiProvider.${provider}.apiKey`;
227
+ }
228
+ async function invokeDirectProviderStream(request) {
229
+ const definition = getProviderDefinition(request.provider);
230
+ if (definition.protocol === "custom-gateway") {
231
+ throw new Error("Custom Gateway is handled by the gateway adapter.");
232
+ }
233
+ if (definition.requiresApiKey && !request.apiKey?.trim()) {
234
+ throw new Error(`${definition.label} API key is required.`);
235
+ }
236
+ if (!request.model.trim()) {
237
+ throw new Error(`${definition.label} model is required.`);
238
+ }
239
+ if (!request.baseUrl.trim()) {
240
+ throw new Error(`${definition.label} base URL is required.`);
241
+ }
242
+ switch (definition.protocol) {
243
+ case "openai-responses":
244
+ return invokeOpenAIResponsesStream(definition, request);
245
+ case "anthropic-messages":
246
+ return invokeAnthropicMessagesStream(definition, request);
247
+ case "ollama-chat":
248
+ return invokeOllamaChatStream(definition, request);
249
+ case "openai-compatible-chat":
250
+ return invokeOpenAICompatibleChatStream(definition, request);
251
+ case "local-agent-cli":
252
+ return invokeLocalAgentCliStream(definition, request);
253
+ case "gemini-generate-content": {
254
+ const response = await invokeGeminiGenerateContent(definition, request);
255
+ streamTextFallback(response.text, request.onToken);
256
+ return response;
257
+ }
258
+ }
259
+ }
260
+ async function invokeDirectProvider(request) {
261
+ const definition = getProviderDefinition(request.provider);
262
+ if (definition.protocol === "custom-gateway") {
263
+ throw new Error("Custom Gateway is handled by the gateway adapter.");
264
+ }
265
+ if (definition.requiresApiKey && !request.apiKey?.trim()) {
266
+ throw new Error(`${definition.label} API key is required.`);
267
+ }
268
+ if (!request.model.trim()) {
269
+ throw new Error(`${definition.label} model is required.`);
270
+ }
271
+ if (!request.baseUrl.trim()) {
272
+ throw new Error(`${definition.label} base URL is required.`);
273
+ }
274
+ switch (definition.protocol) {
275
+ case "openai-responses":
276
+ return invokeOpenAIResponses(definition, request);
277
+ case "anthropic-messages":
278
+ return invokeAnthropicMessages(definition, request);
279
+ case "gemini-generate-content":
280
+ return invokeGeminiGenerateContent(definition, request);
281
+ case "ollama-chat":
282
+ return invokeOllamaChat(definition, request);
283
+ case "openai-compatible-chat":
284
+ return invokeOpenAICompatibleChat(definition, request);
285
+ case "local-agent-cli":
286
+ return invokeLocalAgentCli(definition, request);
287
+ }
288
+ }
289
+ async function invokeCustomOpenAICompatibleProvider(request) {
290
+ if (!request.apiKey?.trim()) {
291
+ throw new Error("Custom OpenAI-compatible API key/token is required.");
292
+ }
293
+ if (!request.model.trim() || request.model === "configured-by-gateway") {
294
+ throw new Error("Custom OpenAI-compatible endpoint requires a model. Example: deepseek-v4-pro[1m].");
295
+ }
296
+ if (!request.baseUrl.trim()) {
297
+ throw new Error("Custom OpenAI-compatible base URL is required. It usually ends with /v1.");
298
+ }
299
+ const definition = {
300
+ id: "customGateway",
301
+ label: "Custom OpenAI-Compatible",
302
+ protocol: "openai-compatible-chat",
303
+ authMethod: "api_key",
304
+ defaultBaseUrl: "",
305
+ defaultModel: request.model,
306
+ requiresApiKey: true,
307
+ apiKeyLabel: "API key / token",
308
+ supportsModelList: false
309
+ };
310
+ const body = {
311
+ model: request.model,
312
+ stream: false,
313
+ messages: [{ role: "user", content: request.prompt }]
314
+ };
315
+ if (request.jsonMode) {
316
+ body.response_format = { type: "json_object" };
317
+ }
318
+ const { body: json, responseHeaders } = await postJson(definition, customChatCompletionsUrl(request.baseUrl), bearerHeaders(request.apiKey), body, request.apiKey);
319
+ return { text: textFromChatCompletion(json), responseHeaders };
320
+ }
321
+ async function invokeCustomOpenAICompatibleProviderStream(request) {
322
+ if (!request.apiKey?.trim()) {
323
+ throw new Error("Custom OpenAI-compatible API key/token is required.");
324
+ }
325
+ if (!request.model.trim() || request.model === "configured-by-gateway") {
326
+ throw new Error("Custom OpenAI-compatible endpoint requires a model. Example: deepseek-v4-pro[1m].");
327
+ }
328
+ if (!request.baseUrl.trim()) {
329
+ throw new Error("Custom OpenAI-compatible base URL is required. It usually ends with /v1.");
330
+ }
331
+ const definition = {
332
+ id: "customGateway",
333
+ label: "Custom OpenAI-Compatible",
334
+ protocol: "openai-compatible-chat",
335
+ authMethod: "api_key",
336
+ defaultBaseUrl: "",
337
+ defaultModel: request.model,
338
+ requiresApiKey: true,
339
+ apiKeyLabel: "API key / token",
340
+ supportsModelList: false
341
+ };
342
+ return streamOpenAIChatCompletion(definition, customChatCompletionsUrl(request.baseUrl), bearerHeaders(request.apiKey), {
343
+ model: request.model,
344
+ stream: true,
345
+ messages: [{ role: "user", content: request.prompt }]
346
+ }, request);
347
+ }
348
+ async function invokeOpenAIResponses(definition, request) {
349
+ const { body: json, responseHeaders } = await postJson(definition, joinUrl(request.baseUrl, "/responses"), bearerHeaders(request.apiKey), {
350
+ model: request.model,
351
+ input: request.prompt,
352
+ max_output_tokens: 4096
353
+ }, request.apiKey);
354
+ return { text: textFromOpenAIResponse(json), responseHeaders };
355
+ }
356
+ async function invokeAnthropicMessages(definition, request) {
357
+ const { body: json, responseHeaders } = await postJson(definition, joinUrl(request.baseUrl, "/messages"), {
358
+ "content-type": "application/json",
359
+ "x-api-key": request.apiKey ?? "",
360
+ "anthropic-version": "2023-06-01"
361
+ }, {
362
+ model: request.model,
363
+ max_tokens: 4096,
364
+ messages: [{ role: "user", content: request.prompt }]
365
+ }, request.apiKey);
366
+ return { text: textFromAnthropicMessage(json), responseHeaders };
367
+ }
368
+ async function invokeGeminiGenerateContent(definition, request) {
369
+ const cleanModel = request.model.replace(/^models\//, "");
370
+ const url = `${joinUrl(request.baseUrl, `/models/${encodeURIComponent(cleanModel)}:generateContent`)}?key=${encodeURIComponent(request.apiKey ?? "")}`;
371
+ const { body: json, responseHeaders } = await postJson(definition, url, { "content-type": "application/json" }, {
372
+ contents: [
373
+ {
374
+ role: "user",
375
+ parts: [{ text: request.prompt }]
376
+ }
377
+ ]
378
+ }, request.apiKey);
379
+ return { text: textFromGeminiResponse(json), responseHeaders };
380
+ }
381
+ async function invokeOllamaChat(definition, request) {
382
+ const { body: json, responseHeaders } = await postJson(definition, joinUrl(request.baseUrl, "/api/chat"), { "content-type": "application/json" }, {
383
+ model: request.model,
384
+ stream: false,
385
+ messages: [{ role: "user", content: request.prompt }]
386
+ }, request.apiKey);
387
+ return { text: textFromOllamaResponse(json), responseHeaders };
388
+ }
389
+ async function invokeOpenAICompatibleChat(definition, request) {
390
+ const body = {
391
+ model: request.model,
392
+ stream: false,
393
+ messages: [{ role: "user", content: request.prompt }]
394
+ };
395
+ if (request.jsonMode) {
396
+ body.response_format = { type: "json_object" };
397
+ }
398
+ const { body: json, responseHeaders } = await postJson(definition, joinUrl(request.baseUrl, "/chat/completions"), bearerHeaders(request.apiKey), body, request.apiKey);
399
+ return { text: textFromChatCompletion(json), responseHeaders };
400
+ }
401
+ async function invokeLocalAgentCli(definition, request) {
402
+ const command = request.baseUrl.trim();
403
+ if (!command) {
404
+ throw new Error(`${definition.label} command is required. Example: ${definition.id === "claudeCode" ? "claude" : "codex"}`);
405
+ }
406
+ const promptFile = await writePromptFile(request.prompt);
407
+ try {
408
+ const prompt = `Read the complete FlowSeeker solve packet from this file and answer using only the relevant context unless more repo inspection is necessary:\n${promptFile}`;
409
+ const args = localAgentArgs(definition, request.model, prompt);
410
+ const text = await runLocalCommand(definition, command, args, request.cwd);
411
+ return { text, responseHeaders: {} };
412
+ }
413
+ finally {
414
+ void fs.rm(path.dirname(promptFile), { recursive: true, force: true }).catch(() => undefined);
415
+ }
416
+ }
417
+ function localAgentArgs(definition, model, prompt) {
418
+ const cleanModel = model.trim();
419
+ const hasModel = Boolean(cleanModel && cleanModel !== "default");
420
+ if (definition.id === "claudeCode") {
421
+ const args = ["-p"];
422
+ if (hasModel) {
423
+ args.push("--model", cleanModel);
424
+ }
425
+ args.push(prompt);
426
+ return args;
427
+ }
428
+ const args = ["exec"];
429
+ if (hasModel) {
430
+ args.push("--model", cleanModel);
431
+ }
432
+ args.push(prompt);
433
+ return args;
434
+ }
435
+ async function writePromptFile(prompt) {
436
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "flowseeker-agent-"));
437
+ const file = path.join(dir, "solve-packet.md");
438
+ await fs.writeFile(file, prompt, "utf8");
439
+ return file;
440
+ }
441
+ function runLocalCommand(definition, command, args, cwd) {
442
+ return new Promise((resolve, reject) => {
443
+ const child = (0, child_process_1.spawn)(command, args, {
444
+ cwd,
445
+ shell: false,
446
+ windowsHide: true,
447
+ env: process.env
448
+ });
449
+ let stdout = "";
450
+ let stderr = "";
451
+ const timeout = setTimeout(() => {
452
+ child.kill();
453
+ reject(new Error(`${definition.label} timed out after 10 minutes.`));
454
+ }, 600000);
455
+ child.stdout.on("data", (chunk) => {
456
+ stdout += chunk.toString("utf8");
457
+ if (stdout.length > 2000000) {
458
+ stdout = stdout.slice(-2000000);
459
+ }
460
+ });
461
+ child.stderr.on("data", (chunk) => {
462
+ stderr += chunk.toString("utf8");
463
+ if (stderr.length > 500000) {
464
+ stderr = stderr.slice(-500000);
465
+ }
466
+ });
467
+ child.on("error", (error) => {
468
+ clearTimeout(timeout);
469
+ reject(new Error(`${definition.label} command failed to start: ${error.message}. Set the CLI command/path in the Command field and make sure you are logged in.`));
470
+ });
471
+ child.on("close", (code) => {
472
+ clearTimeout(timeout);
473
+ const cleanStdout = stdout.trim();
474
+ const cleanStderr = stderr.trim();
475
+ if (code !== 0) {
476
+ reject(new Error(`${definition.label} exited with code ${code ?? "unknown"}: ${errorBodySummary(cleanStderr || cleanStdout)}`));
477
+ return;
478
+ }
479
+ resolve(cleanStdout || cleanStderr || `${definition.label} returned an empty response.`);
480
+ });
481
+ });
482
+ }
483
+ async function invokeOpenAIResponsesStream(definition, request) {
484
+ return streamSseText(definition, joinUrl(request.baseUrl, "/responses"), bearerHeaders(request.apiKey), {
485
+ model: request.model,
486
+ input: request.prompt,
487
+ max_output_tokens: 4096,
488
+ stream: true
489
+ }, request, (json) => {
490
+ if (!isObject(json))
491
+ return "";
492
+ if (json.type === "response.output_text.delta") {
493
+ return firstString(json.delta) ?? "";
494
+ }
495
+ if (json.type === "response.completed" && isObject(json.response)) {
496
+ return "";
497
+ }
498
+ return "";
499
+ });
500
+ }
501
+ async function invokeAnthropicMessagesStream(definition, request) {
502
+ return streamSseText(definition, joinUrl(request.baseUrl, "/messages"), {
503
+ "content-type": "application/json",
504
+ "x-api-key": request.apiKey ?? "",
505
+ "anthropic-version": "2023-06-01"
506
+ }, {
507
+ model: request.model,
508
+ max_tokens: 4096,
509
+ stream: true,
510
+ messages: [{ role: "user", content: request.prompt }]
511
+ }, request, (json) => {
512
+ if (!isObject(json))
513
+ return "";
514
+ if (json.type === "content_block_delta" && isObject(json.delta)) {
515
+ return firstString(json.delta.text) ?? "";
516
+ }
517
+ return "";
518
+ });
519
+ }
520
+ async function invokeOpenAICompatibleChatStream(definition, request) {
521
+ const body = {
522
+ model: request.model,
523
+ stream: true,
524
+ messages: [{ role: "user", content: request.prompt }]
525
+ };
526
+ if (request.jsonMode) {
527
+ body.response_format = { type: "json_object" };
528
+ }
529
+ return streamOpenAIChatCompletion(definition, joinUrl(request.baseUrl, "/chat/completions"), bearerHeaders(request.apiKey), body, request);
530
+ }
531
+ async function invokeOllamaChatStream(definition, request) {
532
+ const response = await fetch(joinUrl(request.baseUrl, "/api/chat"), {
533
+ method: "POST",
534
+ headers: { "content-type": "application/json" },
535
+ body: JSON.stringify({
536
+ model: request.model,
537
+ stream: true,
538
+ messages: [{ role: "user", content: request.prompt }]
539
+ }),
540
+ signal: request.signal
541
+ });
542
+ const responseHeaders = collectHeaders(response);
543
+ if (!response.ok) {
544
+ throw new Error(`${definition.label} returned ${response.status}: ${sanitizeProviderError(errorBodySummary(await response.text()), request.apiKey)}`);
545
+ }
546
+ let fullText = "";
547
+ await readResponseLines(response, (line) => {
548
+ if (!line.trim())
549
+ return;
550
+ try {
551
+ const json = JSON.parse(line);
552
+ const chunk = isObject(json) && isObject(json.message) ? firstString(json.message.content) : undefined;
553
+ if (chunk) {
554
+ fullText += chunk;
555
+ request.onToken(chunk);
556
+ }
557
+ }
558
+ catch {
559
+ // Ignore malformed partial lines.
560
+ }
561
+ });
562
+ return { text: fullText.trim() || "(empty response)", responseHeaders };
563
+ }
564
+ async function invokeLocalAgentCliStream(definition, request) {
565
+ const command = request.baseUrl.trim();
566
+ if (!command) {
567
+ throw new Error(`${definition.label} command is required. Example: ${definition.id === "claudeCode" ? "claude" : "codex"}`);
568
+ }
569
+ const promptFile = await writePromptFile(request.prompt);
570
+ try {
571
+ const prompt = `Read the complete FlowSeeker solve packet from this file and answer using only the relevant context unless more repo inspection is necessary:\n${promptFile}`;
572
+ const args = localAgentArgs(definition, request.model, prompt);
573
+ const text = await runLocalCommandStream(definition, command, args, request.cwd, request.onToken, request.signal);
574
+ return { text, responseHeaders: {} };
575
+ }
576
+ finally {
577
+ void fs.rm(path.dirname(promptFile), { recursive: true, force: true }).catch(() => undefined);
578
+ }
579
+ }
580
+ async function streamOpenAIChatCompletion(definition, url, headers, body, request) {
581
+ return streamSseText(definition, url, headers, body, request, (json) => {
582
+ if (!isObject(json))
583
+ return "";
584
+ const choices = Array.isArray(json.choices) ? json.choices : [];
585
+ for (const choice of choices) {
586
+ if (isObject(choice) && isObject(choice.delta)) {
587
+ const chunk = firstString(choice.delta.content);
588
+ if (chunk)
589
+ return chunk;
590
+ }
591
+ }
592
+ return "";
593
+ });
594
+ }
595
+ async function streamSseText(definition, url, headers, body, request, extract) {
596
+ const response = await fetch(url, {
597
+ method: "POST",
598
+ headers,
599
+ body: JSON.stringify(body),
600
+ signal: request.signal
601
+ });
602
+ const responseHeaders = collectHeaders(response);
603
+ if (!response.ok) {
604
+ throw new Error(`${definition.label} returned ${response.status}: ${sanitizeProviderError(errorBodySummary(await response.text()), request.apiKey)}`);
605
+ }
606
+ let fullText = "";
607
+ await readResponseLines(response, (line) => {
608
+ const trimmed = line.trim();
609
+ if (!trimmed.startsWith("data:"))
610
+ return;
611
+ const data = trimmed.slice(5).trim();
612
+ if (!data || data === "[DONE]")
613
+ return;
614
+ try {
615
+ const json = JSON.parse(data);
616
+ const chunk = extract(json);
617
+ if (chunk) {
618
+ fullText += chunk;
619
+ request.onToken(chunk);
620
+ }
621
+ }
622
+ catch {
623
+ // Ignore malformed stream events.
624
+ }
625
+ });
626
+ return { text: fullText.trim() || "(empty response)", responseHeaders };
627
+ }
628
+ async function readResponseLines(response, onLine) {
629
+ const reader = response.body?.getReader();
630
+ if (!reader)
631
+ return;
632
+ const decoder = new TextDecoder();
633
+ let buffer = "";
634
+ while (true) {
635
+ const { value, done } = await reader.read();
636
+ if (done)
637
+ break;
638
+ buffer += decoder.decode(value, { stream: true });
639
+ let index = buffer.indexOf("\n");
640
+ while (index >= 0) {
641
+ const line = buffer.slice(0, index);
642
+ buffer = buffer.slice(index + 1);
643
+ onLine(line);
644
+ index = buffer.indexOf("\n");
645
+ }
646
+ }
647
+ buffer += decoder.decode();
648
+ if (buffer.trim())
649
+ onLine(buffer);
650
+ }
651
+ function streamTextFallback(text, onToken) {
652
+ const chunks = text.match(/\S+\s*/g) ?? [];
653
+ for (const chunk of chunks) {
654
+ onToken(chunk);
655
+ }
656
+ }
657
+ function collectHeaders(response) {
658
+ const responseHeaders = {};
659
+ response.headers.forEach((value, key) => { responseHeaders[key.toLowerCase()] = value; });
660
+ return responseHeaders;
661
+ }
662
+ function runLocalCommandStream(definition, command, args, cwd, onToken, signal) {
663
+ return new Promise((resolve, reject) => {
664
+ const child = (0, child_process_1.spawn)(command, args, {
665
+ cwd,
666
+ shell: false,
667
+ windowsHide: true,
668
+ env: process.env
669
+ });
670
+ let stdout = "";
671
+ let stderr = "";
672
+ const timeout = setTimeout(() => {
673
+ child.kill();
674
+ reject(new Error(`${definition.label} timed out after 10 minutes.`));
675
+ }, 600000);
676
+ const abort = () => {
677
+ child.kill();
678
+ reject(new Error("cancelled"));
679
+ };
680
+ signal?.addEventListener("abort", abort, { once: true });
681
+ child.stdout.on("data", (chunk) => {
682
+ const text = chunk.toString("utf8");
683
+ stdout += text;
684
+ onToken(text);
685
+ if (stdout.length > 2000000)
686
+ stdout = stdout.slice(-2000000);
687
+ });
688
+ child.stderr.on("data", (chunk) => {
689
+ stderr += chunk.toString("utf8");
690
+ if (stderr.length > 500000)
691
+ stderr = stderr.slice(-500000);
692
+ });
693
+ child.on("error", (error) => {
694
+ clearTimeout(timeout);
695
+ signal?.removeEventListener("abort", abort);
696
+ reject(new Error(`${definition.label} command failed to start: ${error.message}. Set the CLI command/path in the Command field and make sure you are logged in.`));
697
+ });
698
+ child.on("close", (code) => {
699
+ clearTimeout(timeout);
700
+ signal?.removeEventListener("abort", abort);
701
+ const cleanStdout = stdout.trim();
702
+ const cleanStderr = stderr.trim();
703
+ if (signal?.aborted) {
704
+ reject(new Error("cancelled"));
705
+ return;
706
+ }
707
+ if (code !== 0) {
708
+ reject(new Error(`${definition.label} exited with code ${code ?? "unknown"}: ${errorBodySummary(cleanStderr || cleanStdout)}`));
709
+ return;
710
+ }
711
+ resolve(cleanStdout || cleanStderr || `${definition.label} returned an empty response.`);
712
+ });
713
+ });
714
+ }
715
+ async function postJson(definition, url, headers, body, apiKey) {
716
+ let response;
717
+ try {
718
+ response = await fetch(url, {
719
+ method: "POST",
720
+ headers,
721
+ body: JSON.stringify(body)
722
+ });
723
+ }
724
+ catch (error) {
725
+ const message = error instanceof Error ? error.message : String(error);
726
+ throw new Error(`${definition.label} request failed: ${sanitizeProviderError(message, apiKey)}`);
727
+ }
728
+ const responseHeaders = {};
729
+ response.headers.forEach((value, key) => { responseHeaders[key.toLowerCase()] = value; });
730
+ const text = await response.text();
731
+ if (!response.ok) {
732
+ throw new Error(`${definition.label} returned ${response.status}: ${sanitizeProviderError(errorBodySummary(text), apiKey)}`);
733
+ }
734
+ try {
735
+ return { body: JSON.parse(text), responseHeaders };
736
+ }
737
+ catch {
738
+ throw new Error(`${definition.label} returned a non-JSON response. ${nonJsonResponseHint(text)}`);
739
+ }
740
+ }
741
+ function bearerHeaders(apiKey) {
742
+ return {
743
+ "content-type": "application/json",
744
+ authorization: `Bearer ${apiKey ?? ""}`
745
+ };
746
+ }
747
+ function joinUrl(baseUrl, path) {
748
+ const cleanBase = baseUrl.trim().replace(/\/+$/, "");
749
+ const cleanPath = path.replace(/^\/+/, "");
750
+ return `${cleanBase}/${cleanPath}`;
751
+ }
752
+ function customChatCompletionsUrl(baseUrl) {
753
+ const cleanBase = baseUrl.trim().replace(/\/+$/, "");
754
+ if (/\/chat\/completions$/i.test(cleanBase)) {
755
+ return cleanBase;
756
+ }
757
+ if (/\/(?:v\d+|api\/v\d+|openai\/v\d+)$/i.test(cleanBase)) {
758
+ return joinUrl(cleanBase, "/chat/completions");
759
+ }
760
+ return joinUrl(cleanBase, "/v1/chat/completions");
761
+ }
762
+ function nonJsonResponseHint(text) {
763
+ const trimmed = text.trim();
764
+ if (/^<!doctype html/i.test(trimmed) || /^<html[\s>]/i.test(trimmed)) {
765
+ return "The endpoint returned an HTML page, not an API response. Use the API base URL, usually ending in /v1, not the dashboard/home page URL.";
766
+ }
767
+ return `First response bytes: ${trimmed.slice(0, 160) || "empty response body"}`;
768
+ }
769
+ function textFromOpenAIResponse(value) {
770
+ if (!isObject(value)) {
771
+ return "";
772
+ }
773
+ const outputText = firstString(value.output_text, value.text);
774
+ if (outputText) {
775
+ return outputText;
776
+ }
777
+ const output = Array.isArray(value.output) ? value.output : [];
778
+ const parts = [];
779
+ for (const item of output) {
780
+ if (!isObject(item)) {
781
+ continue;
782
+ }
783
+ const content = Array.isArray(item.content) ? item.content : [];
784
+ for (const part of content) {
785
+ if (isObject(part)) {
786
+ const text = firstString(part.text, part.output_text, part.content);
787
+ if (text) {
788
+ parts.push(text);
789
+ }
790
+ }
791
+ }
792
+ }
793
+ return parts.join("\n").trim() || JSON.stringify(value, null, 2);
794
+ }
795
+ function textFromAnthropicMessage(value) {
796
+ if (!isObject(value)) {
797
+ return "";
798
+ }
799
+ const content = Array.isArray(value.content) ? value.content : [];
800
+ const parts = content
801
+ .map((part) => (isObject(part) ? firstString(part.text, part.content) : undefined))
802
+ .filter((part) => Boolean(part));
803
+ return parts.join("\n").trim() || JSON.stringify(value, null, 2);
804
+ }
805
+ function textFromGeminiResponse(value) {
806
+ if (!isObject(value)) {
807
+ return "";
808
+ }
809
+ const candidates = Array.isArray(value.candidates) ? value.candidates : [];
810
+ const parts = [];
811
+ for (const candidate of candidates) {
812
+ if (!isObject(candidate) || !isObject(candidate.content)) {
813
+ continue;
814
+ }
815
+ const candidateParts = Array.isArray(candidate.content.parts) ? candidate.content.parts : [];
816
+ for (const part of candidateParts) {
817
+ if (isObject(part)) {
818
+ const text = firstString(part.text);
819
+ if (text) {
820
+ parts.push(text);
821
+ }
822
+ }
823
+ }
824
+ }
825
+ return parts.join("\n").trim() || JSON.stringify(value, null, 2);
826
+ }
827
+ function textFromOllamaResponse(value) {
828
+ if (!isObject(value)) {
829
+ return "";
830
+ }
831
+ if (isObject(value.message)) {
832
+ const content = firstString(value.message.content);
833
+ if (content) {
834
+ return content;
835
+ }
836
+ }
837
+ return firstString(value.response, value.text) ?? JSON.stringify(value, null, 2);
838
+ }
839
+ function textFromChatCompletion(value) {
840
+ if (!isObject(value)) {
841
+ return "";
842
+ }
843
+ const choices = Array.isArray(value.choices) ? value.choices : [];
844
+ for (const choice of choices) {
845
+ if (!isObject(choice) || !isObject(choice.message)) {
846
+ continue;
847
+ }
848
+ const content = choice.message.content;
849
+ if (typeof content === "string" && content.trim()) {
850
+ return content;
851
+ }
852
+ if (Array.isArray(content)) {
853
+ const parts = content
854
+ .map((part) => (isObject(part) ? firstString(part.text, part.content) : undefined))
855
+ .filter((part) => Boolean(part));
856
+ if (parts.length > 0) {
857
+ return parts.join("\n").trim();
858
+ }
859
+ }
860
+ }
861
+ return JSON.stringify(value, null, 2);
862
+ }
863
+ function errorBodySummary(text) {
864
+ const trimmed = text.trim();
865
+ if (!trimmed) {
866
+ return "empty response body";
867
+ }
868
+ try {
869
+ const json = JSON.parse(trimmed);
870
+ if (isObject(json)) {
871
+ const direct = firstString(json.message, json.error, json.detail);
872
+ if (direct) {
873
+ return direct;
874
+ }
875
+ if (isObject(json.error)) {
876
+ const nested = firstString(json.error.message, json.error.type, json.error.code);
877
+ if (nested) {
878
+ return nested;
879
+ }
880
+ }
881
+ }
882
+ }
883
+ catch {
884
+ // Fall back to raw text below.
885
+ }
886
+ return trimmed.slice(0, 500);
887
+ }
888
+ function sanitizeProviderError(value, apiKey) {
889
+ if (!apiKey) {
890
+ return value;
891
+ }
892
+ const escaped = apiKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
893
+ return value.replace(new RegExp(escaped, "g"), "[redacted]");
894
+ }
895
+ function firstString(...values) {
896
+ return values.find((value) => typeof value === "string" && value.trim().length > 0);
897
+ }
898
+ function isObject(value) {
899
+ return typeof value === "object" && value !== null;
900
+ }
901
+ //# sourceMappingURL=aiProviders.js.map