openfox 0.1.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/README.md +68 -0
- package/dist/branch.api-WDHKZJS6.js +48 -0
- package/dist/branch.api-WDHKZJS6.js.map +1 -0
- package/dist/chunk-26NRL3HX.js +116 -0
- package/dist/chunk-26NRL3HX.js.map +1 -0
- package/dist/chunk-43SWNGYW.js +273 -0
- package/dist/chunk-43SWNGYW.js.map +1 -0
- package/dist/chunk-4Z2OZ6NX.js +132 -0
- package/dist/chunk-4Z2OZ6NX.js.map +1 -0
- package/dist/chunk-6MOGZMGX.js +218 -0
- package/dist/chunk-6MOGZMGX.js.map +1 -0
- package/dist/chunk-AF7XS4OI.js +55 -0
- package/dist/chunk-AF7XS4OI.js.map +1 -0
- package/dist/chunk-EI46G5WO.js +138 -0
- package/dist/chunk-EI46G5WO.js.map +1 -0
- package/dist/chunk-FF6Z2MC3.js +844 -0
- package/dist/chunk-FF6Z2MC3.js.map +1 -0
- package/dist/chunk-IKAWIOXM.js +5817 -0
- package/dist/chunk-IKAWIOXM.js.map +1 -0
- package/dist/chunk-IVHCPQO2.js +29 -0
- package/dist/chunk-IVHCPQO2.js.map +1 -0
- package/dist/chunk-MTX5Y3V3.js +46 -0
- package/dist/chunk-MTX5Y3V3.js.map +1 -0
- package/dist/chunk-N2XYX4ZA.js +83 -0
- package/dist/chunk-N2XYX4ZA.js.map +1 -0
- package/dist/chunk-QY7BMXWT.js +124 -0
- package/dist/chunk-QY7BMXWT.js.map +1 -0
- package/dist/chunk-U62ZECSQ.js +54 -0
- package/dist/chunk-U62ZECSQ.js.map +1 -0
- package/dist/chunk-YQ36AK5A.js +5570 -0
- package/dist/chunk-YQ36AK5A.js.map +1 -0
- package/dist/cli/dev.d.ts +1 -0
- package/dist/cli/dev.js +14 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/command-defaults/defaults/commit-push.command.md +13 -0
- package/dist/command-defaults/defaults/init.command.md +18 -0
- package/dist/command-defaults/defaults/test-ui.command.md +16 -0
- package/dist/config-6GJ4SVFH.js +33 -0
- package/dist/config-6GJ4SVFH.js.map +1 -0
- package/dist/init-UVZDOHMX.js +185 -0
- package/dist/init-UVZDOHMX.js.map +1 -0
- package/dist/paths-4BPXWB2W.js +17 -0
- package/dist/paths-4BPXWB2W.js.map +1 -0
- package/dist/project-creator-CWPKRIKI.js +69 -0
- package/dist/project-creator-CWPKRIKI.js.map +1 -0
- package/dist/protocol-BJsm88OW.d.ts +760 -0
- package/dist/provider-LCE6WZA6.js +343 -0
- package/dist/provider-LCE6WZA6.js.map +1 -0
- package/dist/serve-27DZK34K.js +177 -0
- package/dist/serve-27DZK34K.js.map +1 -0
- package/dist/server/index.d.ts +479 -0
- package/dist/server/index.js +20 -0
- package/dist/server/index.js.map +1 -0
- package/dist/settings-D5O3TMLD.js +17 -0
- package/dist/settings-D5O3TMLD.js.map +1 -0
- package/dist/shared/index.d.ts +19 -0
- package/dist/shared/index.js +17 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/skill-defaults/defaults/playwright-cli.skill.md +38 -0
- package/dist/tools-R4YQAYUF.js +46 -0
- package/dist/tools-R4YQAYUF.js.map +1 -0
- package/dist/web/assets/html2canvas.esm-QH1iLAAe.js +22 -0
- package/dist/web/assets/index-BMNDCsbJ.js +120 -0
- package/dist/web/assets/index-BSlSWZrM.css +1 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-600-normal-8K4wrrwR.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-600-normal-EVf6-Yzo.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-600-normal-H7WoG9Et.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-600-normal-mc2nkWzM.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-600-normal-BfB_LPfz.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-600-normal-DObL3zCW.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-600-normal-OWROknRo.woff +0 -0
- package/dist/web/assets/openfox.png +0 -0
- package/dist/web/fox.svg +25 -0
- package/dist/web/index.html +14 -0
- package/dist/web/sounds/achievement.mp3 +0 -0
- package/dist/web/sounds/intervention.mp3 +0 -0
- package/dist/web/sounds/notification.mp3 +0 -0
- package/dist/web/sounds/waiting-for-user.mp3 +0 -0
- package/package.json +101 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# OpenFox
|
|
2
|
+
|
|
3
|
+
**Local-LLM-first agentic coding assistant**
|
|
4
|
+
|
|
5
|
+
Autonomous coding agent for local LLMs with contract-driven execution.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx openfox
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
On first run, OpenFox automatically detects your local LLM backend (vLLM, sglang, ollama, llamacpp) and configures itself.
|
|
14
|
+
|
|
15
|
+
## CLI Commands
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Start server for current project
|
|
19
|
+
npx openfox
|
|
20
|
+
|
|
21
|
+
# Start on custom port
|
|
22
|
+
npx openfox --port 8080
|
|
23
|
+
|
|
24
|
+
# Start without opening browser
|
|
25
|
+
npx openfox --no-browser
|
|
26
|
+
|
|
27
|
+
# Interactive configuration setup
|
|
28
|
+
npx openfox init
|
|
29
|
+
|
|
30
|
+
# Show current configuration
|
|
31
|
+
npx openfox config
|
|
32
|
+
|
|
33
|
+
# Manage LLM providers
|
|
34
|
+
npx openfox provider add # Add new provider
|
|
35
|
+
npx openfox provider list # List configured providers
|
|
36
|
+
npx openfox provider use # Switch active provider
|
|
37
|
+
npx openfox provider remove # Remove provider
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## CLI Options
|
|
41
|
+
|
|
42
|
+
| Option | Description | Default |
|
|
43
|
+
|--------|-------------|---------|
|
|
44
|
+
| `-p, --port <number>` | Specify server port | 10369 |
|
|
45
|
+
| `--no-browser` | Don't open browser on start | Opens browser |
|
|
46
|
+
| `-h, --help` | Show help message | - |
|
|
47
|
+
| `-v, --version` | Show version number | - |
|
|
48
|
+
|
|
49
|
+
## Requirements
|
|
50
|
+
|
|
51
|
+
- Node.js >= 24.0.0
|
|
52
|
+
- Local LLM backend with OpenAI-compatible API:
|
|
53
|
+
- vLLM
|
|
54
|
+
- sglang
|
|
55
|
+
- ollama
|
|
56
|
+
- llamacpp
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Plan → Builder Workflow**: Interactive task breakdown followed by autonomous implementation
|
|
61
|
+
- **Contract-Driven Execution**: Acceptance criteria serve as immutable contract
|
|
62
|
+
- **Iterative Verification**: Agent loops until all criteria pass
|
|
63
|
+
- **LSP Integration**: Immediate feedback on code validity
|
|
64
|
+
- **Real-Time Metrics**: Prefill time, generation speed, context usage
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-U62ZECSQ.js";
|
|
4
|
+
|
|
5
|
+
// src/server/branch.api.ts
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
import { resolve } from "path";
|
|
8
|
+
async function getCurrentBranch(req, res) {
|
|
9
|
+
try {
|
|
10
|
+
const workdir = req.query["workdir"] || process.cwd();
|
|
11
|
+
const resolvedWorkdir = resolve(workdir);
|
|
12
|
+
const branch = await getGitBranch(resolvedWorkdir);
|
|
13
|
+
if (branch) {
|
|
14
|
+
res.json({ branch, workdir: resolvedWorkdir });
|
|
15
|
+
} else {
|
|
16
|
+
res.json({ branch: null, workdir: resolvedWorkdir, error: "Not a git repository" });
|
|
17
|
+
}
|
|
18
|
+
} catch (error) {
|
|
19
|
+
logger.error("Error getting current branch", { error: error instanceof Error ? error.message : String(error) });
|
|
20
|
+
res.status(500).json({ error: "Failed to get branch", branch: null });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function getGitBranch(cwd) {
|
|
24
|
+
return new Promise((resolve2) => {
|
|
25
|
+
const proc = spawn("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
26
|
+
cwd,
|
|
27
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
28
|
+
});
|
|
29
|
+
let stdout = "";
|
|
30
|
+
proc.stdout.on("data", (data) => {
|
|
31
|
+
stdout += data.toString();
|
|
32
|
+
});
|
|
33
|
+
proc.on("close", (code) => {
|
|
34
|
+
if (code === 0 && stdout.trim()) {
|
|
35
|
+
resolve2(stdout.trim());
|
|
36
|
+
} else {
|
|
37
|
+
resolve2(null);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
proc.on("error", () => {
|
|
41
|
+
resolve2(null);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
getCurrentBranch
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=branch.api-WDHKZJS6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/branch.api.ts"],"sourcesContent":["import type { Request, Response } from 'express'\nimport { spawn } from 'node:child_process'\nimport { resolve } from 'node:path'\nimport { logger } from './utils/logger.js'\n\n/**\n * GET /api/branch\n * Get the current git branch name for a workdir\n */\nexport async function getCurrentBranch(req: Request, res: Response): Promise<void> {\n try {\n const workdir = (req.query['workdir'] as string) || process.cwd()\n const resolvedWorkdir = resolve(workdir)\n\n const branch = await getGitBranch(resolvedWorkdir)\n\n if (branch) {\n res.json({ branch, workdir: resolvedWorkdir })\n } else {\n res.json({ branch: null, workdir: resolvedWorkdir, error: 'Not a git repository' })\n }\n } catch (error) {\n logger.error('Error getting current branch', { error: error instanceof Error ? error.message : String(error) })\n res.status(500).json({ error: 'Failed to get branch', branch: null })\n }\n}\n\n/**\n * Execute git rev-parse --abbrev-ref HEAD to get the current branch name\n */\nfunction getGitBranch(cwd: string): Promise<string | null> {\n return new Promise((resolve) => {\n const proc = spawn('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n\n let stdout = ''\n\n proc.stdout.on('data', (data: Buffer) => {\n stdout += data.toString()\n })\n\n proc.on('close', (code) => {\n if (code === 0 && stdout.trim()) {\n resolve(stdout.trim())\n } else {\n resolve(null)\n }\n })\n\n proc.on('error', () => {\n resolve(null)\n })\n })\n}\n"],"mappings":";;;;;AACA,SAAS,aAAa;AACtB,SAAS,eAAe;AAOxB,eAAsB,iBAAiB,KAAc,KAA8B;AACjF,MAAI;AACF,UAAM,UAAW,IAAI,MAAM,SAAS,KAAgB,QAAQ,IAAI;AAChE,UAAM,kBAAkB,QAAQ,OAAO;AAEvC,UAAM,SAAS,MAAM,aAAa,eAAe;AAEjD,QAAI,QAAQ;AACV,UAAI,KAAK,EAAE,QAAQ,SAAS,gBAAgB,CAAC;AAAA,IAC/C,OAAO;AACL,UAAI,KAAK,EAAE,QAAQ,MAAM,SAAS,iBAAiB,OAAO,uBAAuB,CAAC;AAAA,IACpF;AAAA,EACF,SAAS,OAAO;AACd,WAAO,MAAM,gCAAgC,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AAC9G,QAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,QAAQ,KAAK,CAAC;AAAA,EACtE;AACF;AAKA,SAAS,aAAa,KAAqC;AACzD,SAAO,IAAI,QAAQ,CAACA,aAAY;AAC9B,UAAM,OAAO,MAAM,OAAO,CAAC,aAAa,gBAAgB,MAAM,GAAG;AAAA,MAC/D;AAAA,MACA,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,IACpC,CAAC;AAED,QAAI,SAAS;AAEb,SAAK,OAAO,GAAG,QAAQ,CAAC,SAAiB;AACvC,gBAAU,KAAK,SAAS;AAAA,IAC1B,CAAC;AAED,SAAK,GAAG,SAAS,CAAC,SAAS;AACzB,UAAI,SAAS,KAAK,OAAO,KAAK,GAAG;AAC/B,QAAAA,SAAQ,OAAO,KAAK,CAAC;AAAA,MACvB,OAAO;AACL,QAAAA,SAAQ,IAAI;AAAA,MACd;AAAA,IACF,CAAC;AAED,SAAK,GAAG,SAAS,MAAM;AACrB,MAAAA,SAAQ,IAAI;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AACH;","names":["resolve"]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/server/llm/streaming.ts
|
|
2
|
+
var XML_TOOL_PATTERNS = ["<tool_call>", "<function=", "</tool_call>", "<parameter="];
|
|
3
|
+
function hasXmlToolPattern(text) {
|
|
4
|
+
return XML_TOOL_PATTERNS.some((p) => text.includes(p));
|
|
5
|
+
}
|
|
6
|
+
async function* streamWithSegments(client, request) {
|
|
7
|
+
const xmlAbortController = new AbortController();
|
|
8
|
+
const combinedSignal = request.signal ? AbortSignal.any([request.signal, xmlAbortController.signal]) : xmlAbortController.signal;
|
|
9
|
+
let content = "";
|
|
10
|
+
let thinkingContent = "";
|
|
11
|
+
let response = null;
|
|
12
|
+
const segments = [];
|
|
13
|
+
let currentTextSegment = "";
|
|
14
|
+
let currentThinkingSegment = "";
|
|
15
|
+
const startTime = performance.now();
|
|
16
|
+
let firstTokenTime = null;
|
|
17
|
+
const flushText = () => {
|
|
18
|
+
if (currentTextSegment.trim()) {
|
|
19
|
+
segments.push({ type: "text", content: currentTextSegment });
|
|
20
|
+
}
|
|
21
|
+
currentTextSegment = "";
|
|
22
|
+
};
|
|
23
|
+
const flushThinking = () => {
|
|
24
|
+
if (currentThinkingSegment.trim()) {
|
|
25
|
+
segments.push({ type: "thinking", content: currentThinkingSegment });
|
|
26
|
+
}
|
|
27
|
+
currentThinkingSegment = "";
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
for await (const event of client.stream({ ...request, signal: combinedSignal })) {
|
|
31
|
+
switch (event.type) {
|
|
32
|
+
case "text_delta":
|
|
33
|
+
if (firstTokenTime === null) {
|
|
34
|
+
firstTokenTime = performance.now();
|
|
35
|
+
}
|
|
36
|
+
flushThinking();
|
|
37
|
+
content += event.content;
|
|
38
|
+
currentTextSegment += event.content;
|
|
39
|
+
if (hasXmlToolPattern(content)) {
|
|
40
|
+
xmlAbortController.abort();
|
|
41
|
+
yield { type: "xml_tool_abort" };
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
yield { type: "text_delta", content: event.content };
|
|
45
|
+
break;
|
|
46
|
+
case "thinking_delta":
|
|
47
|
+
if (firstTokenTime === null) {
|
|
48
|
+
firstTokenTime = performance.now();
|
|
49
|
+
}
|
|
50
|
+
flushText();
|
|
51
|
+
thinkingContent += event.content;
|
|
52
|
+
currentThinkingSegment += event.content;
|
|
53
|
+
if (hasXmlToolPattern(thinkingContent)) {
|
|
54
|
+
xmlAbortController.abort();
|
|
55
|
+
yield { type: "xml_tool_abort" };
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
yield { type: "thinking_delta", content: event.content };
|
|
59
|
+
break;
|
|
60
|
+
case "tool_call_delta":
|
|
61
|
+
yield {
|
|
62
|
+
type: "tool_call_delta",
|
|
63
|
+
index: event.index,
|
|
64
|
+
...event.id !== void 0 ? { id: event.id } : {},
|
|
65
|
+
...event.name !== void 0 ? { name: event.name } : {},
|
|
66
|
+
...event.arguments !== void 0 ? { arguments: event.arguments } : {}
|
|
67
|
+
};
|
|
68
|
+
break;
|
|
69
|
+
case "done":
|
|
70
|
+
flushThinking();
|
|
71
|
+
flushText();
|
|
72
|
+
response = event.response;
|
|
73
|
+
yield { type: "done", response: event.response };
|
|
74
|
+
break;
|
|
75
|
+
case "error":
|
|
76
|
+
yield { type: "error", error: event.error };
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
yield { type: "error", error: error instanceof Error ? error.message : "Unknown error" };
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (!response) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const toolCalls = response.toolCalls ?? [];
|
|
91
|
+
for (const tc of toolCalls) {
|
|
92
|
+
segments.push({ type: "tool_call", toolCallId: tc.id });
|
|
93
|
+
}
|
|
94
|
+
const endTime = performance.now();
|
|
95
|
+
const ttft = ((firstTokenTime ?? endTime) - startTime) / 1e3;
|
|
96
|
+
const completionTime = (endTime - (firstTokenTime ?? startTime)) / 1e3;
|
|
97
|
+
const { promptTokens, completionTokens } = response.usage;
|
|
98
|
+
return {
|
|
99
|
+
content,
|
|
100
|
+
thinkingContent,
|
|
101
|
+
toolCalls,
|
|
102
|
+
response,
|
|
103
|
+
segments,
|
|
104
|
+
timing: {
|
|
105
|
+
ttft,
|
|
106
|
+
completionTime,
|
|
107
|
+
tps: completionTime > 0 ? completionTokens / completionTime : 0,
|
|
108
|
+
prefillTps: ttft > 0 ? promptTokens / ttft : 0
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
streamWithSegments
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=chunk-26NRL3HX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/llm/streaming.ts"],"sourcesContent":["import type { MessageSegment, ToolCall } from '../../shared/types.js'\nimport type { LLMClient, LLMCompletionRequest, LLMStreamEvent, LLMCompletionResponse } from './types.js'\n\n// XML patterns that indicate wrong tool format (model outputting XML instead of JSON function calls)\nconst XML_TOOL_PATTERNS = ['<tool_call>', '<function=', '</tool_call>', '<parameter=']\n\nfunction hasXmlToolPattern(text: string): boolean {\n return XML_TOOL_PATTERNS.some(p => text.includes(p))\n}\n\n/**\n * Streaming event with segment tracking.\n * Same as LLMStreamEvent but adds segment context.\n */\nexport type StreamEvent =\n | { type: 'text_delta'; content: string }\n | { type: 'thinking_delta'; content: string }\n | { type: 'tool_call_delta'; index: number; id?: string; name?: string; arguments?: string }\n | { type: 'done'; response: LLMCompletionResponse }\n | { type: 'error'; error: string }\n | { type: 'xml_tool_abort' }\n\n/**\n * Timing metrics for a streaming LLM call.\n */\nexport interface StreamTiming {\n ttft: number // time to first token (seconds)\n completionTime: number // generation time (seconds)\n tps: number // tokens per second (generation)\n prefillTps: number // prompt tokens / ttft\n}\n\n/**\n * Result from a single streaming LLM call.\n * Contains accumulated content and segments for that call.\n */\nexport interface StreamResult {\n content: string\n thinkingContent: string\n toolCalls: ToolCall[]\n response: LLMCompletionResponse\n segments: MessageSegment[]\n timing: StreamTiming\n}\n\n/**\n * Streams an LLM completion and accumulates content with segment tracking.\n * Yields events for real-time streaming, returns accumulated result.\n * \n * Merges consecutive text/thinking deltas into single segments.\n */\nexport async function* streamWithSegments(\n client: LLMClient,\n request: LLMCompletionRequest\n): AsyncGenerator<StreamEvent, StreamResult | null> {\n // Create internal abort controller for XML detection\n const xmlAbortController = new AbortController()\n \n // Combine with any external signal (e.g., user abort)\n const combinedSignal = request.signal\n ? AbortSignal.any([request.signal, xmlAbortController.signal])\n : xmlAbortController.signal\n \n let content = ''\n let thinkingContent = ''\n let response: LLMCompletionResponse | null = null\n \n const segments: MessageSegment[] = []\n let currentTextSegment = ''\n let currentThinkingSegment = ''\n \n // Timing tracking\n const startTime = performance.now()\n let firstTokenTime: number | null = null\n \n // Flush accumulated text to segments (skip whitespace-only)\n const flushText = () => {\n if (currentTextSegment.trim()) {\n segments.push({ type: 'text', content: currentTextSegment })\n }\n currentTextSegment = ''\n }\n \n // Flush accumulated thinking to segments (skip whitespace-only)\n const flushThinking = () => {\n if (currentThinkingSegment.trim()) {\n segments.push({ type: 'thinking', content: currentThinkingSegment })\n }\n currentThinkingSegment = ''\n }\n \n try {\n for await (const event of client.stream({ ...request, signal: combinedSignal })) {\n switch (event.type) {\n case 'text_delta':\n // Track first token time\n if (firstTokenTime === null) {\n firstTokenTime = performance.now()\n }\n // If we were accumulating thinking, flush it first\n flushThinking()\n content += event.content\n currentTextSegment += event.content\n \n // Check for XML tool syntax - abort immediately to save tokens\n if (hasXmlToolPattern(content)) {\n xmlAbortController.abort()\n yield { type: 'xml_tool_abort' }\n return null\n }\n \n yield { type: 'text_delta', content: event.content }\n break\n \n case 'thinking_delta':\n // Track first token time\n if (firstTokenTime === null) {\n firstTokenTime = performance.now()\n }\n // If we were accumulating text, flush it first\n flushText()\n thinkingContent += event.content\n currentThinkingSegment += event.content\n \n // Check for XML tool syntax in thinking too\n if (hasXmlToolPattern(thinkingContent)) {\n xmlAbortController.abort()\n yield { type: 'xml_tool_abort' }\n return null\n }\n \n yield { type: 'thinking_delta', content: event.content }\n break\n \n case 'tool_call_delta':\n // Forward tool call delta events for early UI feedback\n yield {\n type: 'tool_call_delta',\n index: event.index,\n ...(event.id !== undefined ? { id: event.id } : {}),\n ...(event.name !== undefined ? { name: event.name } : {}),\n ...(event.arguments !== undefined ? { arguments: event.arguments } : {}),\n }\n break\n \n case 'done':\n // Flush any remaining content\n flushThinking()\n flushText()\n response = event.response\n yield { type: 'done', response: event.response }\n break\n \n case 'error':\n yield { type: 'error', error: event.error }\n return null\n }\n }\n } catch (error) {\n // AbortError is expected when we abort for XML detection\n if (error instanceof Error && error.name === 'AbortError') {\n return null\n }\n yield { type: 'error', error: error instanceof Error ? error.message : 'Unknown error' }\n return null\n }\n \n if (!response) {\n return null\n }\n \n // Add tool call segments (references to the toolCalls array)\n const toolCalls = response.toolCalls ?? []\n for (const tc of toolCalls) {\n segments.push({ type: 'tool_call', toolCallId: tc.id })\n }\n \n // Calculate timing\n const endTime = performance.now()\n const ttft = ((firstTokenTime ?? endTime) - startTime) / 1000\n const completionTime = (endTime - (firstTokenTime ?? startTime)) / 1000\n const { promptTokens, completionTokens } = response.usage\n \n return {\n content,\n thinkingContent,\n toolCalls,\n response,\n segments,\n timing: {\n ttft,\n completionTime,\n tps: completionTime > 0 ? completionTokens / completionTime : 0,\n prefillTps: ttft > 0 ? promptTokens / ttft : 0,\n },\n }\n}\n\n/**\n * Builder for accumulating segments across multiple LLM calls.\n * Use this when you have a loop with multiple LLM calls + tool executions.\n */\nexport class SegmentBuilder {\n private segments: MessageSegment[] = []\n \n /**\n * Add segments from a streaming result.\n */\n addFromResult(result: StreamResult): void {\n this.segments.push(...result.segments)\n }\n \n /**\n * Manually add a tool call segment (for tool calls executed between LLM calls).\n */\n addToolCall(toolCallId: string): void {\n this.segments.push({ type: 'tool_call', toolCallId })\n }\n \n /**\n * Get all accumulated segments.\n */\n build(): MessageSegment[] {\n return [...this.segments]\n }\n \n /**\n * Clear all segments (for reuse).\n */\n clear(): void {\n this.segments = []\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectBackend,
|
|
3
|
+
detectModel
|
|
4
|
+
} from "./chunk-FF6Z2MC3.js";
|
|
5
|
+
import {
|
|
6
|
+
getGlobalConfigPath
|
|
7
|
+
} from "./chunk-AF7XS4OI.js";
|
|
8
|
+
|
|
9
|
+
// src/cli/config.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
12
|
+
import { readFile, writeFile, mkdir, access } from "fs/promises";
|
|
13
|
+
import { dirname } from "path";
|
|
14
|
+
var SMART_DEFAULTS = [
|
|
15
|
+
"http://localhost:8000",
|
|
16
|
+
"http://localhost:11434",
|
|
17
|
+
"http://localhost:8080"
|
|
18
|
+
];
|
|
19
|
+
async function trySmartDefaults(mode) {
|
|
20
|
+
const results = await Promise.all(
|
|
21
|
+
SMART_DEFAULTS.map(async (url) => {
|
|
22
|
+
try {
|
|
23
|
+
const [backend, model] = await Promise.all([
|
|
24
|
+
detectBackend(url, void 0, true),
|
|
25
|
+
detectModel(url, 1, true)
|
|
26
|
+
// Only 1 retry attempt
|
|
27
|
+
]);
|
|
28
|
+
if (backend !== "unknown" && model) {
|
|
29
|
+
return { url, backend, model };
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
return results.find((r) => r !== null) || null;
|
|
37
|
+
}
|
|
38
|
+
async function configFileExists(mode) {
|
|
39
|
+
const configPath = getGlobalConfigPath(mode);
|
|
40
|
+
try {
|
|
41
|
+
await access(configPath);
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
var backendSchema = z.enum(["auto", "vllm", "sglang", "ollama", "llamacpp", "openai", "anthropic", "unknown"]);
|
|
48
|
+
var providerSchema = z.object({
|
|
49
|
+
id: z.string(),
|
|
50
|
+
name: z.string(),
|
|
51
|
+
url: z.string(),
|
|
52
|
+
backend: backendSchema,
|
|
53
|
+
apiKey: z.string().optional(),
|
|
54
|
+
maxContext: z.number().optional(),
|
|
55
|
+
isActive: z.boolean(),
|
|
56
|
+
createdAt: z.string(),
|
|
57
|
+
// Deprecated: model field kept for migration, will be removed after migration
|
|
58
|
+
model: z.string().optional()
|
|
59
|
+
});
|
|
60
|
+
var serverSchema = z.object({
|
|
61
|
+
port: z.number().default(10369),
|
|
62
|
+
host: z.string().default("127.0.0.1"),
|
|
63
|
+
openBrowser: z.boolean().default(true)
|
|
64
|
+
});
|
|
65
|
+
var loggingSchema = z.object({
|
|
66
|
+
level: z.enum(["debug", "info", "warn", "error"]).default("info")
|
|
67
|
+
});
|
|
68
|
+
var databaseSchema = z.object({
|
|
69
|
+
path: z.string().default("")
|
|
70
|
+
});
|
|
71
|
+
var workspaceSchema = z.object({
|
|
72
|
+
workdir: z.string().default(process.cwd())
|
|
73
|
+
});
|
|
74
|
+
var configSchema = z.object({
|
|
75
|
+
providers: z.array(providerSchema).default([]),
|
|
76
|
+
defaultModelSelection: z.string().optional(),
|
|
77
|
+
activeProviderId: z.string().optional(),
|
|
78
|
+
activeWorkflowId: z.string().optional(),
|
|
79
|
+
server: serverSchema.default({}),
|
|
80
|
+
logging: loggingSchema.default({}),
|
|
81
|
+
database: databaseSchema.default({}),
|
|
82
|
+
workspace: workspaceSchema.default({})
|
|
83
|
+
});
|
|
84
|
+
var oldLlmSchema = z.object({
|
|
85
|
+
url: z.string().url().default("http://localhost:8000/v1"),
|
|
86
|
+
model: z.string().default("auto"),
|
|
87
|
+
backend: z.enum(["auto", "vllm", "sglang", "ollama", "llamacpp"]).default("auto"),
|
|
88
|
+
maxContext: z.number().default(2e5),
|
|
89
|
+
disableThinking: z.boolean().default(false),
|
|
90
|
+
apiKey: z.string().optional()
|
|
91
|
+
});
|
|
92
|
+
var oldConfigSchema = z.object({
|
|
93
|
+
llm: oldLlmSchema,
|
|
94
|
+
server: serverSchema.default({}),
|
|
95
|
+
logging: loggingSchema.default({}),
|
|
96
|
+
database: databaseSchema.default({})
|
|
97
|
+
});
|
|
98
|
+
function migrateConfig(raw) {
|
|
99
|
+
if (typeof raw === "object" && raw !== null && "providers" in raw) {
|
|
100
|
+
const obj = raw;
|
|
101
|
+
if (obj.defaultModelSelection) {
|
|
102
|
+
const providers2 = obj.providers.map((p) => {
|
|
103
|
+
const { model, ...rest } = p;
|
|
104
|
+
return rest;
|
|
105
|
+
});
|
|
106
|
+
return configSchema.parse({
|
|
107
|
+
...obj,
|
|
108
|
+
providers: providers2
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const providers = obj.providers.map((p) => {
|
|
112
|
+
const { model, ...rest } = p;
|
|
113
|
+
return rest;
|
|
114
|
+
});
|
|
115
|
+
let defaultModelSelection;
|
|
116
|
+
if (obj.activeProviderId) {
|
|
117
|
+
const activeProvider = obj.providers.find((p) => p.id === obj.activeProviderId);
|
|
118
|
+
if (activeProvider?.model) {
|
|
119
|
+
defaultModelSelection = `${obj.activeProviderId}/${activeProvider.model}`;
|
|
120
|
+
} else {
|
|
121
|
+
defaultModelSelection = `${obj.activeProviderId}/auto`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return configSchema.parse({
|
|
125
|
+
...obj,
|
|
126
|
+
providers,
|
|
127
|
+
defaultModelSelection
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (typeof raw === "object" && raw !== null && "llm" in raw) {
|
|
131
|
+
const oldConfig = oldConfigSchema.parse(raw);
|
|
132
|
+
const providerId = randomUUID();
|
|
133
|
+
const provider = {
|
|
134
|
+
id: providerId,
|
|
135
|
+
name: "Default",
|
|
136
|
+
url: oldConfig.llm.url,
|
|
137
|
+
backend: oldConfig.llm.backend,
|
|
138
|
+
apiKey: oldConfig.llm.apiKey,
|
|
139
|
+
maxContext: oldConfig.llm.maxContext,
|
|
140
|
+
isActive: true,
|
|
141
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
142
|
+
};
|
|
143
|
+
const model = oldConfig.llm.model || "auto";
|
|
144
|
+
return {
|
|
145
|
+
providers: [provider],
|
|
146
|
+
defaultModelSelection: `${providerId}/${model}`,
|
|
147
|
+
server: oldConfig.server,
|
|
148
|
+
logging: oldConfig.logging,
|
|
149
|
+
database: oldConfig.database,
|
|
150
|
+
workspace: { workdir: process.cwd() }
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return configSchema.parse(raw);
|
|
154
|
+
}
|
|
155
|
+
async function loadGlobalConfig(mode) {
|
|
156
|
+
const configPath = getGlobalConfigPath(mode);
|
|
157
|
+
try {
|
|
158
|
+
const content = await readFile(configPath, "utf-8");
|
|
159
|
+
const parsed = JSON.parse(content);
|
|
160
|
+
return migrateConfig(parsed);
|
|
161
|
+
} catch {
|
|
162
|
+
return configSchema.parse({});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function saveGlobalConfig(mode, config) {
|
|
166
|
+
const configPath = getGlobalConfigPath(mode);
|
|
167
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
168
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
169
|
+
}
|
|
170
|
+
function getActiveProvider(config) {
|
|
171
|
+
if (config.defaultModelSelection) {
|
|
172
|
+
const [providerId] = config.defaultModelSelection.split("/");
|
|
173
|
+
return config.providers?.find((p) => p.id === providerId);
|
|
174
|
+
}
|
|
175
|
+
if (!config.activeProviderId) return void 0;
|
|
176
|
+
return config.providers?.find((p) => p.id === config.activeProviderId);
|
|
177
|
+
}
|
|
178
|
+
function getDefaultModel(config) {
|
|
179
|
+
if (!config.defaultModelSelection) return void 0;
|
|
180
|
+
const parts = config.defaultModelSelection.split("/");
|
|
181
|
+
return parts[1] ?? "auto";
|
|
182
|
+
}
|
|
183
|
+
function setDefaultModelSelection(config, providerId, model) {
|
|
184
|
+
const defaultModelSelection = `${providerId}/${model}`;
|
|
185
|
+
return {
|
|
186
|
+
...config,
|
|
187
|
+
defaultModelSelection,
|
|
188
|
+
activeProviderId: providerId,
|
|
189
|
+
// Keep for backwards compatibility
|
|
190
|
+
providers: config.providers?.map((p) => ({ ...p, isActive: p.id === providerId })) ?? []
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function addProvider(config, provider) {
|
|
194
|
+
const newProvider = {
|
|
195
|
+
...provider,
|
|
196
|
+
id: randomUUID(),
|
|
197
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
198
|
+
};
|
|
199
|
+
const shouldActivate = provider.isActive || config.providers?.length === 0;
|
|
200
|
+
return {
|
|
201
|
+
...config,
|
|
202
|
+
providers: [
|
|
203
|
+
...(config.providers ?? []).map((p) => shouldActivate ? { ...p, isActive: false } : p),
|
|
204
|
+
{ ...newProvider, isActive: shouldActivate }
|
|
205
|
+
],
|
|
206
|
+
defaultModelSelection: shouldActivate ? `${newProvider.id}/auto` : config.defaultModelSelection,
|
|
207
|
+
activeProviderId: shouldActivate ? newProvider.id : config.activeProviderId,
|
|
208
|
+
workspace: config.workspace ?? { workdir: process.cwd() }
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function removeProvider(config, providerId) {
|
|
212
|
+
const currentProviders = config.providers ?? [];
|
|
213
|
+
const filtered = currentProviders.filter((p) => p.id !== providerId);
|
|
214
|
+
let newDefaultModelSelection = config.defaultModelSelection;
|
|
215
|
+
if (config.defaultModelSelection) {
|
|
216
|
+
const [selectedProviderId] = config.defaultModelSelection.split("/");
|
|
217
|
+
if (selectedProviderId === providerId) {
|
|
218
|
+
newDefaultModelSelection = filtered.length > 0 ? `${filtered[0].id}/auto` : void 0;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const wasActive = config.activeProviderId === providerId;
|
|
222
|
+
const newActiveId = wasActive && filtered.length > 0 ? filtered[0].id : wasActive ? void 0 : config.activeProviderId;
|
|
223
|
+
return {
|
|
224
|
+
...config,
|
|
225
|
+
providers: filtered.map((p) => ({ ...p, isActive: p.id === newActiveId })),
|
|
226
|
+
activeProviderId: newActiveId,
|
|
227
|
+
defaultModelSelection: newDefaultModelSelection
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function activateProvider(config, providerId) {
|
|
231
|
+
const provider = config.providers?.find((p) => p.id === providerId);
|
|
232
|
+
if (!provider) return config;
|
|
233
|
+
return {
|
|
234
|
+
...config,
|
|
235
|
+
providers: (config.providers ?? []).map((p) => ({ ...p, isActive: p.id === providerId })),
|
|
236
|
+
activeProviderId: providerId
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function mergeConfigs(...configs) {
|
|
240
|
+
const result = configs.reduce((acc, curr) => {
|
|
241
|
+
if (curr.llm) {
|
|
242
|
+
acc.llm = { ...acc.llm, ...curr.llm };
|
|
243
|
+
}
|
|
244
|
+
if (curr.server) {
|
|
245
|
+
acc.server = { ...acc.server, ...curr.server };
|
|
246
|
+
}
|
|
247
|
+
if (curr.logging) {
|
|
248
|
+
acc.logging = { ...acc.logging, ...curr.logging };
|
|
249
|
+
}
|
|
250
|
+
return acc;
|
|
251
|
+
}, {
|
|
252
|
+
llm: { url: "http://localhost:8000/v1", model: "auto", backend: "auto", maxContext: 2e5, disableThinking: false },
|
|
253
|
+
server: { port: 10369, host: "127.0.0.1", openBrowser: true },
|
|
254
|
+
logging: { level: "info" }
|
|
255
|
+
});
|
|
256
|
+
return oldConfigSchema.parse(result);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
trySmartDefaults,
|
|
261
|
+
configFileExists,
|
|
262
|
+
migrateConfig,
|
|
263
|
+
loadGlobalConfig,
|
|
264
|
+
saveGlobalConfig,
|
|
265
|
+
getActiveProvider,
|
|
266
|
+
getDefaultModel,
|
|
267
|
+
setDefaultModelSelection,
|
|
268
|
+
addProvider,
|
|
269
|
+
removeProvider,
|
|
270
|
+
activateProvider,
|
|
271
|
+
mergeConfigs
|
|
272
|
+
};
|
|
273
|
+
//# sourceMappingURL=chunk-43SWNGYW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/config.ts"],"sourcesContent":["import { z } from 'zod'\nimport { randomUUID } from 'node:crypto'\nimport { readFile, writeFile, mkdir, access } from 'node:fs/promises'\nimport { dirname } from 'node:path'\nimport type { Mode } from './main.js'\nimport { getGlobalConfigPath } from './paths.js'\nimport { detectBackend, detectModel } from '../server/llm/index.js'\nimport type { Provider, ProviderBackend } from '../shared/types.js'\n\nconst SMART_DEFAULTS = [\n 'http://localhost:8000',\n 'http://localhost:11434',\n 'http://localhost:8080',\n]\n\nexport async function trySmartDefaults(mode: Mode): Promise<{ url: string; backend: string; model: string } | null> {\n // Try all URLs in parallel, no retries\n const results = await Promise.all(\n SMART_DEFAULTS.map(async (url) => {\n try {\n const [backend, model] = await Promise.all([\n detectBackend(url, undefined, true),\n detectModel(url, 1, true), // Only 1 retry attempt\n ])\n if (backend !== 'unknown' && model) {\n return { url, backend, model }\n }\n } catch {\n // Silent fail\n }\n return null\n })\n )\n \n // Return first successful detection\n return results.find(r => r !== null) || null\n}\n\nexport async function configFileExists(mode: Mode): Promise<boolean> {\n const configPath = getGlobalConfigPath(mode)\n try {\n await access(configPath)\n return true\n } catch {\n return false\n }\n}\n\n// ============================================================================\n// Schema Definitions\n// ============================================================================\n\nconst backendSchema = z.enum(['auto', 'vllm', 'sglang', 'ollama', 'llamacpp', 'openai', 'anthropic', 'unknown'])\n\nconst providerSchema = z.object({\n id: z.string(),\n name: z.string(),\n url: z.string(),\n backend: backendSchema,\n apiKey: z.string().optional(),\n maxContext: z.number().optional(),\n isActive: z.boolean(),\n createdAt: z.string(),\n // Deprecated: model field kept for migration, will be removed after migration\n model: z.string().optional(),\n})\n\nconst serverSchema = z.object({\n port: z.number().default(10369),\n host: z.string().default('127.0.0.1'),\n openBrowser: z.boolean().default(true),\n})\n\nconst loggingSchema = z.object({\n level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),\n})\n\nconst databaseSchema = z.object({\n path: z.string().default(''),\n})\n\nconst workspaceSchema = z.object({\n workdir: z.string().default(process.cwd()),\n})\n\n// New config schema with providers array\nconst configSchema = z.object({\n providers: z.array(providerSchema).default([]),\n defaultModelSelection: z.string().optional(),\n activeProviderId: z.string().optional(),\n activeWorkflowId: z.string().optional(),\n server: serverSchema.default({}),\n logging: loggingSchema.default({}),\n database: databaseSchema.default({}),\n workspace: workspaceSchema.default({}),\n})\n\n// Old config schema (for migration detection)\nconst oldLlmSchema = z.object({\n url: z.string().url().default('http://localhost:8000/v1'),\n model: z.string().default('auto'),\n backend: z.enum(['auto', 'vllm', 'sglang', 'ollama', 'llamacpp']).default('auto'),\n maxContext: z.number().default(200000),\n disableThinking: z.boolean().default(false),\n apiKey: z.string().optional(),\n})\n\nconst oldConfigSchema = z.object({\n llm: oldLlmSchema,\n server: serverSchema.default({}),\n logging: loggingSchema.default({}),\n database: databaseSchema.default({}),\n})\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type GlobalConfig = z.infer<typeof configSchema>\nexport type OldGlobalConfig = z.infer<typeof oldConfigSchema>\n\n// ============================================================================\n// Migration\n// ============================================================================\n\n/**\n * Migrate old config format (single llm object) to new format (providers array).\n * Also migrates provider.model to global defaultModelSelection.\n * If already in new format with defaultModelSelection, returns as-is.\n */\nexport function migrateConfig(raw: unknown): GlobalConfig {\n type RawProvider = { id: string; name: string; url: string; model?: string; backend: string; apiKey?: string; maxContext?: number; isActive: boolean; createdAt: string }\n type RawConfig = { providers: RawProvider[]; activeProviderId?: string; defaultModelSelection?: string; [key: string]: unknown }\n \n // Check if it's already the new format (has providers array)\n if (typeof raw === 'object' && raw !== null && 'providers' in raw) {\n const obj = raw as RawConfig\n \n // If already has defaultModelSelection, just parse and return\n if (obj.defaultModelSelection) {\n // Strip model from providers before parsing\n const providers = obj.providers.map(p => {\n const { model, ...rest } = p\n return rest\n })\n return configSchema.parse({\n ...obj,\n providers,\n })\n }\n \n // Migrate from activeProviderId + provider.model to defaultModelSelection\n const providers = obj.providers.map(p => {\n const { model, ...rest } = p\n return rest\n })\n \n let defaultModelSelection: string | undefined\n if (obj.activeProviderId) {\n const activeProvider = obj.providers.find(p => p.id === obj.activeProviderId)\n if (activeProvider?.model) {\n defaultModelSelection = `${obj.activeProviderId}/${activeProvider.model}`\n } else {\n defaultModelSelection = `${obj.activeProviderId}/auto`\n }\n }\n \n return configSchema.parse({\n ...obj,\n providers,\n defaultModelSelection,\n })\n }\n \n // Check if it's the old format (has llm object)\n if (typeof raw === 'object' && raw !== null && 'llm' in raw) {\n const oldConfig = oldConfigSchema.parse(raw)\n const providerId = randomUUID()\n \n const provider: Provider = {\n id: providerId,\n name: 'Default',\n url: oldConfig.llm.url,\n backend: oldConfig.llm.backend as ProviderBackend,\n apiKey: oldConfig.llm.apiKey,\n maxContext: oldConfig.llm.maxContext,\n isActive: true,\n createdAt: new Date().toISOString(),\n }\n \n const model = oldConfig.llm.model || 'auto'\n \n return {\n providers: [provider],\n defaultModelSelection: `${providerId}/${model}`,\n server: oldConfig.server,\n logging: oldConfig.logging,\n database: oldConfig.database,\n workspace: { workdir: process.cwd() },\n }\n }\n \n // Empty or minimal config - return defaults\n return configSchema.parse(raw)\n}\n\n// ============================================================================\n// Load / Save\n// ============================================================================\n\nexport async function loadGlobalConfig(mode: Mode): Promise<GlobalConfig> {\n const configPath = getGlobalConfigPath(mode)\n \n try {\n const content = await readFile(configPath, 'utf-8')\n const parsed = JSON.parse(content)\n return migrateConfig(parsed)\n } catch {\n return configSchema.parse({})\n }\n}\n\nexport async function saveGlobalConfig(mode: Mode, config: GlobalConfig): Promise<void> {\n const configPath = getGlobalConfigPath(mode)\n await mkdir(dirname(configPath), { recursive: true })\n await writeFile(configPath, JSON.stringify(config, null, 2))\n}\n\n// ============================================================================\n// Provider Helpers\n// ============================================================================\n\nexport function getActiveProvider(config: GlobalConfig): Provider | undefined {\n // Use defaultModelSelection if available\n if (config.defaultModelSelection) {\n const [providerId] = config.defaultModelSelection.split('/')\n return config.providers?.find(p => p.id === providerId)\n }\n // Fallback to activeProviderId for backwards compatibility\n if (!config.activeProviderId) return undefined\n return config.providers?.find(p => p.id === config.activeProviderId)\n}\n\nexport function getDefaultModel(config: GlobalConfig): string | undefined {\n if (!config.defaultModelSelection) return undefined\n const parts = config.defaultModelSelection.split('/')\n return parts[1] ?? 'auto'\n}\n\nexport function setDefaultModelSelection(config: GlobalConfig, providerId: string, model: string): GlobalConfig {\n const defaultModelSelection = `${providerId}/${model}`\n return {\n ...config,\n defaultModelSelection,\n activeProviderId: providerId, // Keep for backwards compatibility\n providers: config.providers?.map(p => ({ ...p, isActive: p.id === providerId })) ?? [],\n }\n}\n\nexport function addProvider(config: GlobalConfig, provider: Omit<Provider, 'id' | 'createdAt'>): GlobalConfig {\n const newProvider: Provider = {\n ...provider,\n id: randomUUID(),\n createdAt: new Date().toISOString(),\n }\n \n // If this is the first provider or marked active, update defaultModelSelection\n const shouldActivate = provider.isActive || config.providers?.length === 0\n \n return {\n ...config,\n providers: [\n ...(config.providers ?? []).map(p => shouldActivate ? { ...p, isActive: false } : p),\n { ...newProvider, isActive: shouldActivate },\n ],\n defaultModelSelection: shouldActivate ? `${newProvider.id}/auto` : config.defaultModelSelection,\n activeProviderId: shouldActivate ? newProvider.id : config.activeProviderId,\n workspace: config.workspace ?? { workdir: process.cwd() },\n }\n}\n\nexport function removeProvider(config: GlobalConfig, providerId: string): GlobalConfig {\n const currentProviders = config.providers ?? []\n const filtered = currentProviders.filter(p => p.id !== providerId)\n \n // Check if we're removing the default model selection's provider\n let newDefaultModelSelection = config.defaultModelSelection\n if (config.defaultModelSelection) {\n const [selectedProviderId] = config.defaultModelSelection.split('/')\n if (selectedProviderId === providerId) {\n // Reset to first available provider with auto\n newDefaultModelSelection = filtered.length > 0 ? `${filtered[0]!.id}/auto` : undefined\n }\n }\n \n const wasActive = config.activeProviderId === providerId\n \n // If we removed the active provider, activate the first remaining one\n const newActiveId = wasActive && filtered.length > 0 \n ? filtered[0]!.id \n : (wasActive ? undefined : config.activeProviderId)\n \n return {\n ...config,\n providers: filtered.map(p => ({ ...p, isActive: p.id === newActiveId })),\n activeProviderId: newActiveId,\n defaultModelSelection: newDefaultModelSelection,\n }\n}\n\nexport function activateProvider(config: GlobalConfig, providerId: string): GlobalConfig {\n const provider = config.providers?.find(p => p.id === providerId)\n if (!provider) return config\n \n return {\n ...config,\n providers: (config.providers ?? []).map(p => ({ ...p, isActive: p.id === providerId })),\n activeProviderId: providerId,\n }\n}\n\n// ============================================================================\n// Legacy mergeConfigs (for backwards compatibility during transition)\n// ============================================================================\n\n/**\n * @deprecated Use provider-based config instead\n */\nexport function mergeConfigs(...configs: Array<Partial<OldGlobalConfig>>): OldGlobalConfig {\n const result = configs.reduce((acc, curr) => {\n if (curr.llm) {\n acc.llm = { ...acc.llm, ...curr.llm }\n }\n if (curr.server) {\n acc.server = { ...acc.server, ...curr.server }\n }\n if (curr.logging) {\n acc.logging = { ...acc.logging, ...curr.logging }\n }\n return acc\n }, {\n llm: { url: 'http://localhost:8000/v1', model: 'auto', backend: 'auto' as const, maxContext: 200000, disableThinking: false },\n server: { port: 10369, host: '127.0.0.1', openBrowser: true },\n logging: { level: 'info' as const },\n })\n return oldConfigSchema.parse(result)\n}\n"],"mappings":"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","names":["providers"]}
|