openfox 0.2.3 → 1.0.0
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/dist/agent-defaults/builder.agent.md +3 -7
- package/dist/agent-defaults/code-reviewer.agent.md +1 -2
- package/dist/agent-defaults/debugger.agent.md +1 -2
- package/dist/agent-defaults/planner.agent.md +2 -9
- package/dist/agent-defaults/test-generator.agent.md +1 -1
- package/dist/agent-defaults/verifier.agent.md +4 -5
- package/dist/ask-3RK5YJZE.js +19 -0
- package/dist/auto-compaction-7LNZ5J3W.js +23 -0
- package/dist/{branch.api-WDHKZJS6.js → branch.api-XADFZZAA.js} +2 -2
- package/dist/chat-handler-T7SLWZ4E.js +201 -0
- package/dist/chunk-22CTURMH.js +128 -0
- package/dist/chunk-2KSCZSXQ.js +680 -0
- package/dist/{chunk-YMUIRZUI.js → chunk-2TBNG7CS.js} +12 -12
- package/dist/{chunk-4Z2OZ6NX.js → chunk-3EHGGBWE.js} +29 -2
- package/dist/{chunk-L475ND2Z.js → chunk-3UQACISJ.js} +162 -52
- package/dist/{chunk-AF7XS4OI.js → chunk-5YQAXUKO.js} +4 -1
- package/dist/{chunk-MTX5Y3V3.js → chunk-7IOZFJBW.js} +2 -2
- package/dist/chunk-BU2ZYOEA.js +1067 -0
- package/dist/chunk-M2C5GEQR.js +137 -0
- package/dist/{chunk-U62ZECSQ.js → chunk-PNBH3RAX.js} +2 -2
- package/dist/chunk-QDEKU5RL.js +75 -0
- package/dist/chunk-QYP5HQV4.js +438 -0
- package/dist/{chunk-3WOUSUW7.js → chunk-RAZQTQK2.js} +1353 -1134
- package/dist/chunk-S4QKTRGJ.js +343 -0
- package/dist/{chunk-ARFK3RZW.js → chunk-TPT6HP4H.js} +1 -1
- package/dist/chunk-TVQOONDR.js +75 -0
- package/dist/{chunk-5ZUU23XF.js → chunk-U6BUT37A.js} +286 -33
- package/dist/chunk-VZXXJKSQ.js +314 -0
- package/dist/{chunk-26NRL3HX.js → chunk-XFXOSPYH.js} +3 -2
- package/dist/{chunk-N2XYX4ZA.js → chunk-XKFPU2FA.js} +13 -2
- package/dist/{chunk-FUUYYDVG.js → chunk-YB2574C6.js} +2841 -4507
- package/dist/cli/dev.js +4 -3
- package/dist/cli/index.js +4 -3
- package/dist/{config-BMXSDOZB.js → config-GIFCEJDU.js} +11 -6
- package/dist/events-G6CKHDNZ.js +114 -0
- package/dist/folding-O52L65VN.js +37 -0
- package/dist/{init-PLRTRSDC.js → init-MKTUIGZU.js} +46 -13
- package/dist/orchestrator-UGXX7JRQ.js +39 -0
- package/dist/package.json +9 -2
- package/dist/{paths-4BPXWB2W.js → paths-MM76AWYB.js} +2 -2
- package/dist/processor-KLHKDILM.js +236 -0
- package/dist/{project-creator-CWPKRIKI.js → project-creator-KAQXMZCT.js} +9 -8
- package/dist/projects-GSMMEVSO.js +19 -0
- package/dist/{protocol-BFFz3WM0.d.ts → protocol-BZ6OIASP.d.ts} +71 -50
- package/dist/protocol-WQENDK72.js +88 -0
- package/dist/{provider-ETVY64A2.js → provider-4KTWU6IV.js} +41 -9
- package/dist/runtime-config-2XJJIMSC.js +9 -0
- package/dist/{serve-TL34LE77.js → serve-UK6B6G2O.js} +38 -18
- package/dist/server/index.d.ts +81 -27
- package/dist/server/index.js +19 -11
- package/dist/{settings-D5O3TMLD.js → settings-NCLN4JDZ.js} +4 -4
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +1 -1
- package/dist/tools-SGBQE736.js +48 -0
- package/dist/vision-fallback-63VPBSEN.js +22 -0
- package/dist/web/assets/{index-DHmZmhnR.css → index-BsaaC0X0.css} +32 -1
- package/dist/web/assets/index-CvO7QkGh.js +141 -0
- package/dist/web/index.html +2 -2
- package/dist/web/sounds/typing.mp3 +0 -0
- package/dist/workflow-defaults/default.workflow.json +1 -1
- package/package.json +9 -2
- package/dist/chunk-L4XDA3V7.js +0 -218
- package/dist/tools-QMIIDJTV.js +0 -48
- package/dist/web/assets/index-CAD7Iv_d.js +0 -122
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-PNBH3RAX.js";
|
|
4
|
+
|
|
5
|
+
// src/server/llm/vision-fallback.ts
|
|
6
|
+
var DEFAULT_CONFIG = {
|
|
7
|
+
enabled: false,
|
|
8
|
+
url: "http://localhost:11434",
|
|
9
|
+
model: "qwen3-vl:2b",
|
|
10
|
+
timeout: 12e4
|
|
11
|
+
};
|
|
12
|
+
var config = { ...DEFAULT_CONFIG };
|
|
13
|
+
var configLoaded = false;
|
|
14
|
+
var descriptionCache = /* @__PURE__ */ new Map();
|
|
15
|
+
function clearDescriptionCache() {
|
|
16
|
+
descriptionCache.clear();
|
|
17
|
+
}
|
|
18
|
+
function setVisionFallbackConfig(newConfig) {
|
|
19
|
+
config = { ...config, ...newConfig };
|
|
20
|
+
configLoaded = true;
|
|
21
|
+
logger.debug("Vision fallback config updated", { config });
|
|
22
|
+
}
|
|
23
|
+
function getVisionFallbackConfig() {
|
|
24
|
+
return { ...config };
|
|
25
|
+
}
|
|
26
|
+
function isVisionFallbackEnabled() {
|
|
27
|
+
return config.enabled;
|
|
28
|
+
}
|
|
29
|
+
async function ensureVisionFallbackConfigLoaded() {
|
|
30
|
+
if (configLoaded) return;
|
|
31
|
+
try {
|
|
32
|
+
const { loadGlobalConfig, getVisionFallback } = await import("./config-GIFCEJDU.js");
|
|
33
|
+
const { getRuntimeConfig } = await import("./runtime-config-2XJJIMSC.js");
|
|
34
|
+
const runtimeConfig = getRuntimeConfig();
|
|
35
|
+
const mode = runtimeConfig.mode ?? "production";
|
|
36
|
+
const globalConfig = await loadGlobalConfig(mode);
|
|
37
|
+
const fallback = getVisionFallback(globalConfig);
|
|
38
|
+
config = {
|
|
39
|
+
enabled: fallback.enabled ?? false,
|
|
40
|
+
url: fallback.url ?? "http://localhost:11434",
|
|
41
|
+
model: fallback.model ?? "qwen3-vl:2b",
|
|
42
|
+
timeout: (fallback.timeout ?? 120) * 1e3
|
|
43
|
+
};
|
|
44
|
+
configLoaded = true;
|
|
45
|
+
logger.debug("Vision fallback config loaded from global config", { config });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.warn("Failed to load vision fallback config from global config", { error: error instanceof Error ? error.message : String(error) });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
var IMAGE_PROMPT = `Describe this image in detail. Focus on:
|
|
51
|
+
- What the image shows (UI, diagram, photo, etc.)
|
|
52
|
+
- Any text visible in the image
|
|
53
|
+
- Layout and visual structure
|
|
54
|
+
- Key elements and their relationships
|
|
55
|
+
|
|
56
|
+
Provide a concise but comprehensive description.`;
|
|
57
|
+
async function describeImage(base64Data, options) {
|
|
58
|
+
await ensureVisionFallbackConfigLoaded();
|
|
59
|
+
if (!config.enabled) {
|
|
60
|
+
return "[Image - vision fallback not enabled]";
|
|
61
|
+
}
|
|
62
|
+
const cacheKey = `${base64Data.slice(0, 100)}:${options?.context ?? ""}`;
|
|
63
|
+
const cached = descriptionCache.get(cacheKey);
|
|
64
|
+
if (cached) {
|
|
65
|
+
logger.debug("Using cached image description");
|
|
66
|
+
return cached;
|
|
67
|
+
}
|
|
68
|
+
const timeout = options?.timeout ?? config.timeout;
|
|
69
|
+
try {
|
|
70
|
+
const url = `${config.url}/api/chat`;
|
|
71
|
+
const requestBody = {
|
|
72
|
+
model: config.model,
|
|
73
|
+
messages: [
|
|
74
|
+
{
|
|
75
|
+
role: "user",
|
|
76
|
+
content: options?.context ? `${IMAGE_PROMPT}
|
|
77
|
+
|
|
78
|
+
Context: ${options.context}` : IMAGE_PROMPT,
|
|
79
|
+
images: [base64Data]
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
stream: false,
|
|
83
|
+
think: false
|
|
84
|
+
};
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
87
|
+
const response = await fetch(url, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json"
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(requestBody),
|
|
93
|
+
signal: controller.signal
|
|
94
|
+
});
|
|
95
|
+
clearTimeout(timeoutId);
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
98
|
+
logger.error("Vision fallback API error", { status: response.status, error: errorText });
|
|
99
|
+
return `[Image description failed: HTTP ${response.status}]`;
|
|
100
|
+
}
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
const description = data.message?.content?.trim();
|
|
103
|
+
if (!description) {
|
|
104
|
+
logger.warn("Vision fallback returned empty description");
|
|
105
|
+
return "[Image - could not describe]";
|
|
106
|
+
}
|
|
107
|
+
descriptionCache.set(cacheKey, description);
|
|
108
|
+
logger.debug("Cached image description", { cacheKey: cacheKey.slice(0, 20) });
|
|
109
|
+
return description;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
112
|
+
logger.error("Vision fallback error", { error: message });
|
|
113
|
+
if (message.includes("abort")) {
|
|
114
|
+
return "[Image description timed out]";
|
|
115
|
+
}
|
|
116
|
+
return `[Image description failed: ${message}]`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function describeImageFromDataUrl(dataUrl, options) {
|
|
120
|
+
const base64Match = dataUrl.match(/^data:image\/[^;]+;base64,(.+)$/);
|
|
121
|
+
if (!base64Match || !base64Match[1]) {
|
|
122
|
+
return "[Invalid image data URL]";
|
|
123
|
+
}
|
|
124
|
+
return describeImage(base64Match[1], options);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
descriptionCache,
|
|
129
|
+
clearDescriptionCache,
|
|
130
|
+
setVisionFallbackConfig,
|
|
131
|
+
getVisionFallbackConfig,
|
|
132
|
+
isVisionFallbackEnabled,
|
|
133
|
+
ensureVisionFallbackConfigLoaded,
|
|
134
|
+
describeImage,
|
|
135
|
+
describeImageFromDataUrl
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=chunk-M2C5GEQR.js.map
|
|
@@ -8,7 +8,7 @@ var LEVELS = {
|
|
|
8
8
|
var currentLevel = "info";
|
|
9
9
|
function setLogLevel(level, mode) {
|
|
10
10
|
if (mode && !level) {
|
|
11
|
-
currentLevel = mode === "development" ? "debug" : "warn";
|
|
11
|
+
currentLevel = mode === "development" ? "debug" : mode === "test" ? "error" : "warn";
|
|
12
12
|
} else {
|
|
13
13
|
currentLevel = level ?? "info";
|
|
14
14
|
}
|
|
@@ -51,4 +51,4 @@ export {
|
|
|
51
51
|
setLogLevel,
|
|
52
52
|
logger
|
|
53
53
|
};
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
54
|
+
//# sourceMappingURL=chunk-PNBH3RAX.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-PNBH3RAX.js";
|
|
4
|
+
|
|
5
|
+
// src/server/session/name-generator.ts
|
|
6
|
+
var SESSION_NAME_PROMPT = `Generate a concise, descriptive session name (max 50 characters) based on the user's message.
|
|
7
|
+
Return ONLY the name, nothing else.
|
|
8
|
+
|
|
9
|
+
Example inputs and outputs:
|
|
10
|
+
- "How do I set up React?" \u2192 "React setup"
|
|
11
|
+
- "Fix the authentication bug" \u2192 "Fix authentication bug"
|
|
12
|
+
- "Add unit tests for the API" \u2192 "Add API unit tests"
|
|
13
|
+
|
|
14
|
+
User message: {message}`;
|
|
15
|
+
async function generateSessionName(options) {
|
|
16
|
+
const { userMessage, llmClient, signal } = options;
|
|
17
|
+
try {
|
|
18
|
+
logger.debug("Generating session name", { messagePreview: userMessage.slice(0, 50) });
|
|
19
|
+
const prompt = SESSION_NAME_PROMPT.replace("{message}", userMessage);
|
|
20
|
+
const messages = [
|
|
21
|
+
{
|
|
22
|
+
role: "user",
|
|
23
|
+
content: prompt
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
const timeoutSignal = AbortSignal.timeout(6e4);
|
|
27
|
+
const composedSignal = signal ? AbortSignal.any([timeoutSignal, signal]) : timeoutSignal;
|
|
28
|
+
const response = await llmClient.complete({
|
|
29
|
+
messages,
|
|
30
|
+
tools: [],
|
|
31
|
+
signal: composedSignal,
|
|
32
|
+
disableThinking: true
|
|
33
|
+
});
|
|
34
|
+
let name = response.content.trim();
|
|
35
|
+
if (name.length > 50) {
|
|
36
|
+
name = name.substring(0, 47) + "...";
|
|
37
|
+
}
|
|
38
|
+
if (!name || name.length < 3) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: "Generated name is too short or empty"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
logger.debug("Session name generated successfully", { name });
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
name
|
|
48
|
+
};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error generating session name";
|
|
51
|
+
logger.debug("Session name generation error", { error: errorMessage });
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: errorMessage
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function needsNameGeneration(sessionTitle, messageCount) {
|
|
59
|
+
if (messageCount > 1) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (!sessionTitle || sessionTitle.trim() === "") {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (/^Session \d+$/.test(sessionTitle)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
generateSessionName,
|
|
73
|
+
needsNameGeneration
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=chunk-QDEKU5RL.js.map
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearModelCache,
|
|
3
|
+
createLLMClient,
|
|
4
|
+
detectBackend,
|
|
5
|
+
detectModel
|
|
6
|
+
} from "./chunk-U6BUT37A.js";
|
|
7
|
+
import {
|
|
8
|
+
logger
|
|
9
|
+
} from "./chunk-PNBH3RAX.js";
|
|
10
|
+
|
|
11
|
+
// src/server/provider-manager.ts
|
|
12
|
+
async function fetchAvailableModelsFromBackend(baseUrl, apiKey) {
|
|
13
|
+
const url = baseUrl.includes("/v1") ? `${baseUrl}/models` : `${baseUrl}/v1/models`;
|
|
14
|
+
try {
|
|
15
|
+
const headers = {
|
|
16
|
+
"Content-Type": "application/json"
|
|
17
|
+
};
|
|
18
|
+
if (apiKey) {
|
|
19
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
20
|
+
}
|
|
21
|
+
const response = await fetch(url, {
|
|
22
|
+
method: "GET",
|
|
23
|
+
headers,
|
|
24
|
+
signal: AbortSignal.timeout(1e4)
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
logger.debug("Failed to fetch models", { url, status: response.status });
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
if (data.data && Array.isArray(data.data)) {
|
|
32
|
+
return data.data.map((m) => m.id).filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
return [];
|
|
35
|
+
} catch (error) {
|
|
36
|
+
logger.debug("Error fetching models from backend", { url, error: error instanceof Error ? error.message : String(error) });
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function fetchModelsWithContext(baseUrl, apiKey, backend) {
|
|
41
|
+
if (backend === "ollama") {
|
|
42
|
+
return fetchOllamaModelsWithContext(baseUrl, apiKey);
|
|
43
|
+
}
|
|
44
|
+
const url = baseUrl.includes("/v1") ? `${baseUrl}/models` : `${baseUrl}/v1/models`;
|
|
45
|
+
try {
|
|
46
|
+
const headers = {
|
|
47
|
+
"Content-Type": "application/json"
|
|
48
|
+
};
|
|
49
|
+
if (apiKey) {
|
|
50
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
51
|
+
}
|
|
52
|
+
const response = await fetch(url, {
|
|
53
|
+
method: "GET",
|
|
54
|
+
headers,
|
|
55
|
+
signal: AbortSignal.timeout(1e4)
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
logger.debug("Failed to fetch models with context", { url, status: response.status });
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const data = await response.json();
|
|
62
|
+
if (data.data && Array.isArray(data.data)) {
|
|
63
|
+
return data.data.map((m) => ({
|
|
64
|
+
id: m.id,
|
|
65
|
+
contextWindow: m.max_model_len ?? 2e5,
|
|
66
|
+
source: m.max_model_len ? "backend" : "default"
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.debug("Error fetching models with context", { url, error: error instanceof Error ? error.message : String(error) });
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function fetchOllamaModelsWithContext(baseUrl, apiKey) {
|
|
76
|
+
const tagsUrl = `${baseUrl}/api/tags`;
|
|
77
|
+
try {
|
|
78
|
+
const tagsResponse = await fetch(tagsUrl, {
|
|
79
|
+
signal: AbortSignal.timeout(1e4)
|
|
80
|
+
});
|
|
81
|
+
if (!tagsResponse.ok) {
|
|
82
|
+
logger.debug("Failed to fetch Ollama tags", { url: tagsUrl });
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const tagsData = await tagsResponse.json();
|
|
86
|
+
if (!tagsData.models || !Array.isArray(tagsData.models)) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const modelsWithContext = [];
|
|
90
|
+
for (const model of tagsData.models) {
|
|
91
|
+
try {
|
|
92
|
+
const showUrl = `${baseUrl}/api/show`;
|
|
93
|
+
const showResponse = await fetch(showUrl, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ name: model.name, verbose: true }),
|
|
97
|
+
signal: AbortSignal.timeout(5e3)
|
|
98
|
+
});
|
|
99
|
+
if (showResponse.ok) {
|
|
100
|
+
const showData = await showResponse.json();
|
|
101
|
+
const contextLength = showData.model_info?.llama?.context_length ?? showData.model_info?.context_length ?? 2e5;
|
|
102
|
+
modelsWithContext.push({
|
|
103
|
+
id: model.name,
|
|
104
|
+
contextWindow: contextLength,
|
|
105
|
+
source: contextLength !== 2e5 ? "backend" : "default"
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
modelsWithContext.push({
|
|
109
|
+
id: model.name,
|
|
110
|
+
contextWindow: 2e5,
|
|
111
|
+
source: "default"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.debug("Failed to fetch Ollama model context", {
|
|
116
|
+
model: model.name,
|
|
117
|
+
error: error instanceof Error ? error.message : String(error)
|
|
118
|
+
});
|
|
119
|
+
modelsWithContext.push({
|
|
120
|
+
id: model.name,
|
|
121
|
+
contextWindow: 2e5,
|
|
122
|
+
source: "default"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return modelsWithContext;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
logger.debug("Error fetching Ollama models", { url: baseUrl, error: error instanceof Error ? error.message : String(error) });
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function parseDefaultModelSelection(selection) {
|
|
133
|
+
if (!selection) return { providerId: void 0, model: void 0 };
|
|
134
|
+
const slashIndex = selection.indexOf("/");
|
|
135
|
+
if (slashIndex === -1) return { providerId: selection, model: "auto" };
|
|
136
|
+
return {
|
|
137
|
+
providerId: selection.substring(0, slashIndex),
|
|
138
|
+
model: selection.substring(slashIndex + 1)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function createProviderManager(config) {
|
|
142
|
+
let providers = [...config.providers ?? []];
|
|
143
|
+
const { providerId: initialProviderId, model: initialModel } = parseDefaultModelSelection(config.defaultModelSelection);
|
|
144
|
+
let defaultModelSelection = config.defaultModelSelection;
|
|
145
|
+
let llmClient = createLLMClient(config);
|
|
146
|
+
const providerStatus = /* @__PURE__ */ new Map();
|
|
147
|
+
logger.debug("ProviderManager created", { providers: providers.map((p) => ({ id: p.id, models: p.models.map((m) => ({ id: m.id, contextWindow: m.contextWindow, source: m.source })) })) });
|
|
148
|
+
for (const p of providers) {
|
|
149
|
+
providerStatus.set(p.id, "unknown");
|
|
150
|
+
}
|
|
151
|
+
function createConfigForProvider(provider, model) {
|
|
152
|
+
return {
|
|
153
|
+
...config,
|
|
154
|
+
llm: {
|
|
155
|
+
...config.llm,
|
|
156
|
+
baseUrl: provider.url.includes("/v1") ? provider.url : `${provider.url}/v1`,
|
|
157
|
+
model,
|
|
158
|
+
backend: provider.backend
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
getProviders() {
|
|
164
|
+
return [...providers];
|
|
165
|
+
},
|
|
166
|
+
getActiveProvider() {
|
|
167
|
+
const { providerId } = parseDefaultModelSelection(defaultModelSelection);
|
|
168
|
+
if (!providerId) return void 0;
|
|
169
|
+
return providers.find((p) => p.id === providerId);
|
|
170
|
+
},
|
|
171
|
+
getActiveProviderId() {
|
|
172
|
+
const { providerId } = parseDefaultModelSelection(defaultModelSelection);
|
|
173
|
+
return providerId;
|
|
174
|
+
},
|
|
175
|
+
getCurrentModel() {
|
|
176
|
+
const { model } = parseDefaultModelSelection(defaultModelSelection);
|
|
177
|
+
return model;
|
|
178
|
+
},
|
|
179
|
+
getLLMClient() {
|
|
180
|
+
return llmClient;
|
|
181
|
+
},
|
|
182
|
+
async activateProvider(providerId, options) {
|
|
183
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
184
|
+
if (!provider) {
|
|
185
|
+
return { success: false, error: "Provider not found" };
|
|
186
|
+
}
|
|
187
|
+
const currentModel = parseDefaultModelSelection(defaultModelSelection).model ?? "auto";
|
|
188
|
+
const targetModel = options?.model ?? currentModel;
|
|
189
|
+
const isModelSwitch = providerId === parseDefaultModelSelection(defaultModelSelection).providerId && options?.model && options.model !== currentModel;
|
|
190
|
+
if (providerId === parseDefaultModelSelection(defaultModelSelection).providerId && !isModelSwitch) {
|
|
191
|
+
return { success: true };
|
|
192
|
+
}
|
|
193
|
+
logger.info("Switching provider", {
|
|
194
|
+
from: parseDefaultModelSelection(defaultModelSelection).providerId,
|
|
195
|
+
to: providerId,
|
|
196
|
+
providerName: provider.name,
|
|
197
|
+
url: provider.url,
|
|
198
|
+
model: targetModel
|
|
199
|
+
});
|
|
200
|
+
const providerConfig = createConfigForProvider(provider, targetModel);
|
|
201
|
+
const newClient = createLLMClient(providerConfig);
|
|
202
|
+
try {
|
|
203
|
+
const url = provider.url.includes("/v1") ? provider.url.replace("/v1", "") : provider.url;
|
|
204
|
+
clearModelCache(url);
|
|
205
|
+
const backend = provider.backend;
|
|
206
|
+
const modelsWithContext = await fetchModelsWithContext(url, provider.apiKey, backend);
|
|
207
|
+
const normalize = (s) => s.toLowerCase().replace(/[-_\s:.]+/g, "");
|
|
208
|
+
const userModels = provider.models.filter((m) => m.source === "user");
|
|
209
|
+
logger.debug("activateProvider", { providerId, backendModelsCount: modelsWithContext.length, userModelsCount: userModels.length, userModels: userModels.map((m) => ({ id: m.id, contextWindow: m.contextWindow })) });
|
|
210
|
+
if (modelsWithContext.length > 0) {
|
|
211
|
+
const updatedModels = modelsWithContext.map((m) => {
|
|
212
|
+
let existingModel = provider.models.find((pm) => pm.id === m.id);
|
|
213
|
+
if (!existingModel) {
|
|
214
|
+
const normalizedId = normalize(m.id);
|
|
215
|
+
existingModel = provider.models.find((pm) => {
|
|
216
|
+
if (pm.source !== "user") return false;
|
|
217
|
+
return normalize(pm.id) === normalizedId;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (existingModel && existingModel.source === "user") {
|
|
221
|
+
return { ...existingModel, id: m.id };
|
|
222
|
+
}
|
|
223
|
+
return m;
|
|
224
|
+
});
|
|
225
|
+
for (const userModel of userModels) {
|
|
226
|
+
const normalizedUserId = normalize(userModel.id);
|
|
227
|
+
const matchedInUpdated = updatedModels.some((m) => {
|
|
228
|
+
const normalizedBackendId = normalize(m.id);
|
|
229
|
+
return normalizedBackendId === normalizedUserId;
|
|
230
|
+
});
|
|
231
|
+
if (!matchedInUpdated) {
|
|
232
|
+
updatedModels.push(userModel);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
providers = providers.map((p) => p.id === providerId ? { ...p, models: updatedModels } : p);
|
|
236
|
+
} else if (userModels.length > 0) {
|
|
237
|
+
logger.debug("Backend unavailable during provider switch, preserving user models", { providerId, userModelsCount: userModels.length });
|
|
238
|
+
providers = providers.map((p) => p.id === providerId ? { ...p, models: userModels } : p);
|
|
239
|
+
}
|
|
240
|
+
if (provider.backend === "auto") {
|
|
241
|
+
const detected = await detectBackend(url);
|
|
242
|
+
newClient.setBackend(detected);
|
|
243
|
+
} else {
|
|
244
|
+
newClient.setBackend(provider.backend);
|
|
245
|
+
}
|
|
246
|
+
if (targetModel === "auto") {
|
|
247
|
+
const detected = await detectModel(url);
|
|
248
|
+
if (detected) {
|
|
249
|
+
newClient.setModel(detected);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
newClient.setModel(targetModel);
|
|
253
|
+
}
|
|
254
|
+
providerStatus.set(providerId, "connected");
|
|
255
|
+
} catch (err) {
|
|
256
|
+
logger.warn("Could not connect to provider", {
|
|
257
|
+
providerId,
|
|
258
|
+
error: err instanceof Error ? err.message : String(err)
|
|
259
|
+
});
|
|
260
|
+
providerStatus.set(providerId, "disconnected");
|
|
261
|
+
}
|
|
262
|
+
providers = providers.map((p) => ({
|
|
263
|
+
...p,
|
|
264
|
+
isActive: p.id === providerId
|
|
265
|
+
}));
|
|
266
|
+
defaultModelSelection = `${providerId}/${targetModel}`;
|
|
267
|
+
llmClient = newClient;
|
|
268
|
+
logger.info("Provider activated", {
|
|
269
|
+
providerId,
|
|
270
|
+
providerName: provider.name,
|
|
271
|
+
model: llmClient.getModel(),
|
|
272
|
+
backend: llmClient.getBackend()
|
|
273
|
+
});
|
|
274
|
+
return { success: true };
|
|
275
|
+
},
|
|
276
|
+
addProvider(providerData) {
|
|
277
|
+
const id = crypto.randomUUID();
|
|
278
|
+
const provider = {
|
|
279
|
+
...providerData,
|
|
280
|
+
id,
|
|
281
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
282
|
+
};
|
|
283
|
+
if (providerData.isActive || providers.length === 0) {
|
|
284
|
+
providers = providers.map((p) => ({ ...p, isActive: false }));
|
|
285
|
+
provider.isActive = true;
|
|
286
|
+
defaultModelSelection = `${id}/auto`;
|
|
287
|
+
}
|
|
288
|
+
providers.push(provider);
|
|
289
|
+
providerStatus.set(id, "unknown");
|
|
290
|
+
return provider;
|
|
291
|
+
},
|
|
292
|
+
removeProvider(providerId) {
|
|
293
|
+
const index = providers.findIndex((p) => p.id === providerId);
|
|
294
|
+
if (index === -1) return false;
|
|
295
|
+
const wasActive = providers[index]?.isActive;
|
|
296
|
+
providers.splice(index, 1);
|
|
297
|
+
providerStatus.delete(providerId);
|
|
298
|
+
if (wasActive && providers.length > 0) {
|
|
299
|
+
providers[0].isActive = true;
|
|
300
|
+
defaultModelSelection = `${providers[0].id}/auto`;
|
|
301
|
+
} else if (providers.length === 0) {
|
|
302
|
+
defaultModelSelection = void 0;
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
},
|
|
306
|
+
setProviders(newProviders, newDefaultModelSelection) {
|
|
307
|
+
providers = [...newProviders];
|
|
308
|
+
defaultModelSelection = newDefaultModelSelection;
|
|
309
|
+
providerStatus.clear();
|
|
310
|
+
for (const p of providers) {
|
|
311
|
+
providerStatus.set(p.id, "unknown");
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
getProviderStatus(providerId) {
|
|
315
|
+
return providerStatus.get(providerId) ?? "unknown";
|
|
316
|
+
},
|
|
317
|
+
async getProviderModels(providerId) {
|
|
318
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
319
|
+
if (!provider) {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
if (provider.models && provider.models.length > 0) {
|
|
323
|
+
return provider.models;
|
|
324
|
+
}
|
|
325
|
+
const url = provider.url.includes("/v1") ? provider.url.replace("/v1", "") : provider.url;
|
|
326
|
+
const backend = provider.backend;
|
|
327
|
+
return fetchModelsWithContext(url, provider.apiKey, backend);
|
|
328
|
+
},
|
|
329
|
+
async setDefaultModelSelection(providerId, model) {
|
|
330
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
331
|
+
if (!provider) {
|
|
332
|
+
return { success: false, error: "Provider not found" };
|
|
333
|
+
}
|
|
334
|
+
logger.info("Setting default model selection", { providerId, model, providerName: provider.name });
|
|
335
|
+
defaultModelSelection = `${providerId}/${model}`;
|
|
336
|
+
providers = providers.map((p) => ({ ...p, isActive: p.id === providerId }));
|
|
337
|
+
const currentProviderId = parseDefaultModelSelection(defaultModelSelection).providerId;
|
|
338
|
+
if (currentProviderId === providerId) {
|
|
339
|
+
llmClient.setModel(model);
|
|
340
|
+
logger.info("Model updated", { providerId, model });
|
|
341
|
+
}
|
|
342
|
+
return { success: true };
|
|
343
|
+
},
|
|
344
|
+
getCurrentModelContext() {
|
|
345
|
+
const { providerId, model } = parseDefaultModelSelection(defaultModelSelection);
|
|
346
|
+
if (!providerId || !model) return config.context.maxTokens;
|
|
347
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
348
|
+
if (!provider) return config.context.maxTokens;
|
|
349
|
+
const modelConfig = provider.models.find((m) => m.id === model);
|
|
350
|
+
return modelConfig?.contextWindow ?? config.context.maxTokens;
|
|
351
|
+
},
|
|
352
|
+
async updateModelContext(providerId, modelId, contextWindow) {
|
|
353
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
354
|
+
if (!provider) {
|
|
355
|
+
return { success: false, error: "Provider not found" };
|
|
356
|
+
}
|
|
357
|
+
const modelIndex = provider.models.findIndex((m) => m.id === modelId);
|
|
358
|
+
if (modelIndex === -1) {
|
|
359
|
+
providers = providers.map(
|
|
360
|
+
(p) => p.id === providerId ? { ...p, models: [...p.models, { id: modelId, contextWindow, source: "user" }] } : p
|
|
361
|
+
);
|
|
362
|
+
} else {
|
|
363
|
+
providers = providers.map(
|
|
364
|
+
(p) => p.id === providerId ? { ...p, models: p.models.map((m, i) => i === modelIndex ? { ...m, contextWindow, source: "user" } : m) } : p
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
logger.info("Model context updated", { providerId, modelId, contextWindow });
|
|
368
|
+
return { success: true };
|
|
369
|
+
},
|
|
370
|
+
async refreshProviderModels(providerId) {
|
|
371
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
372
|
+
if (!provider) {
|
|
373
|
+
return { success: false, error: "Provider not found" };
|
|
374
|
+
}
|
|
375
|
+
const url = provider.url.includes("/v1") ? provider.url.replace("/v1", "") : provider.url;
|
|
376
|
+
const backend = provider.backend;
|
|
377
|
+
const modelsWithContext = await fetchModelsWithContext(url, provider.apiKey, backend);
|
|
378
|
+
const normalize = (s) => s.toLowerCase().replace(/[-_\s:.]+/g, "");
|
|
379
|
+
const userModels = provider.models.filter((m) => m.source === "user");
|
|
380
|
+
const allModelsBefore = provider.models.map((m) => ({ id: m.id, contextWindow: m.contextWindow, source: m.source }));
|
|
381
|
+
logger.info("refreshProviderModels", { providerId, userModelsCount: userModels.length, backendModelsCount: modelsWithContext.length, userModels: userModels.map((m) => ({ id: m.id, contextWindow: m.contextWindow, source: m.source })), allModelsBefore, backendModels: modelsWithContext });
|
|
382
|
+
if (modelsWithContext.length === 0) {
|
|
383
|
+
if (userModels.length > 0) {
|
|
384
|
+
logger.debug("Backend unavailable, preserving user models", { providerId, userModels: userModels.map((m) => ({ id: m.id, contextWindow: m.contextWindow })) });
|
|
385
|
+
providers = providers.map((p) => p.id === providerId ? { ...p, models: userModels } : p);
|
|
386
|
+
const preservedProvider = providers.find((p) => p.id === providerId);
|
|
387
|
+
logger.debug("After preservation", { providerId, models: preservedProvider?.models.map((m) => ({ id: m.id, contextWindow: m.contextWindow, source: m.source })) });
|
|
388
|
+
return { success: true };
|
|
389
|
+
}
|
|
390
|
+
return { success: false, error: "No models returned from backend" };
|
|
391
|
+
}
|
|
392
|
+
const updatedModels = modelsWithContext.map((m) => {
|
|
393
|
+
let existingModel = provider.models.find((pm) => pm.id === m.id);
|
|
394
|
+
if (!existingModel) {
|
|
395
|
+
const normalizedId = normalize(m.id);
|
|
396
|
+
existingModel = provider.models.find((pm) => {
|
|
397
|
+
if (pm.source !== "user") return false;
|
|
398
|
+
return normalize(pm.id) === normalizedId;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (existingModel && existingModel.source === "user") {
|
|
402
|
+
return { ...existingModel, id: m.id };
|
|
403
|
+
}
|
|
404
|
+
return m;
|
|
405
|
+
});
|
|
406
|
+
for (const userModel of userModels) {
|
|
407
|
+
const normalizedUserId = normalize(userModel.id);
|
|
408
|
+
const matchedInUpdated = updatedModels.some((m) => {
|
|
409
|
+
const normalizedBackendId = normalize(m.id);
|
|
410
|
+
return normalizedBackendId === normalizedUserId;
|
|
411
|
+
});
|
|
412
|
+
logger.debug("User model match check", { userModelId: userModel.id, normalizedUserId, matchedInUpdated, updatedModelsIds: updatedModels.map((m) => m.id) });
|
|
413
|
+
if (!matchedInUpdated) {
|
|
414
|
+
updatedModels.push(userModel);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
providers = providers.map((p) => p.id === providerId ? { ...p, models: updatedModels } : p);
|
|
418
|
+
const { providerId: currentProviderId, model: currentModel } = parseDefaultModelSelection(defaultModelSelection);
|
|
419
|
+
if (currentProviderId === providerId && currentModel) {
|
|
420
|
+
const normalizedCurrentModel = normalize(currentModel);
|
|
421
|
+
const matchedModel = updatedModels.find((m) => normalize(m.id) === normalizedCurrentModel);
|
|
422
|
+
if (matchedModel && matchedModel.id !== currentModel) {
|
|
423
|
+
defaultModelSelection = `${providerId}/${matchedModel.id}`;
|
|
424
|
+
logger.debug("Updated defaultModelSelection after fuzzy match", { from: currentModel, to: matchedModel.id });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
logger.info("Provider models refreshed", { providerId, modelCount: updatedModels.length });
|
|
428
|
+
return { success: true };
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export {
|
|
434
|
+
fetchAvailableModelsFromBackend,
|
|
435
|
+
parseDefaultModelSelection,
|
|
436
|
+
createProviderManager
|
|
437
|
+
};
|
|
438
|
+
//# sourceMappingURL=chunk-QYP5HQV4.js.map
|