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,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
|