@kmugalkhod/lightcode 0.1.2 → 0.2.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/LICENSE +21 -0
- package/README.md +10 -0
- package/cli.js +1350 -503
- package/lightcode.cjs +41 -26
- package/package.json +4 -2
- package/server.js +917 -181
package/server.js
CHANGED
|
@@ -17,6 +17,19 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
17
17
|
var __require = import.meta.require;
|
|
18
18
|
|
|
19
19
|
// packages/shared/src/data-dir.ts
|
|
20
|
+
import { homedir, platform } from "os";
|
|
21
|
+
import path from "path";
|
|
22
|
+
function getLightcodeDataDir(env = process.env) {
|
|
23
|
+
const configuredHome = env.LIGHTCODE_HOME?.trim();
|
|
24
|
+
if (configuredHome) {
|
|
25
|
+
return path.resolve(configuredHome);
|
|
26
|
+
}
|
|
27
|
+
if (platform() === "win32") {
|
|
28
|
+
const appData = env.APPDATA || env.LOCALAPPDATA;
|
|
29
|
+
return appData ? path.join(appData, "lightcode") : path.join(homedir(), "AppData", "Roaming", "lightcode");
|
|
30
|
+
}
|
|
31
|
+
return path.join(homedir(), ".lightcode");
|
|
32
|
+
}
|
|
20
33
|
var init_data_dir = () => {};
|
|
21
34
|
|
|
22
35
|
// packages/shared/src/error-utils.ts
|
|
@@ -31,6 +44,14 @@ function getErrorMessage(error, fallback = "Unknown error") {
|
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
// packages/shared/src/logger.ts
|
|
47
|
+
import {
|
|
48
|
+
appendFileSync,
|
|
49
|
+
existsSync,
|
|
50
|
+
mkdirSync,
|
|
51
|
+
readdirSync,
|
|
52
|
+
unlinkSync
|
|
53
|
+
} from "fs";
|
|
54
|
+
import path2 from "path";
|
|
34
55
|
function resolveConfiguredLevel() {
|
|
35
56
|
const raw = process.env.LIGHTCODE_LOG_LEVEL?.trim().toLowerCase();
|
|
36
57
|
if (raw === "debug" || raw === "info" || raw === "warn" || raw === "error") {
|
|
@@ -38,6 +59,49 @@ function resolveConfiguredLevel() {
|
|
|
38
59
|
}
|
|
39
60
|
return "info";
|
|
40
61
|
}
|
|
62
|
+
function getLogDirectory(env = process.env) {
|
|
63
|
+
return path2.join(getLightcodeDataDir(env), "logs");
|
|
64
|
+
}
|
|
65
|
+
function currentDateStamp() {
|
|
66
|
+
return new Date().toISOString().slice(0, 10);
|
|
67
|
+
}
|
|
68
|
+
function pruneOldLogFiles(directory) {
|
|
69
|
+
try {
|
|
70
|
+
const logFiles = readdirSync(directory).filter((name) => logFilePattern.test(name)).sort();
|
|
71
|
+
for (const name of logFiles.slice(0, -maxLogFilesKept)) {
|
|
72
|
+
unlinkSync(path2.join(directory, name));
|
|
73
|
+
}
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
function enableFileLogSink(env = process.env) {
|
|
77
|
+
try {
|
|
78
|
+
const directory = getLogDirectory(env);
|
|
79
|
+
mkdirSync(directory, { recursive: true });
|
|
80
|
+
fileSinkPrefix = directory;
|
|
81
|
+
pruneOldLogFiles(directory);
|
|
82
|
+
return directory;
|
|
83
|
+
} catch {
|
|
84
|
+
fileSinkPrefix = null;
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function writeToFileSink(line) {
|
|
89
|
+
if (!fileSinkPrefix) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const date = currentDateStamp();
|
|
94
|
+
if (!fileSinkState || fileSinkState.date !== date) {
|
|
95
|
+
fileSinkState = {
|
|
96
|
+
date,
|
|
97
|
+
filePath: path2.join(fileSinkPrefix, `server-${date}.log`)
|
|
98
|
+
};
|
|
99
|
+
pruneOldLogFiles(fileSinkPrefix);
|
|
100
|
+
}
|
|
101
|
+
appendFileSync(fileSinkState.filePath, `${line}
|
|
102
|
+
`);
|
|
103
|
+
} catch {}
|
|
104
|
+
}
|
|
41
105
|
function createLogger(name) {
|
|
42
106
|
const write = (level, event, fields) => {
|
|
43
107
|
if (levelOrder[level] < levelOrder[resolveConfiguredLevel()]) {
|
|
@@ -52,6 +116,7 @@ function createLogger(name) {
|
|
|
52
116
|
});
|
|
53
117
|
process.stderr.write(`${line}
|
|
54
118
|
`);
|
|
119
|
+
writeToFileSink(line);
|
|
55
120
|
};
|
|
56
121
|
return {
|
|
57
122
|
debug: (event, fields) => write("debug", event, fields),
|
|
@@ -60,14 +125,16 @@ function createLogger(name) {
|
|
|
60
125
|
error: (event, fields) => write("error", event, fields)
|
|
61
126
|
};
|
|
62
127
|
}
|
|
63
|
-
var levelOrder;
|
|
128
|
+
var levelOrder, maxLogFilesKept = 5, logFilePattern, fileSinkPrefix = null, fileSinkState = null;
|
|
64
129
|
var init_logger = __esm(() => {
|
|
130
|
+
init_data_dir();
|
|
65
131
|
levelOrder = {
|
|
66
132
|
debug: 10,
|
|
67
133
|
info: 20,
|
|
68
134
|
warn: 30,
|
|
69
135
|
error: 40
|
|
70
136
|
};
|
|
137
|
+
logFilePattern = /^server-\d{4}-\d{2}-\d{2}\.log$/;
|
|
71
138
|
});
|
|
72
139
|
// packages/shared/src/index.ts
|
|
73
140
|
function createGreeting(target) {
|
|
@@ -1486,6 +1553,29 @@ var init_agent_tools = __esm(() => {
|
|
|
1486
1553
|
});
|
|
1487
1554
|
});
|
|
1488
1555
|
|
|
1556
|
+
// packages/ai/src/chat-error.ts
|
|
1557
|
+
function formatChatStreamError(info) {
|
|
1558
|
+
const payload = {
|
|
1559
|
+
...info,
|
|
1560
|
+
message: info.message.slice(0, maxTransportMessageLength)
|
|
1561
|
+
};
|
|
1562
|
+
return `${chatStreamErrorPrefix}${JSON.stringify(payload)}`;
|
|
1563
|
+
}
|
|
1564
|
+
var chatStreamErrorPrefix = "LIGHTCODE_ERROR:", maxTransportMessageLength = 500, chatErrorKinds;
|
|
1565
|
+
var init_chat_error = __esm(() => {
|
|
1566
|
+
chatErrorKinds = new Set([
|
|
1567
|
+
"auth",
|
|
1568
|
+
"billing",
|
|
1569
|
+
"rate_limit",
|
|
1570
|
+
"invalid_request",
|
|
1571
|
+
"context_overflow",
|
|
1572
|
+
"provider_unavailable",
|
|
1573
|
+
"network",
|
|
1574
|
+
"aborted",
|
|
1575
|
+
"unknown"
|
|
1576
|
+
]);
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1489
1579
|
// packages/ai/src/context/context-state.ts
|
|
1490
1580
|
import { z as z22 } from "zod";
|
|
1491
1581
|
var contextStateTierSchema, sessionContextStateSchema;
|
|
@@ -1811,7 +1901,9 @@ var init_coding_agent_prompt = __esm(() => {
|
|
|
1811
1901
|
init_coding_agent_modes();
|
|
1812
1902
|
defaultCodingAgentSystemPrompt = "You are a basic coding agent. Use tools for filesystem and codebase tasks instead of guessing. " + "Respect the user's intent, explain changes clearly, and prefer incremental, auditable actions. " + "You can only interact with files under this working directory: {cwd}. " + "Use glob_search for path discovery, grep for text search, structured git tools for repository inspection, and bash only when dedicated tools are not enough. " + "Use todo_write to keep multi-step implementation work visible and current. " + "For risky or uncertain operations, inspect context first and be explicit about assumptions. " + "While working, emit brief progress notes in natural language before major tool actions and after important findings. " + `Keep them short, human, and concrete. Vary wording naturally and avoid repetitive templates or rigid labels.
|
|
1813
1903
|
|
|
1814
|
-
` + "You are an agent \u2014 keep working until the user's request is fully resolved before ending your turn. " + "Never stop with a partial answer. If you say you will do something, do it in the same turn by calling a tool. " + "Use todo_write to track multi-step work and do not finish while any todo is pending or in_progress. " +
|
|
1904
|
+
` + "You are an agent \u2014 keep working until the user's request is fully resolved before ending your turn. " + "Never stop with a partial answer. If you say you will do something, do it in the same turn by calling a tool. " + "Use todo_write to track multi-step work and do not finish while any todo is pending or in_progress. " + `Only end your turn when the task is complete or you are blocked on input that only the user can provide.
|
|
1905
|
+
|
|
1906
|
+
` + "Long-running processes: never run dev servers, watchers, or other non-exiting commands as plain foreground bash \u2014 " + "the bash tool enforces a timeout and the command will be killed. Start them detached with output redirected to a " + 'log file (e.g. on Windows: start /B cmd /c "npm run dev > dev.log 2>&1"; on Unix: nohup npm run dev > dev.log 2>&1 &), ' + "wait briefly, then verify by reading the log file or with a bounded probe like curl --max-time 5. " + "Dev servers do not always get their default port \u2014 read the actual port from the startup log instead of assuming it. " + "Never kill processes or free ports you did not open yourself; other tools (including this agent's own backend) may be using them.";
|
|
1815
1907
|
toolCallingDisciplineAddendum = "Invoke tools only through the function-calling interface. " + "Never write tool calls as XML or plain text such as <tool_call>, <function_call>, <invoke>, or bare JSON in your message. " + "Tool arguments must be valid JSON matching the tool's schema. " + "Call one tool at a time and wait for its result before deciding the next step.";
|
|
1816
1908
|
});
|
|
1817
1909
|
|
|
@@ -2456,6 +2548,7 @@ function createCodingAgent({
|
|
|
2456
2548
|
promptOverride,
|
|
2457
2549
|
maxOutputTokens = 16384,
|
|
2458
2550
|
maxSteps = 30,
|
|
2551
|
+
maxRetries = 5,
|
|
2459
2552
|
providerOptions,
|
|
2460
2553
|
includeToolDiscipline = false
|
|
2461
2554
|
}) {
|
|
@@ -2465,6 +2558,7 @@ function createCodingAgent({
|
|
|
2465
2558
|
tools,
|
|
2466
2559
|
stopWhen: stepCountIs(maxSteps),
|
|
2467
2560
|
maxOutputTokens,
|
|
2561
|
+
maxRetries,
|
|
2468
2562
|
providerOptions,
|
|
2469
2563
|
experimental_repairToolCall: repairCodingAgentToolCall,
|
|
2470
2564
|
callOptionsSchema: codingAgentCallOptionsSchema,
|
|
@@ -2518,20 +2612,20 @@ var init_coding_agent = __esm(() => {
|
|
|
2518
2612
|
});
|
|
2519
2613
|
|
|
2520
2614
|
// packages/ai/src/config/credentials.ts
|
|
2521
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2615
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
2522
2616
|
import os from "os";
|
|
2523
|
-
import
|
|
2617
|
+
import path3 from "path";
|
|
2524
2618
|
import { z as z25 } from "zod";
|
|
2525
2619
|
function getCredentialsPath(env = process.env) {
|
|
2526
2620
|
const configuredPath = env.LIGHTCODE_CREDENTIALS?.trim();
|
|
2527
2621
|
if (configuredPath) {
|
|
2528
|
-
return
|
|
2622
|
+
return path3.resolve(configuredPath);
|
|
2529
2623
|
}
|
|
2530
|
-
return
|
|
2624
|
+
return path3.join(os.homedir(), ".lightcode", "credentials.json");
|
|
2531
2625
|
}
|
|
2532
2626
|
function readStoredCredentials(env = process.env) {
|
|
2533
2627
|
const credentialsPath = getCredentialsPath(env);
|
|
2534
|
-
if (!
|
|
2628
|
+
if (!existsSync2(credentialsPath)) {
|
|
2535
2629
|
return {};
|
|
2536
2630
|
}
|
|
2537
2631
|
try {
|
|
@@ -2912,11 +3006,11 @@ function normalizeReadPathKey(input) {
|
|
|
2912
3006
|
if (!isRecord3(input)) {
|
|
2913
3007
|
return null;
|
|
2914
3008
|
}
|
|
2915
|
-
const
|
|
2916
|
-
if (typeof
|
|
3009
|
+
const path4 = input.path;
|
|
3010
|
+
if (typeof path4 !== "string" || !path4.trim()) {
|
|
2917
3011
|
return null;
|
|
2918
3012
|
}
|
|
2919
|
-
return
|
|
3013
|
+
return path4.trim().replaceAll("\\", "/").toLowerCase();
|
|
2920
3014
|
}
|
|
2921
3015
|
function getPartState(part) {
|
|
2922
3016
|
const state = isRecord3(part) ? Reflect.get(part, "state") : undefined;
|
|
@@ -3119,18 +3213,18 @@ var init_context = __esm(() => {
|
|
|
3119
3213
|
});
|
|
3120
3214
|
|
|
3121
3215
|
// packages/ai/src/config/lightcode-config.ts
|
|
3122
|
-
import { existsSync as
|
|
3216
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3123
3217
|
import os2 from "os";
|
|
3124
|
-
import
|
|
3218
|
+
import path4 from "path";
|
|
3125
3219
|
import { z as z28 } from "zod";
|
|
3126
3220
|
function getDefaultUserConfigPath(env) {
|
|
3127
3221
|
if (env.LIGHTCODE_USER_CONFIG) {
|
|
3128
3222
|
return env.LIGHTCODE_USER_CONFIG;
|
|
3129
3223
|
}
|
|
3130
|
-
return
|
|
3224
|
+
return path4.join(os2.homedir(), ".lightcode", "settings.json");
|
|
3131
3225
|
}
|
|
3132
3226
|
function readConfigFile(scope, configPath) {
|
|
3133
|
-
if (!
|
|
3227
|
+
if (!existsSync3(configPath)) {
|
|
3134
3228
|
return {
|
|
3135
3229
|
config: {},
|
|
3136
3230
|
file: {
|
|
@@ -3221,6 +3315,7 @@ function readEnvConfig(env) {
|
|
|
3221
3315
|
permissionMode: env.LIGHTCODE_PERMISSION_MODE,
|
|
3222
3316
|
maxOutputTokens: parseNumberEnv(env.LIGHTCODE_MAX_OUTPUT_TOKENS, "LIGHTCODE_MAX_OUTPUT_TOKENS"),
|
|
3223
3317
|
maxSteps: parseNumberEnv(env.LIGHTCODE_MAX_STEPS, "LIGHTCODE_MAX_STEPS"),
|
|
3318
|
+
maxRetries: parseNumberEnv(env.LIGHTCODE_MAX_RETRIES, "LIGHTCODE_MAX_RETRIES"),
|
|
3224
3319
|
context: Object.keys(compactContextConfig).length > 0 ? compactContextConfig : undefined
|
|
3225
3320
|
};
|
|
3226
3321
|
const compactConfig = Object.fromEntries(Object.entries(rawConfig).filter(([, value]) => value !== undefined));
|
|
@@ -3248,10 +3343,30 @@ function mergeConfig(...configs) {
|
|
|
3248
3343
|
allowedTools: config2.allowedTools ?? mergedConfig.allowedTools
|
|
3249
3344
|
}), {});
|
|
3250
3345
|
}
|
|
3346
|
+
function updateUserSettings(update, env = process.env) {
|
|
3347
|
+
const configPath = getDefaultUserConfigPath(env);
|
|
3348
|
+
const existing = readConfigFile("user", configPath).config;
|
|
3349
|
+
const merged = { ...existing };
|
|
3350
|
+
for (const [key, value] of Object.entries(update)) {
|
|
3351
|
+
if (value === undefined || value === null) {
|
|
3352
|
+
delete merged[key];
|
|
3353
|
+
} else {
|
|
3354
|
+
Reflect.set(merged, key, value);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
const parsed = lightcodeConfigSchema.safeParse(merged);
|
|
3358
|
+
if (!parsed.success) {
|
|
3359
|
+
throw new LightcodeConfigError(`Invalid settings update: ${parsed.error.message}`);
|
|
3360
|
+
}
|
|
3361
|
+
mkdirSync3(path4.dirname(configPath), { recursive: true });
|
|
3362
|
+
writeFileSync2(configPath, `${JSON.stringify(parsed.data, null, 2)}
|
|
3363
|
+
`, "utf8");
|
|
3364
|
+
return { path: configPath, config: parsed.data };
|
|
3365
|
+
}
|
|
3251
3366
|
function loadLightcodeConfig({
|
|
3252
3367
|
cwd = process.cwd(),
|
|
3253
3368
|
env = process.env,
|
|
3254
|
-
projectConfigPath =
|
|
3369
|
+
projectConfigPath = path4.join(cwd, ".lightcode", "settings.json"),
|
|
3255
3370
|
userConfigPath = getDefaultUserConfigPath(env)
|
|
3256
3371
|
} = {}) {
|
|
3257
3372
|
const userConfig = readConfigFile("user", userConfigPath);
|
|
@@ -3302,7 +3417,7 @@ var init_lightcode_config = __esm(() => {
|
|
|
3302
3417
|
enabled: true,
|
|
3303
3418
|
maxAutoContinues: 50,
|
|
3304
3419
|
maxErrorRetries: 5,
|
|
3305
|
-
stallTimeoutSeconds:
|
|
3420
|
+
stallTimeoutSeconds: 180
|
|
3306
3421
|
};
|
|
3307
3422
|
lightcodeConfigSchema = z28.object({
|
|
3308
3423
|
provider: lightcodeProviderSchema.optional(),
|
|
@@ -3317,6 +3432,7 @@ var init_lightcode_config = __esm(() => {
|
|
|
3317
3432
|
context: contextOptimizerConfigSchema.optional(),
|
|
3318
3433
|
maxOutputTokens: z28.number().int().min(1).max(200000).optional(),
|
|
3319
3434
|
maxSteps: z28.number().int().min(1).max(100).optional(),
|
|
3435
|
+
maxRetries: z28.number().int().min(0).max(10).optional(),
|
|
3320
3436
|
autoContinue: autoContinueConfigSchema.optional()
|
|
3321
3437
|
}).strict();
|
|
3322
3438
|
lightcodeConfigDefaults = {
|
|
@@ -3325,6 +3441,7 @@ var init_lightcode_config = __esm(() => {
|
|
|
3325
3441
|
context: defaultContextOptimizerConfig,
|
|
3326
3442
|
maxOutputTokens: 32768,
|
|
3327
3443
|
maxSteps: 30,
|
|
3444
|
+
maxRetries: 5,
|
|
3328
3445
|
autoContinue: defaultAutoContinueConfig
|
|
3329
3446
|
};
|
|
3330
3447
|
loadedConfigFileSchema = z28.object({
|
|
@@ -3339,6 +3456,7 @@ var init_lightcode_config = __esm(() => {
|
|
|
3339
3456
|
context: resolvedContextOptimizerConfigSchema,
|
|
3340
3457
|
maxOutputTokens: z28.number().int().min(1).max(200000),
|
|
3341
3458
|
maxSteps: z28.number().int().min(1).max(100),
|
|
3459
|
+
maxRetries: z28.number().int().min(0).max(10),
|
|
3342
3460
|
autoContinue: resolvedAutoContinueConfigSchema
|
|
3343
3461
|
});
|
|
3344
3462
|
lightcodeConfigLoadResultSchema = z28.object({
|
|
@@ -4227,27 +4345,27 @@ var init_schema16 = __esm(() => {
|
|
|
4227
4345
|
});
|
|
4228
4346
|
|
|
4229
4347
|
// packages/ai/src/plugins/runtime.ts
|
|
4230
|
-
import { existsSync as
|
|
4348
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
|
|
4231
4349
|
import os3 from "os";
|
|
4232
|
-
import
|
|
4350
|
+
import path5 from "path";
|
|
4233
4351
|
function pluginRoots(cwd) {
|
|
4234
4352
|
return [
|
|
4235
|
-
|
|
4236
|
-
|
|
4353
|
+
path5.join(cwd, ".lightcode", "plugins"),
|
|
4354
|
+
path5.join(os3.homedir(), ".lightcode", "plugins")
|
|
4237
4355
|
];
|
|
4238
4356
|
}
|
|
4239
4357
|
function listPlugins({ cwd = process.cwd() } = {}) {
|
|
4240
4358
|
const plugins = [];
|
|
4241
4359
|
for (const root of pluginRoots(cwd)) {
|
|
4242
|
-
if (!
|
|
4360
|
+
if (!existsSync4(root)) {
|
|
4243
4361
|
continue;
|
|
4244
4362
|
}
|
|
4245
|
-
for (const entry of
|
|
4363
|
+
for (const entry of readdirSync2(root, { withFileTypes: true })) {
|
|
4246
4364
|
if (!entry.isDirectory()) {
|
|
4247
4365
|
continue;
|
|
4248
4366
|
}
|
|
4249
|
-
const manifestPath =
|
|
4250
|
-
if (!
|
|
4367
|
+
const manifestPath = path5.join(root, entry.name, "plugin.json");
|
|
4368
|
+
if (!existsSync4(manifestPath)) {
|
|
4251
4369
|
continue;
|
|
4252
4370
|
}
|
|
4253
4371
|
const manifest = pluginManifestSchema.parse(JSON.parse(readFileSync3(manifestPath, "utf8")));
|
|
@@ -4269,14 +4387,14 @@ var init_runtime2 = __esm(() => {
|
|
|
4269
4387
|
});
|
|
4270
4388
|
|
|
4271
4389
|
// packages/ai/src/skills/runtime.ts
|
|
4272
|
-
import { existsSync as
|
|
4390
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
4273
4391
|
import os4 from "os";
|
|
4274
|
-
import
|
|
4392
|
+
import path6 from "path";
|
|
4275
4393
|
function defaultSkillRoots(cwd) {
|
|
4276
4394
|
return [
|
|
4277
|
-
{ source: "project", path:
|
|
4278
|
-
{ source: "project", path:
|
|
4279
|
-
{ source: "user", path:
|
|
4395
|
+
{ source: "project", path: path6.join(cwd, ".lightcode", "skills") },
|
|
4396
|
+
{ source: "project", path: path6.join(cwd, ".agents", "skills") },
|
|
4397
|
+
{ source: "user", path: path6.join(os4.homedir(), ".lightcode", "skills") }
|
|
4280
4398
|
];
|
|
4281
4399
|
}
|
|
4282
4400
|
function normalizeSkillName(value) {
|
|
@@ -4298,7 +4416,7 @@ function parseBasicFrontmatter(content) {
|
|
|
4298
4416
|
]));
|
|
4299
4417
|
}
|
|
4300
4418
|
function findSkillFiles(root) {
|
|
4301
|
-
if (!
|
|
4419
|
+
if (!existsSync5(root.path)) {
|
|
4302
4420
|
return [];
|
|
4303
4421
|
}
|
|
4304
4422
|
const files = [];
|
|
@@ -4306,8 +4424,8 @@ function findSkillFiles(root) {
|
|
|
4306
4424
|
if (depth > 4) {
|
|
4307
4425
|
return;
|
|
4308
4426
|
}
|
|
4309
|
-
for (const entry of
|
|
4310
|
-
const entryPath =
|
|
4427
|
+
for (const entry of readdirSync3(directory)) {
|
|
4428
|
+
const entryPath = path6.join(directory, entry);
|
|
4311
4429
|
const stat = statSync(entryPath);
|
|
4312
4430
|
if (stat.isDirectory()) {
|
|
4313
4431
|
visit(entryPath, depth + 1);
|
|
@@ -4328,7 +4446,7 @@ function listSkills({
|
|
|
4328
4446
|
for (const skillPath of findSkillFiles(root)) {
|
|
4329
4447
|
const content = readFileSync4(skillPath, "utf8");
|
|
4330
4448
|
const frontmatter = parseBasicFrontmatter(content);
|
|
4331
|
-
const fallbackName =
|
|
4449
|
+
const fallbackName = path6.basename(path6.dirname(skillPath));
|
|
4332
4450
|
const name = normalizeSkillName(frontmatter.name ?? fallbackName);
|
|
4333
4451
|
if (!name || seen.has(name)) {
|
|
4334
4452
|
continue;
|
|
@@ -4403,6 +4521,7 @@ var init_subagent_schemas = __esm(() => {
|
|
|
4403
4521
|
// packages/ai/src/index.ts
|
|
4404
4522
|
var init_src2 = __esm(() => {
|
|
4405
4523
|
init_agent_tools();
|
|
4524
|
+
init_chat_error();
|
|
4406
4525
|
init_chat_interaction_schemas();
|
|
4407
4526
|
init_plan_block();
|
|
4408
4527
|
init_chat_schemas();
|
|
@@ -4433,20 +4552,20 @@ var init_src2 = __esm(() => {
|
|
|
4433
4552
|
|
|
4434
4553
|
// packages/db/src/local-database.ts
|
|
4435
4554
|
import { Database } from "bun:sqlite";
|
|
4436
|
-
import { existsSync as
|
|
4555
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
4437
4556
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
4438
4557
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
4439
|
-
import { homedir, platform } from "os";
|
|
4558
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
4440
4559
|
function getDefaultLightcodeDataDir() {
|
|
4441
4560
|
const configuredHome = process.env.LIGHTCODE_HOME?.trim();
|
|
4442
4561
|
if (configuredHome) {
|
|
4443
4562
|
return resolve(configuredHome);
|
|
4444
4563
|
}
|
|
4445
|
-
if (
|
|
4564
|
+
if (platform2() === "win32") {
|
|
4446
4565
|
const appData = process.env.APPDATA || process.env.LOCALAPPDATA;
|
|
4447
|
-
return appData ? join(appData, appDirectoryName) : join(
|
|
4566
|
+
return appData ? join(appData, appDirectoryName) : join(homedir2(), "AppData", "Roaming", appDirectoryName);
|
|
4448
4567
|
}
|
|
4449
|
-
return join(
|
|
4568
|
+
return join(homedir2(), `.${appDirectoryName}`);
|
|
4450
4569
|
}
|
|
4451
4570
|
function getDefaultDatabasePath() {
|
|
4452
4571
|
const configuredPath = process.env.LIGHTCODE_DATABASE_PATH?.trim();
|
|
@@ -4456,11 +4575,9 @@ function getDefaultDatabaseUrl() {
|
|
|
4456
4575
|
return pathToFileURL(getDefaultDatabasePath()).href;
|
|
4457
4576
|
}
|
|
4458
4577
|
function resolveDatabaseUrl() {
|
|
4459
|
-
const configuredUrl = process.env.
|
|
4460
|
-
const supportedConfiguredUrl = configuredUrl
|
|
4461
|
-
|
|
4462
|
-
process.env.DATABASE_URL = databaseUrl;
|
|
4463
|
-
return databaseUrl;
|
|
4578
|
+
const configuredUrl = process.env.LIGHTCODE_DATABASE_URL?.trim();
|
|
4579
|
+
const supportedConfiguredUrl = configuredUrl && (configuredUrl.startsWith("file:") || configuredUrl.startsWith("libsql:") || configuredUrl.startsWith("http://") || configuredUrl.startsWith("https://")) ? configuredUrl : null;
|
|
4580
|
+
return supportedConfiguredUrl ?? getDefaultDatabaseUrl();
|
|
4464
4581
|
}
|
|
4465
4582
|
function filePathFromDatabaseUrl(databaseUrl) {
|
|
4466
4583
|
if (!databaseUrl.startsWith("file:")) {
|
|
@@ -4474,8 +4591,8 @@ function filePathFromDatabaseUrl(databaseUrl) {
|
|
|
4474
4591
|
}
|
|
4475
4592
|
function ensureParentDirectory(filePath) {
|
|
4476
4593
|
const parentDirectory = dirname(filePath);
|
|
4477
|
-
if (!
|
|
4478
|
-
|
|
4594
|
+
if (!existsSync6(parentDirectory)) {
|
|
4595
|
+
mkdirSync4(parentDirectory, { recursive: true });
|
|
4479
4596
|
}
|
|
4480
4597
|
}
|
|
4481
4598
|
function initializeLocalDatabase(databaseUrl) {
|
|
@@ -4611,14 +4728,10 @@ var init_local_database = () => {};
|
|
|
4611
4728
|
// apps/server/src/lib/chat-interaction-store.ts
|
|
4612
4729
|
import { Database as Database2 } from "bun:sqlite";
|
|
4613
4730
|
function getDatabase() {
|
|
4614
|
-
if (!
|
|
4731
|
+
if (!_db) {
|
|
4615
4732
|
const dbUrl = resolveDatabaseUrl();
|
|
4616
4733
|
initializeLocalDatabase(dbUrl);
|
|
4617
|
-
|
|
4618
|
-
}
|
|
4619
|
-
if (!_db) {
|
|
4620
|
-
const dbUrl = process.env.DATABASE_URL ?? getDefaultDatabasePath();
|
|
4621
|
-
const dbPath = filePathFromDatabaseUrl(dbUrl) ?? (dbUrl.startsWith("file:") ? dbUrl.slice(5) : dbUrl);
|
|
4734
|
+
const dbPath = filePathFromDatabaseUrl(dbUrl) ?? getDefaultDatabasePath();
|
|
4622
4735
|
_db = new Database2(dbPath);
|
|
4623
4736
|
}
|
|
4624
4737
|
return _db;
|
|
@@ -4769,7 +4882,7 @@ async function summarizePendingChatInteractions({
|
|
|
4769
4882
|
recoverable: total
|
|
4770
4883
|
};
|
|
4771
4884
|
}
|
|
4772
|
-
var ChatInteractionNotFoundError, _db = null
|
|
4885
|
+
var ChatInteractionNotFoundError, _db = null;
|
|
4773
4886
|
var init_chat_interaction_store = __esm(() => {
|
|
4774
4887
|
init_src2();
|
|
4775
4888
|
init_local_database();
|
|
@@ -4786,6 +4899,28 @@ var init_chat_interaction_store = __esm(() => {
|
|
|
4786
4899
|
});
|
|
4787
4900
|
|
|
4788
4901
|
// apps/server/src/lib/chat-observability.ts
|
|
4902
|
+
import { APICallError, RetryError } from "ai";
|
|
4903
|
+
function chatFailureClassForErrorKind(kind) {
|
|
4904
|
+
switch (kind) {
|
|
4905
|
+
case "auth":
|
|
4906
|
+
return "provider_auth";
|
|
4907
|
+
case "billing":
|
|
4908
|
+
return "provider_billing_quota";
|
|
4909
|
+
case "rate_limit":
|
|
4910
|
+
return "provider_rate_limit";
|
|
4911
|
+
case "invalid_request":
|
|
4912
|
+
return "provider_invalid_request";
|
|
4913
|
+
case "context_overflow":
|
|
4914
|
+
return "context_overflow";
|
|
4915
|
+
case "provider_unavailable":
|
|
4916
|
+
return "provider_unavailable";
|
|
4917
|
+
case "network":
|
|
4918
|
+
case "aborted":
|
|
4919
|
+
return "timeout_disconnect";
|
|
4920
|
+
default:
|
|
4921
|
+
return "provider_unknown";
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4789
4924
|
function normalizeErrorMessage(message) {
|
|
4790
4925
|
return message.toLowerCase();
|
|
4791
4926
|
}
|
|
@@ -4797,6 +4932,76 @@ function isProviderBillingOrQuotaError(error) {
|
|
|
4797
4932
|
const message = normalizeErrorMessage(getErrorMessage(error));
|
|
4798
4933
|
return message.includes("credit balance") || message.includes("insufficient credit") || message.includes("insufficient funds") || message.includes("out of credits") || message.includes("quota") || message.includes("billing") || message.includes("payment required");
|
|
4799
4934
|
}
|
|
4935
|
+
function isContextOverflowMessage(message) {
|
|
4936
|
+
return contextOverflowPattern.test(message);
|
|
4937
|
+
}
|
|
4938
|
+
function isAbortError(error) {
|
|
4939
|
+
return error instanceof Error && error.name === "AbortError" || normalizeErrorMessage(getErrorMessage(error)).includes("aborted");
|
|
4940
|
+
}
|
|
4941
|
+
function classifyByStatusCode(statusCode, message) {
|
|
4942
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
4943
|
+
return { kind: "auth", retryable: false };
|
|
4944
|
+
}
|
|
4945
|
+
if (statusCode === 402) {
|
|
4946
|
+
return { kind: "billing", retryable: false };
|
|
4947
|
+
}
|
|
4948
|
+
if (statusCode === 429) {
|
|
4949
|
+
return { kind: "rate_limit", retryable: true };
|
|
4950
|
+
}
|
|
4951
|
+
if (statusCode === 400 || statusCode === 422 || statusCode === 413) {
|
|
4952
|
+
if (isContextOverflowMessage(message)) {
|
|
4953
|
+
return { kind: "context_overflow", retryable: true };
|
|
4954
|
+
}
|
|
4955
|
+
if (isProviderBillingOrQuotaError(message)) {
|
|
4956
|
+
return { kind: "billing", retryable: false };
|
|
4957
|
+
}
|
|
4958
|
+
return { kind: "invalid_request", retryable: false };
|
|
4959
|
+
}
|
|
4960
|
+
if (retryableStatusCodes.has(statusCode) || statusCode >= 500) {
|
|
4961
|
+
return { kind: "provider_unavailable", retryable: true };
|
|
4962
|
+
}
|
|
4963
|
+
return { kind: "unknown", retryable: false };
|
|
4964
|
+
}
|
|
4965
|
+
function classifyChatError(error) {
|
|
4966
|
+
if (RetryError.isInstance(error) && error.lastError) {
|
|
4967
|
+
return classifyChatError(error.lastError);
|
|
4968
|
+
}
|
|
4969
|
+
if (APICallError.isInstance(error)) {
|
|
4970
|
+
const responseBody = error.responseBody ?? "";
|
|
4971
|
+
const message2 = responseBody.length > 0 ? responseBody : error.message;
|
|
4972
|
+
if (typeof error.statusCode === "number") {
|
|
4973
|
+
const { kind, retryable } = classifyByStatusCode(error.statusCode, message2);
|
|
4974
|
+
return {
|
|
4975
|
+
kind,
|
|
4976
|
+
statusCode: error.statusCode,
|
|
4977
|
+
retryable,
|
|
4978
|
+
message: message2
|
|
4979
|
+
};
|
|
4980
|
+
}
|
|
4981
|
+
return {
|
|
4982
|
+
kind: error.isRetryable ? "network" : "unknown",
|
|
4983
|
+
retryable: error.isRetryable,
|
|
4984
|
+
message: message2
|
|
4985
|
+
};
|
|
4986
|
+
}
|
|
4987
|
+
const message = getErrorMessage(error);
|
|
4988
|
+
if (isAbortError(error)) {
|
|
4989
|
+
return { kind: "aborted", retryable: false, message };
|
|
4990
|
+
}
|
|
4991
|
+
if (isContextOverflowMessage(message)) {
|
|
4992
|
+
return { kind: "context_overflow", retryable: true, message };
|
|
4993
|
+
}
|
|
4994
|
+
if (isProviderBillingOrQuotaError(error)) {
|
|
4995
|
+
return { kind: "billing", retryable: false, message };
|
|
4996
|
+
}
|
|
4997
|
+
if (isProviderSchemaRejectionError(error)) {
|
|
4998
|
+
return { kind: "invalid_request", retryable: false, message };
|
|
4999
|
+
}
|
|
5000
|
+
if (isDisconnectOrTimeoutError(error)) {
|
|
5001
|
+
return { kind: "network", retryable: true, message };
|
|
5002
|
+
}
|
|
5003
|
+
return { kind: "unknown", retryable: false, message };
|
|
5004
|
+
}
|
|
4800
5005
|
function isDisconnectOrTimeoutError(error) {
|
|
4801
5006
|
const message = normalizeErrorMessage(getErrorMessage(error));
|
|
4802
5007
|
return message.includes("timed out") || message.includes("timeout") || message.includes("socket") || message.includes("connection") || message.includes("aborted") || message.includes("broken pipe") || message.includes("econnreset");
|
|
@@ -4822,6 +5027,22 @@ function incrementChatFailureCounter(failureClass, context2 = {}) {
|
|
|
4822
5027
|
...context2
|
|
4823
5028
|
});
|
|
4824
5029
|
}
|
|
5030
|
+
function logChatStreamErrorEvent({
|
|
5031
|
+
sessionId,
|
|
5032
|
+
phase,
|
|
5033
|
+
classified,
|
|
5034
|
+
error
|
|
5035
|
+
}) {
|
|
5036
|
+
logger2.error("chat_stream_error", {
|
|
5037
|
+
sessionId,
|
|
5038
|
+
phase,
|
|
5039
|
+
kind: classified.kind,
|
|
5040
|
+
statusCode: classified.statusCode,
|
|
5041
|
+
retryable: classified.retryable,
|
|
5042
|
+
message: classified.message,
|
|
5043
|
+
rawMessage: getErrorMessage(error)
|
|
5044
|
+
});
|
|
5045
|
+
}
|
|
4825
5046
|
function logChatDisconnectEvent({
|
|
4826
5047
|
sessionId,
|
|
4827
5048
|
phase,
|
|
@@ -4834,17 +5055,25 @@ function logChatDisconnectEvent({
|
|
|
4834
5055
|
message: getErrorMessage(error)
|
|
4835
5056
|
});
|
|
4836
5057
|
}
|
|
4837
|
-
var chatFailureCounters, logger2;
|
|
5058
|
+
var chatFailureCounters, logger2, retryableStatusCodes, contextOverflowPattern;
|
|
4838
5059
|
var init_chat_observability = __esm(() => {
|
|
4839
5060
|
init_src();
|
|
4840
5061
|
chatFailureCounters = {
|
|
4841
5062
|
provider_schema_rejection: 0,
|
|
4842
5063
|
provider_billing_quota: 0,
|
|
5064
|
+
provider_auth: 0,
|
|
5065
|
+
provider_rate_limit: 0,
|
|
5066
|
+
provider_invalid_request: 0,
|
|
5067
|
+
provider_unavailable: 0,
|
|
5068
|
+
context_overflow: 0,
|
|
5069
|
+
provider_unknown: 0,
|
|
4843
5070
|
timeout_disconnect: 0,
|
|
4844
5071
|
stale_finish_skip: 0,
|
|
4845
5072
|
db_unique_conflict: 0
|
|
4846
5073
|
};
|
|
4847
5074
|
logger2 = createLogger("chat");
|
|
5075
|
+
retryableStatusCodes = new Set([408, 409, 429, 500, 502, 503, 504]);
|
|
5076
|
+
contextOverflowPattern = /(context window|context length|context_length|maximum context|prompt is too long|input is too long|too many tokens|exceeds? the (?:maximum|model'?s) (?:context|token)|max(?:imum)? input tokens)/i;
|
|
4848
5077
|
});
|
|
4849
5078
|
|
|
4850
5079
|
// packages/db/generated/prisma/internal/class.ts
|
|
@@ -5057,7 +5286,7 @@ var init_prismaNamespace = __esm(() => {
|
|
|
5057
5286
|
var init_enums = () => {};
|
|
5058
5287
|
|
|
5059
5288
|
// packages/db/generated/prisma/client.ts
|
|
5060
|
-
import * as
|
|
5289
|
+
import * as path7 from "path";
|
|
5061
5290
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5062
5291
|
var PrismaClient;
|
|
5063
5292
|
var init_client = __esm(() => {
|
|
@@ -5065,7 +5294,7 @@ var init_client = __esm(() => {
|
|
|
5065
5294
|
init_prismaNamespace();
|
|
5066
5295
|
init_enums();
|
|
5067
5296
|
init_enums();
|
|
5068
|
-
globalThis["__dirname"] =
|
|
5297
|
+
globalThis["__dirname"] = path7.dirname(fileURLToPath2(import.meta.url));
|
|
5069
5298
|
PrismaClient = getPrismaClientClass();
|
|
5070
5299
|
});
|
|
5071
5300
|
|
|
@@ -5643,28 +5872,28 @@ function mergeFinishedMessagesIntoFullHistory({
|
|
|
5643
5872
|
}
|
|
5644
5873
|
|
|
5645
5874
|
// packages/ai/src/common/resolve-within-workspace.ts
|
|
5646
|
-
import
|
|
5875
|
+
import path8 from "path";
|
|
5647
5876
|
import {
|
|
5648
|
-
existsSync as
|
|
5877
|
+
existsSync as existsSync7,
|
|
5649
5878
|
lstatSync,
|
|
5650
5879
|
realpathSync,
|
|
5651
5880
|
statSync as statSync2
|
|
5652
5881
|
} from "fs";
|
|
5653
5882
|
function stripTrailingSeparator(value) {
|
|
5654
|
-
const parsed =
|
|
5883
|
+
const parsed = path8.parse(value);
|
|
5655
5884
|
if (value === parsed.root) {
|
|
5656
5885
|
return value;
|
|
5657
5886
|
}
|
|
5658
5887
|
return value.replace(/[\\/]+$/, "");
|
|
5659
5888
|
}
|
|
5660
5889
|
function normalizeForComparison(value) {
|
|
5661
|
-
const normalized = stripTrailingSeparator(
|
|
5890
|
+
const normalized = stripTrailingSeparator(path8.resolve(value));
|
|
5662
5891
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
5663
5892
|
}
|
|
5664
5893
|
function isWithinWorkspace(absolutePath, context2) {
|
|
5665
5894
|
const workspace = normalizeForComparison(context2.root);
|
|
5666
5895
|
const target = normalizeForComparison(absolutePath);
|
|
5667
|
-
return target === workspace || target.startsWith(`${workspace}${
|
|
5896
|
+
return target === workspace || target.startsWith(`${workspace}${path8.sep}`);
|
|
5668
5897
|
}
|
|
5669
5898
|
function assertWithinWorkspace(absolutePath, context2, input) {
|
|
5670
5899
|
if (!isWithinWorkspace(absolutePath, context2)) {
|
|
@@ -5679,17 +5908,17 @@ function getExistingRealPath(absolutePath) {
|
|
|
5679
5908
|
}
|
|
5680
5909
|
}
|
|
5681
5910
|
function findNearestExistingParent(absolutePath, context2, input) {
|
|
5682
|
-
let currentPath =
|
|
5911
|
+
let currentPath = path8.dirname(absolutePath);
|
|
5683
5912
|
while (true) {
|
|
5684
5913
|
assertWithinWorkspace(currentPath, context2, input);
|
|
5685
|
-
if (
|
|
5914
|
+
if (existsSync7(currentPath)) {
|
|
5686
5915
|
const realParentPath = getExistingRealPath(currentPath);
|
|
5687
5916
|
if (!realParentPath) {
|
|
5688
5917
|
throw new Error(`Unable to resolve existing parent for path: ${input}`);
|
|
5689
5918
|
}
|
|
5690
5919
|
return realParentPath;
|
|
5691
5920
|
}
|
|
5692
|
-
const parentPath =
|
|
5921
|
+
const parentPath = path8.dirname(currentPath);
|
|
5693
5922
|
if (parentPath === currentPath) {
|
|
5694
5923
|
throw new Error(`Unable to find an existing workspace parent for path: ${input}`);
|
|
5695
5924
|
}
|
|
@@ -5709,7 +5938,7 @@ function getDefaultWorkspaceContext() {
|
|
|
5709
5938
|
}
|
|
5710
5939
|
function resolveWithinWorkspace(input, options = {}) {
|
|
5711
5940
|
const context2 = options.context ?? defaultWorkspaceContext;
|
|
5712
|
-
const resolved =
|
|
5941
|
+
const resolved = path8.resolve(context2.root, input);
|
|
5713
5942
|
assertWithinWorkspace(resolved, context2, input);
|
|
5714
5943
|
const realPath = getExistingRealPath(resolved);
|
|
5715
5944
|
if (realPath) {
|
|
@@ -5852,7 +6081,7 @@ var init_runtime11 = __esm(() => {
|
|
|
5852
6081
|
|
|
5853
6082
|
// packages/ai/src/todo-write/runtime.ts
|
|
5854
6083
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
5855
|
-
import
|
|
6084
|
+
import path9 from "path";
|
|
5856
6085
|
import { z as z33 } from "zod";
|
|
5857
6086
|
function safeTodoSessionId(sessionId) {
|
|
5858
6087
|
const trimmed = sessionId?.trim();
|
|
@@ -5862,7 +6091,7 @@ function safeTodoSessionId(sessionId) {
|
|
|
5862
6091
|
return trimmed.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120);
|
|
5863
6092
|
}
|
|
5864
6093
|
function getTodoFilePath(sessionId, workspaceContext) {
|
|
5865
|
-
return resolveWithinWorkspace(
|
|
6094
|
+
return resolveWithinWorkspace(path9.join(".lightcode", "todos", `${safeTodoSessionId(sessionId)}.json`), {
|
|
5866
6095
|
context: workspaceContext,
|
|
5867
6096
|
allowMissing: true
|
|
5868
6097
|
});
|
|
@@ -6066,6 +6295,7 @@ ${transcript}`
|
|
|
6066
6295
|
system: COMPACTION_SYSTEM_PROMPT,
|
|
6067
6296
|
prompt,
|
|
6068
6297
|
maxOutputTokens: COMPACTION_MAX_OUTPUT_TOKENS,
|
|
6298
|
+
maxRetries: 2,
|
|
6069
6299
|
abortSignal: AbortSignal.timeout(COMPACTION_TIMEOUT_MS)
|
|
6070
6300
|
});
|
|
6071
6301
|
const text = result.text.trim();
|
|
@@ -6114,7 +6344,7 @@ function composeSummary({
|
|
|
6114
6344
|
`).trim();
|
|
6115
6345
|
const bodyBudget = Math.max(MIN_SUMMARY_BODY_CHARS, summaryMaxChars - (pinnedText ? pinnedText.length + 2 : 0));
|
|
6116
6346
|
const compressedBody = compressSummary(body, bodyBudget);
|
|
6117
|
-
return [compressedBody, pinnedText].filter(Boolean).join(`
|
|
6347
|
+
return [compressedBody, pinnedText, compactionDirectResumeInstruction].filter(Boolean).join(`
|
|
6118
6348
|
|
|
6119
6349
|
`);
|
|
6120
6350
|
}
|
|
@@ -6192,13 +6422,221 @@ Unfinished work, known follow-ups, and the immediate next step.
|
|
|
6192
6422
|
## Tools and commands that mattered
|
|
6193
6423
|
Important commands run and their relevant outcomes (test results, errors, etc.).
|
|
6194
6424
|
|
|
6195
|
-
Rules: be specific (real file paths, function names, error messages); never invent details; omit pleasantries; if a "Previously compacted context" section is provided, fold its still-relevant facts into your summary instead of referencing it
|
|
6425
|
+
Rules: be specific (real file paths, function names, error messages); never invent details; omit pleasantries; if a "Previously compacted context" section is provided, fold its still-relevant facts into your summary instead of referencing it.`, compactionDirectResumeInstruction;
|
|
6196
6426
|
var init_context_compaction = __esm(() => {
|
|
6197
6427
|
init_src2();
|
|
6198
6428
|
init_runtime17();
|
|
6199
6429
|
init_src();
|
|
6200
6430
|
init_context_state_store();
|
|
6201
6431
|
logger3 = createLogger("context-compaction");
|
|
6432
|
+
compactionDirectResumeInstruction = "Continue the task from where it left off using the summary above. " + "Do not ask for confirmation, do not re-state the plan, and do not repeat completed work.";
|
|
6433
|
+
});
|
|
6434
|
+
|
|
6435
|
+
// apps/server/src/lib/overflow-recovery.ts
|
|
6436
|
+
function noteContextOverflow(sessionId) {
|
|
6437
|
+
overflowRecoveryRounds.set(sessionId, (overflowRecoveryRounds.get(sessionId) ?? 0) + 1);
|
|
6438
|
+
}
|
|
6439
|
+
function clearContextOverflowRounds(sessionId) {
|
|
6440
|
+
overflowRecoveryRounds.delete(sessionId);
|
|
6441
|
+
}
|
|
6442
|
+
function getOverflowPreserveRecentMessages(sessionId) {
|
|
6443
|
+
const rounds = overflowRecoveryRounds.get(sessionId) ?? 0;
|
|
6444
|
+
if (rounds === 0) {
|
|
6445
|
+
return null;
|
|
6446
|
+
}
|
|
6447
|
+
return overflowPreserveSchedule[Math.min(rounds, overflowPreserveSchedule.length) - 1];
|
|
6448
|
+
}
|
|
6449
|
+
var overflowPreserveSchedule, overflowRecoveryRounds;
|
|
6450
|
+
var init_overflow_recovery = __esm(() => {
|
|
6451
|
+
overflowPreserveSchedule = [4, 2, 1, 0];
|
|
6452
|
+
overflowRecoveryRounds = new Map;
|
|
6453
|
+
});
|
|
6454
|
+
|
|
6455
|
+
// apps/server/src/lib/sse-heartbeat.ts
|
|
6456
|
+
function endsWithEventBoundary(chunk) {
|
|
6457
|
+
const length = chunk.length;
|
|
6458
|
+
if (length >= 2 && chunk[length - 1] === 10 && chunk[length - 2] === 10) {
|
|
6459
|
+
return true;
|
|
6460
|
+
}
|
|
6461
|
+
return length >= 4 && chunk[length - 1] === 10 && chunk[length - 2] === 13 && chunk[length - 3] === 10 && chunk[length - 4] === 13;
|
|
6462
|
+
}
|
|
6463
|
+
function withSseHeartbeat(response, intervalMs = defaultHeartbeatIntervalMs) {
|
|
6464
|
+
const body = response.body;
|
|
6465
|
+
if (!body) {
|
|
6466
|
+
return response;
|
|
6467
|
+
}
|
|
6468
|
+
let atEventBoundary = true;
|
|
6469
|
+
let heartbeatTimer = null;
|
|
6470
|
+
let lastChunkAt = Date.now();
|
|
6471
|
+
const stopHeartbeat = () => {
|
|
6472
|
+
if (heartbeatTimer) {
|
|
6473
|
+
clearInterval(heartbeatTimer);
|
|
6474
|
+
heartbeatTimer = null;
|
|
6475
|
+
}
|
|
6476
|
+
};
|
|
6477
|
+
const transform = new TransformStream({
|
|
6478
|
+
start(controller) {
|
|
6479
|
+
heartbeatTimer = setInterval(() => {
|
|
6480
|
+
if (!atEventBoundary || Date.now() - lastChunkAt < intervalMs) {
|
|
6481
|
+
return;
|
|
6482
|
+
}
|
|
6483
|
+
try {
|
|
6484
|
+
controller.enqueue(heartbeatFrame);
|
|
6485
|
+
lastChunkAt = Date.now();
|
|
6486
|
+
} catch {
|
|
6487
|
+
stopHeartbeat();
|
|
6488
|
+
}
|
|
6489
|
+
}, Math.min(intervalMs, 5000));
|
|
6490
|
+
},
|
|
6491
|
+
transform(chunk, controller) {
|
|
6492
|
+
atEventBoundary = endsWithEventBoundary(chunk);
|
|
6493
|
+
lastChunkAt = Date.now();
|
|
6494
|
+
controller.enqueue(chunk);
|
|
6495
|
+
},
|
|
6496
|
+
flush() {
|
|
6497
|
+
stopHeartbeat();
|
|
6498
|
+
}
|
|
6499
|
+
});
|
|
6500
|
+
const heartbeatBody = body.pipeThrough(transform);
|
|
6501
|
+
return new Response(heartbeatBody, {
|
|
6502
|
+
status: response.status,
|
|
6503
|
+
statusText: response.statusText,
|
|
6504
|
+
headers: response.headers
|
|
6505
|
+
});
|
|
6506
|
+
}
|
|
6507
|
+
var encoder, heartbeatFrame, defaultHeartbeatIntervalMs = 15000;
|
|
6508
|
+
var init_sse_heartbeat = __esm(() => {
|
|
6509
|
+
encoder = new TextEncoder;
|
|
6510
|
+
heartbeatFrame = encoder.encode(`: heartbeat
|
|
6511
|
+
|
|
6512
|
+
`);
|
|
6513
|
+
});
|
|
6514
|
+
|
|
6515
|
+
// apps/server/src/lib/openrouter-models.ts
|
|
6516
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
6517
|
+
import path10 from "path";
|
|
6518
|
+
import { z as z34 } from "zod";
|
|
6519
|
+
function getCacheFilePath() {
|
|
6520
|
+
return path10.join(getLightcodeDataDir(), "cache", "openrouter-models.json");
|
|
6521
|
+
}
|
|
6522
|
+
function readCache() {
|
|
6523
|
+
try {
|
|
6524
|
+
const cachePath = getCacheFilePath();
|
|
6525
|
+
if (!existsSync8(cachePath)) {
|
|
6526
|
+
return null;
|
|
6527
|
+
}
|
|
6528
|
+
return modelsCacheSchema.parse(JSON.parse(readFileSync5(cachePath, "utf8")));
|
|
6529
|
+
} catch {
|
|
6530
|
+
return null;
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
function writeCache(cache) {
|
|
6534
|
+
try {
|
|
6535
|
+
const cachePath = getCacheFilePath();
|
|
6536
|
+
mkdirSync5(path10.dirname(cachePath), { recursive: true });
|
|
6537
|
+
writeFileSync3(cachePath, JSON.stringify(cache), "utf8");
|
|
6538
|
+
} catch (error) {
|
|
6539
|
+
logger4.warn("openrouter_models_cache_write_failed", {
|
|
6540
|
+
error: getErrorMessage(error)
|
|
6541
|
+
});
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
function summarizeRawModel(raw2) {
|
|
6545
|
+
if (typeof raw2 !== "object" || raw2 === null) {
|
|
6546
|
+
return null;
|
|
6547
|
+
}
|
|
6548
|
+
const id = Reflect.get(raw2, "id");
|
|
6549
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
6550
|
+
return null;
|
|
6551
|
+
}
|
|
6552
|
+
const name = Reflect.get(raw2, "name");
|
|
6553
|
+
const contextLength = Reflect.get(raw2, "context_length");
|
|
6554
|
+
const supportedParameters = Reflect.get(raw2, "supported_parameters");
|
|
6555
|
+
const parameterList = Array.isArray(supportedParameters) ? supportedParameters.filter((entry) => typeof entry === "string") : [];
|
|
6556
|
+
const pricingRaw = Reflect.get(raw2, "pricing");
|
|
6557
|
+
const pricing = typeof pricingRaw === "object" && pricingRaw !== null ? {
|
|
6558
|
+
prompt: typeof Reflect.get(pricingRaw, "prompt") === "string" ? Reflect.get(pricingRaw, "prompt") : null,
|
|
6559
|
+
completion: typeof Reflect.get(pricingRaw, "completion") === "string" ? Reflect.get(pricingRaw, "completion") : null
|
|
6560
|
+
} : null;
|
|
6561
|
+
return {
|
|
6562
|
+
id,
|
|
6563
|
+
name: typeof name === "string" && name.length > 0 ? name : id,
|
|
6564
|
+
contextLength: typeof contextLength === "number" && contextLength > 0 ? Math.round(contextLength) : null,
|
|
6565
|
+
supportsTools: parameterList.includes("tools"),
|
|
6566
|
+
supportsReasoning: parameterList.includes("reasoning"),
|
|
6567
|
+
pricing
|
|
6568
|
+
};
|
|
6569
|
+
}
|
|
6570
|
+
async function fetchModelsFromApi() {
|
|
6571
|
+
const response = await fetch(openRouterModelsUrl, {
|
|
6572
|
+
signal: AbortSignal.timeout(fetchTimeoutMs)
|
|
6573
|
+
});
|
|
6574
|
+
if (!response.ok) {
|
|
6575
|
+
throw new Error(`OpenRouter models API returned HTTP ${response.status}.`);
|
|
6576
|
+
}
|
|
6577
|
+
const payload = await response.json();
|
|
6578
|
+
const models = (payload.data ?? []).map(summarizeRawModel).filter((model) => model !== null);
|
|
6579
|
+
if (models.length === 0) {
|
|
6580
|
+
throw new Error("OpenRouter models API returned no models.");
|
|
6581
|
+
}
|
|
6582
|
+
return models;
|
|
6583
|
+
}
|
|
6584
|
+
async function listOpenRouterModels({
|
|
6585
|
+
forceRefresh = false
|
|
6586
|
+
} = {}) {
|
|
6587
|
+
const cache = readCache();
|
|
6588
|
+
if (!forceRefresh && cache && Date.now() - cache.fetchedAt < cacheTtlMs) {
|
|
6589
|
+
return { models: cache.models, fromCache: true };
|
|
6590
|
+
}
|
|
6591
|
+
try {
|
|
6592
|
+
const models = await fetchModelsFromApi();
|
|
6593
|
+
writeCache({ fetchedAt: Date.now(), models });
|
|
6594
|
+
return { models, fromCache: false };
|
|
6595
|
+
} catch (error) {
|
|
6596
|
+
logger4.warn("openrouter_models_fetch_failed", {
|
|
6597
|
+
error: getErrorMessage(error)
|
|
6598
|
+
});
|
|
6599
|
+
if (cache) {
|
|
6600
|
+
return { models: cache.models, fromCache: true };
|
|
6601
|
+
}
|
|
6602
|
+
throw error;
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
async function getOpenRouterModelCapabilities(modelId) {
|
|
6606
|
+
try {
|
|
6607
|
+
const { models } = await listOpenRouterModels();
|
|
6608
|
+
const model = models.find((entry) => entry.id === modelId);
|
|
6609
|
+
if (!model) {
|
|
6610
|
+
return;
|
|
6611
|
+
}
|
|
6612
|
+
return {
|
|
6613
|
+
supportsNativeTools: model.supportsTools,
|
|
6614
|
+
contextLength: model.contextLength ?? undefined
|
|
6615
|
+
};
|
|
6616
|
+
} catch {
|
|
6617
|
+
return;
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
var logger4, openRouterModelsUrl = "https://openrouter.ai/api/v1/models", cacheTtlMs, fetchTimeoutMs = 15000, openRouterModelSummarySchema, modelsCacheSchema;
|
|
6621
|
+
var init_openrouter_models = __esm(() => {
|
|
6622
|
+
init_src();
|
|
6623
|
+
logger4 = createLogger("openrouter-models");
|
|
6624
|
+
cacheTtlMs = 24 * 60 * 60 * 1000;
|
|
6625
|
+
openRouterModelSummarySchema = z34.object({
|
|
6626
|
+
id: z34.string(),
|
|
6627
|
+
name: z34.string(),
|
|
6628
|
+
contextLength: z34.number().int().positive().nullable(),
|
|
6629
|
+
supportsTools: z34.boolean(),
|
|
6630
|
+
supportsReasoning: z34.boolean(),
|
|
6631
|
+
pricing: z34.object({
|
|
6632
|
+
prompt: z34.string().nullable(),
|
|
6633
|
+
completion: z34.string().nullable()
|
|
6634
|
+
}).nullable()
|
|
6635
|
+
});
|
|
6636
|
+
modelsCacheSchema = z34.object({
|
|
6637
|
+
fetchedAt: z34.number(),
|
|
6638
|
+
models: z34.array(openRouterModelSummarySchema)
|
|
6639
|
+
});
|
|
6202
6640
|
});
|
|
6203
6641
|
|
|
6204
6642
|
// apps/server/src/lib/provider-registry.ts
|
|
@@ -6265,7 +6703,8 @@ function resolveAnthropicModel({
|
|
|
6265
6703
|
function resolveOpenAITransportModel({
|
|
6266
6704
|
config: config5,
|
|
6267
6705
|
env,
|
|
6268
|
-
storedCredentials
|
|
6706
|
+
storedCredentials,
|
|
6707
|
+
capabilities
|
|
6269
6708
|
}) {
|
|
6270
6709
|
const isOpenCodeZen = config5.provider === "opencode-zen";
|
|
6271
6710
|
const isOpenRouter = config5.provider === "openrouter";
|
|
@@ -6302,7 +6741,7 @@ function resolveOpenAITransportModel({
|
|
|
6302
6741
|
baseUrl,
|
|
6303
6742
|
model: withXmlToolCallSupport(provider(configuredModel)),
|
|
6304
6743
|
needsToolCallDiscipline: true,
|
|
6305
|
-
contextWindow: getModelContextWindow(config5.provider, configuredModel),
|
|
6744
|
+
contextWindow: capabilities?.contextLength ?? getModelContextWindow(config5.provider, configuredModel),
|
|
6306
6745
|
missingCredentialHints: apiKey ? [] : isOpenCodeZen ? ["Set OPENCODE_API_KEY to call OpenCode Zen models."] : isOpenRouter ? ["Set OPENROUTER_API_KEY to call OpenRouter models."] : [
|
|
6307
6746
|
"Set LIGHTCODE_OPENAI_COMPATIBLE_API_KEY or OPENAI_API_KEY if your OpenAI-compatible endpoint requires authentication."
|
|
6308
6747
|
]
|
|
@@ -6310,11 +6749,17 @@ function resolveOpenAITransportModel({
|
|
|
6310
6749
|
}
|
|
6311
6750
|
function resolveConfiguredProviderModel({
|
|
6312
6751
|
config: config5,
|
|
6313
|
-
env = Bun.env
|
|
6752
|
+
env = Bun.env,
|
|
6753
|
+
capabilities
|
|
6314
6754
|
}) {
|
|
6315
6755
|
const storedCredentials = readStoredCredentials(env);
|
|
6316
6756
|
if (config5.provider === "openai-compatible" || config5.provider === "opencode-zen" || config5.provider === "openrouter") {
|
|
6317
|
-
return resolveOpenAITransportModel({
|
|
6757
|
+
return resolveOpenAITransportModel({
|
|
6758
|
+
config: config5,
|
|
6759
|
+
env,
|
|
6760
|
+
storedCredentials,
|
|
6761
|
+
capabilities
|
|
6762
|
+
});
|
|
6318
6763
|
}
|
|
6319
6764
|
return resolveAnthropicModel({ config: config5, env, storedCredentials });
|
|
6320
6765
|
}
|
|
@@ -6352,11 +6797,96 @@ var init_provider_registry = __esm(() => {
|
|
|
6352
6797
|
});
|
|
6353
6798
|
|
|
6354
6799
|
// apps/server/src/lib/runtime-config.ts
|
|
6355
|
-
var
|
|
6800
|
+
var exports_runtime_config = {};
|
|
6801
|
+
__export(exports_runtime_config, {
|
|
6802
|
+
resolvedProviderModel: () => resolvedProviderModel,
|
|
6803
|
+
lightcodeConfigResult: () => lightcodeConfigResult,
|
|
6804
|
+
initializeModelCapabilities: () => initializeModelCapabilities,
|
|
6805
|
+
configStatus: () => configStatus,
|
|
6806
|
+
codingAgent: () => codingAgent,
|
|
6807
|
+
chatModelId: () => chatModelId,
|
|
6808
|
+
applyModelSelection: () => applyModelSelection
|
|
6809
|
+
});
|
|
6810
|
+
function buildCodingAgent(config5, model) {
|
|
6811
|
+
return createCodingAgent({
|
|
6812
|
+
model: model.model,
|
|
6813
|
+
promptOverride: codingAgentPromptOverride,
|
|
6814
|
+
providerOptions: model.providerOptions,
|
|
6815
|
+
maxSteps: config5.maxSteps,
|
|
6816
|
+
maxRetries: config5.maxRetries,
|
|
6817
|
+
maxOutputTokens: config5.maxOutputTokens,
|
|
6818
|
+
includeToolDiscipline: model.needsToolCallDiscipline ?? false
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
6821
|
+
function applyModelSelection({
|
|
6822
|
+
provider,
|
|
6823
|
+
model,
|
|
6824
|
+
capabilities,
|
|
6825
|
+
persist = true
|
|
6826
|
+
}) {
|
|
6827
|
+
const nextConfig = {
|
|
6828
|
+
...lightcodeConfigResult.config,
|
|
6829
|
+
provider,
|
|
6830
|
+
model
|
|
6831
|
+
};
|
|
6832
|
+
const nextResolved = resolveConfiguredProviderModel({
|
|
6833
|
+
config: nextConfig,
|
|
6834
|
+
env: Bun.env,
|
|
6835
|
+
capabilities
|
|
6836
|
+
});
|
|
6837
|
+
const nextAgent = buildCodingAgent(nextConfig, nextResolved);
|
|
6838
|
+
if (persist) {
|
|
6839
|
+
const { path: path11 } = updateUserSettings({ provider, model }, Bun.env);
|
|
6840
|
+
logger5.info("model_selection_persisted", { path: path11, provider, model });
|
|
6841
|
+
}
|
|
6842
|
+
lightcodeConfigResult = {
|
|
6843
|
+
...lightcodeConfigResult,
|
|
6844
|
+
config: nextConfig
|
|
6845
|
+
};
|
|
6846
|
+
resolvedProviderModel = nextResolved;
|
|
6847
|
+
chatModelId = nextResolved.resolvedModelId;
|
|
6848
|
+
codingAgent = nextAgent;
|
|
6849
|
+
configStatus = createConfigStatus({
|
|
6850
|
+
config: nextConfig,
|
|
6851
|
+
loadedFiles: lightcodeConfigResult.loadedFiles,
|
|
6852
|
+
resolvedProviderModel: nextResolved
|
|
6853
|
+
});
|
|
6854
|
+
logger5.info("model_selection_applied", {
|
|
6855
|
+
provider,
|
|
6856
|
+
model,
|
|
6857
|
+
contextWindow: nextResolved.contextWindow,
|
|
6858
|
+
supportsNativeTools: capabilities?.supportsNativeTools ?? null
|
|
6859
|
+
});
|
|
6860
|
+
return configStatus;
|
|
6861
|
+
}
|
|
6862
|
+
async function initializeModelCapabilities() {
|
|
6863
|
+
if (lightcodeConfigResult.config.provider !== "openrouter") {
|
|
6864
|
+
return;
|
|
6865
|
+
}
|
|
6866
|
+
const modelId = lightcodeConfigResult.config.model;
|
|
6867
|
+
if (!modelId) {
|
|
6868
|
+
return;
|
|
6869
|
+
}
|
|
6870
|
+
const capabilities = await getOpenRouterModelCapabilities(modelId);
|
|
6871
|
+
if (!capabilities || chatModelId !== modelId) {
|
|
6872
|
+
return;
|
|
6873
|
+
}
|
|
6874
|
+
applyModelSelection({
|
|
6875
|
+
provider: "openrouter",
|
|
6876
|
+
model: modelId,
|
|
6877
|
+
capabilities,
|
|
6878
|
+
persist: false
|
|
6879
|
+
});
|
|
6880
|
+
}
|
|
6881
|
+
var logger5, codingAgentPromptOverride, lightcodeConfigResult, resolvedProviderModel, configStatus, chatModelId, codingAgent;
|
|
6356
6882
|
var init_runtime_config = __esm(() => {
|
|
6357
6883
|
init_src2();
|
|
6884
|
+
init_src();
|
|
6885
|
+
init_openrouter_models();
|
|
6358
6886
|
init_provider_registry();
|
|
6887
|
+
logger5 = createLogger("runtime-config");
|
|
6359
6888
|
assertProviderToolSchemaBudget();
|
|
6889
|
+
codingAgentPromptOverride = Bun.env.LIGHTCODE_CODING_AGENT_SYSTEM_PROMPT;
|
|
6360
6890
|
lightcodeConfigResult = loadLightcodeConfig({
|
|
6361
6891
|
cwd: process.cwd(),
|
|
6362
6892
|
env: Bun.env
|
|
@@ -6371,15 +6901,7 @@ var init_runtime_config = __esm(() => {
|
|
|
6371
6901
|
resolvedProviderModel
|
|
6372
6902
|
});
|
|
6373
6903
|
chatModelId = resolvedProviderModel.resolvedModelId;
|
|
6374
|
-
|
|
6375
|
-
codingAgent = createCodingAgent({
|
|
6376
|
-
model: resolvedProviderModel.model,
|
|
6377
|
-
promptOverride: codingAgentPromptOverride,
|
|
6378
|
-
providerOptions: resolvedProviderModel.providerOptions,
|
|
6379
|
-
maxSteps: lightcodeConfigResult.config.maxSteps,
|
|
6380
|
-
maxOutputTokens: lightcodeConfigResult.config.maxOutputTokens,
|
|
6381
|
-
includeToolDiscipline: resolvedProviderModel.needsToolCallDiscipline ?? false
|
|
6382
|
-
});
|
|
6904
|
+
codingAgent = buildCodingAgent(lightcodeConfigResult.config, resolvedProviderModel);
|
|
6383
6905
|
});
|
|
6384
6906
|
|
|
6385
6907
|
// apps/server/src/lib/session-auto-title.ts
|
|
@@ -6415,7 +6937,7 @@ Response: ${firstAssistantText.slice(0, AUTO_TITLE_INPUT_PREVIEW_CHARS)}`,
|
|
|
6415
6937
|
});
|
|
6416
6938
|
const title = sanitizeGeneratedTitle(result.text);
|
|
6417
6939
|
if (title && await applyGeneratedSessionTitle(sessionId, title)) {
|
|
6418
|
-
|
|
6940
|
+
logger6.info("session_auto_titled", { sessionId, title, model: chatModelId });
|
|
6419
6941
|
}
|
|
6420
6942
|
}
|
|
6421
6943
|
function maybeScheduleSessionAutoTitle({
|
|
@@ -6423,20 +6945,20 @@ function maybeScheduleSessionAutoTitle({
|
|
|
6423
6945
|
messages
|
|
6424
6946
|
}) {
|
|
6425
6947
|
generateSessionTitle({ sessionId, messages }).catch((error) => {
|
|
6426
|
-
|
|
6948
|
+
logger6.warn("session_auto_title_failed", {
|
|
6427
6949
|
sessionId,
|
|
6428
6950
|
error: getErrorMessage(error)
|
|
6429
6951
|
});
|
|
6430
6952
|
});
|
|
6431
6953
|
}
|
|
6432
|
-
var
|
|
6954
|
+
var logger6, AUTO_TITLE_TIMEOUT_MS = 15000, AUTO_TITLE_MAX_OUTPUT_TOKENS = 24, AUTO_TITLE_INPUT_PREVIEW_CHARS = 600;
|
|
6433
6955
|
var init_session_auto_title = __esm(() => {
|
|
6434
6956
|
init_src2();
|
|
6435
6957
|
init_src();
|
|
6436
6958
|
init_chat_store();
|
|
6437
6959
|
init_prisma_client();
|
|
6438
6960
|
init_runtime_config();
|
|
6439
|
-
|
|
6961
|
+
logger6 = createLogger("session-auto-title");
|
|
6440
6962
|
});
|
|
6441
6963
|
|
|
6442
6964
|
// apps/server/src/lib/chat-stream.ts
|
|
@@ -6485,7 +7007,7 @@ async function countPendingChatInteractions(sessionId) {
|
|
|
6485
7007
|
});
|
|
6486
7008
|
return pendingInteractions.interactions.length;
|
|
6487
7009
|
} catch (error) {
|
|
6488
|
-
|
|
7010
|
+
logger7.warn("context_compaction_pending_interactions_unavailable", {
|
|
6489
7011
|
sessionId,
|
|
6490
7012
|
error: getErrorMessage(error)
|
|
6491
7013
|
});
|
|
@@ -6496,7 +7018,7 @@ async function loadSessionContextStateSafe(sessionId) {
|
|
|
6496
7018
|
try {
|
|
6497
7019
|
return await getSessionContextState(sessionId);
|
|
6498
7020
|
} catch (error) {
|
|
6499
|
-
|
|
7021
|
+
logger7.warn("context_state_unavailable", {
|
|
6500
7022
|
sessionId,
|
|
6501
7023
|
error: getErrorMessage(error)
|
|
6502
7024
|
});
|
|
@@ -6545,7 +7067,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6545
7067
|
staleSkip: false
|
|
6546
7068
|
});
|
|
6547
7069
|
} catch (error) {
|
|
6548
|
-
|
|
7070
|
+
logger7.error("chat_pre_stream_persist_failed", {
|
|
6549
7071
|
sessionId,
|
|
6550
7072
|
error: getErrorMessage(error)
|
|
6551
7073
|
});
|
|
@@ -6585,7 +7107,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6585
7107
|
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
6586
7108
|
pendingInteractionCount
|
|
6587
7109
|
});
|
|
6588
|
-
|
|
7110
|
+
logger7.info("context_auto_compacted", {
|
|
6589
7111
|
sessionId,
|
|
6590
7112
|
tier: compaction.state.tier,
|
|
6591
7113
|
usedFallback: compaction.usedFallback,
|
|
@@ -6594,13 +7116,70 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6594
7116
|
estimatedTokensAfter: view.estimate.tokens
|
|
6595
7117
|
});
|
|
6596
7118
|
} catch (error) {
|
|
6597
|
-
|
|
7119
|
+
logger7.error("context_compaction_failed", {
|
|
7120
|
+
sessionId,
|
|
7121
|
+
error: getErrorMessage(error)
|
|
7122
|
+
});
|
|
7123
|
+
}
|
|
7124
|
+
}
|
|
7125
|
+
const overflowPreserve = getOverflowPreserveRecentMessages(sessionId);
|
|
7126
|
+
if (overflowPreserve !== null) {
|
|
7127
|
+
const overflowConfig = {
|
|
7128
|
+
...contextConfig,
|
|
7129
|
+
preserveRecentMessages: Math.min(overflowPreserve, contextConfig.preserveRecentMessages)
|
|
7130
|
+
};
|
|
7131
|
+
try {
|
|
7132
|
+
const overflowView = buildProviderView({
|
|
7133
|
+
messages: validatedMessages,
|
|
7134
|
+
contextState,
|
|
7135
|
+
config: overflowConfig,
|
|
7136
|
+
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
7137
|
+
pendingInteractionCount: 0
|
|
7138
|
+
});
|
|
7139
|
+
if (overflowView.coveredMessages.length > 0) {
|
|
7140
|
+
const compaction = await compactSessionContext({
|
|
7141
|
+
sessionId,
|
|
7142
|
+
coveredMessages: overflowView.coveredMessages,
|
|
7143
|
+
previousState: contextState,
|
|
7144
|
+
model: resolvedProviderModel.model,
|
|
7145
|
+
modelId: chatModelId,
|
|
7146
|
+
cwd,
|
|
7147
|
+
config: overflowConfig,
|
|
7148
|
+
estimatedTokens: overflowView.estimate.tokens
|
|
7149
|
+
});
|
|
7150
|
+
contextState = compaction.state;
|
|
7151
|
+
}
|
|
7152
|
+
view = buildProviderView({
|
|
7153
|
+
messages: validatedMessages,
|
|
7154
|
+
contextState,
|
|
7155
|
+
config: overflowConfig,
|
|
7156
|
+
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
7157
|
+
pendingInteractionCount: 0
|
|
7158
|
+
});
|
|
7159
|
+
logger7.info("context_overflow_recovery", {
|
|
7160
|
+
sessionId,
|
|
7161
|
+
preserveRecentMessages: overflowConfig.preserveRecentMessages,
|
|
7162
|
+
estimatedTokens: view.estimate.tokens,
|
|
7163
|
+
contextWindow: view.contextWindow,
|
|
7164
|
+
withinWindow: view.estimate.tokens < view.contextWindow,
|
|
7165
|
+
compactedMessages: view.coveredMessages.length
|
|
7166
|
+
});
|
|
7167
|
+
} catch (error) {
|
|
7168
|
+
logger7.error("context_overflow_recovery_failed", {
|
|
6598
7169
|
sessionId,
|
|
6599
7170
|
error: getErrorMessage(error)
|
|
6600
7171
|
});
|
|
6601
7172
|
}
|
|
6602
7173
|
}
|
|
6603
7174
|
const providerMessages = view.providerMessages;
|
|
7175
|
+
let overflowNotedThisRequest = false;
|
|
7176
|
+
const noteOverflowForSession = () => {
|
|
7177
|
+
if (overflowNotedThisRequest) {
|
|
7178
|
+
return;
|
|
7179
|
+
}
|
|
7180
|
+
overflowNotedThisRequest = true;
|
|
7181
|
+
noteContextOverflow(sessionId);
|
|
7182
|
+
};
|
|
6604
7183
|
const persistFinishedMessages = async ({
|
|
6605
7184
|
isAborted,
|
|
6606
7185
|
isContinuation,
|
|
@@ -6610,6 +7189,9 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6610
7189
|
if (isAborted) {
|
|
6611
7190
|
return;
|
|
6612
7191
|
}
|
|
7192
|
+
if (!overflowNotedThisRequest) {
|
|
7193
|
+
clearContextOverflowRounds(sessionId);
|
|
7194
|
+
}
|
|
6613
7195
|
const fullHistory = mergeFinishedMessagesIntoFullHistory({
|
|
6614
7196
|
fullMessages: validatedMessages,
|
|
6615
7197
|
providerMessageCount: providerMessages.length,
|
|
@@ -6619,14 +7201,14 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6619
7201
|
});
|
|
6620
7202
|
const normalizedMessages = removeTrailingEmptyAssistantMessages(fullHistory);
|
|
6621
7203
|
if (normalizedMessages.length !== fullHistory.length) {
|
|
6622
|
-
|
|
7204
|
+
logger7.warn("chat_finish_empty_assistant_skipped", { sessionId });
|
|
6623
7205
|
return;
|
|
6624
7206
|
}
|
|
6625
7207
|
const validatedFinishResult = await safeValidateUIMessages2({
|
|
6626
7208
|
messages: normalizedMessages
|
|
6627
7209
|
});
|
|
6628
7210
|
if (!validatedFinishResult.success) {
|
|
6629
|
-
|
|
7211
|
+
logger7.warn("chat_invalid_finish_payload", {
|
|
6630
7212
|
sessionId,
|
|
6631
7213
|
message: validatedFinishResult.error.message
|
|
6632
7214
|
});
|
|
@@ -6660,7 +7242,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6660
7242
|
messages: validatedFinishResult.data
|
|
6661
7243
|
});
|
|
6662
7244
|
} catch (error) {
|
|
6663
|
-
|
|
7245
|
+
logger7.error("chat_finish_persist_failed", {
|
|
6664
7246
|
sessionId,
|
|
6665
7247
|
error: getErrorMessage(error)
|
|
6666
7248
|
});
|
|
@@ -6692,7 +7274,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6692
7274
|
mode,
|
|
6693
7275
|
allowedTools
|
|
6694
7276
|
})) {
|
|
6695
|
-
|
|
7277
|
+
logger7.info("chat_fast_path", {
|
|
6696
7278
|
sessionId,
|
|
6697
7279
|
model: chatModelId
|
|
6698
7280
|
});
|
|
@@ -6701,6 +7283,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6701
7283
|
system: fastChatSystemPrompt,
|
|
6702
7284
|
messages: buildFastChatModelMessages(providerMessages),
|
|
6703
7285
|
maxOutputTokens: Math.min(lightcodeConfigResult.config.maxOutputTokens, fastChatMaxOutputTokens),
|
|
7286
|
+
maxRetries: lightcodeConfigResult.config.maxRetries,
|
|
6704
7287
|
providerOptions: resolvedProviderModel.providerOptions,
|
|
6705
7288
|
abortSignal: c.req.raw.signal
|
|
6706
7289
|
});
|
|
@@ -6719,50 +7302,46 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6719
7302
|
}
|
|
6720
7303
|
}
|
|
6721
7304
|
});
|
|
6722
|
-
return result.toUIMessageStreamResponse({
|
|
7305
|
+
return withSseHeartbeat(result.toUIMessageStreamResponse({
|
|
6723
7306
|
originalMessages: providerMessages,
|
|
6724
7307
|
generateMessageId: generateId3,
|
|
6725
7308
|
sendReasoning: false,
|
|
6726
7309
|
messageMetadata: buildUsageMessageMetadata,
|
|
6727
7310
|
onError: (error) => {
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
});
|
|
7311
|
+
const classified = classifyChatError(error);
|
|
7312
|
+
if (classified.kind === "context_overflow") {
|
|
7313
|
+
noteOverflowForSession();
|
|
6732
7314
|
}
|
|
6733
|
-
|
|
7315
|
+
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), { sessionId, statusCode: classified.statusCode });
|
|
7316
|
+
logChatStreamErrorEvent({
|
|
7317
|
+
sessionId,
|
|
7318
|
+
phase: "stream",
|
|
7319
|
+
classified,
|
|
7320
|
+
error
|
|
7321
|
+
});
|
|
7322
|
+
return formatChatStreamError(classified);
|
|
6734
7323
|
},
|
|
6735
7324
|
onFinish: persistFinishedMessages
|
|
6736
|
-
});
|
|
7325
|
+
}));
|
|
6737
7326
|
}
|
|
6738
7327
|
const mapStreamError = (error) => {
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
});
|
|
6743
|
-
return genericRecoverableMessage;
|
|
6744
|
-
}
|
|
6745
|
-
if (isProviderBillingOrQuotaError(error)) {
|
|
6746
|
-
incrementChatFailureCounter("provider_billing_quota", {
|
|
6747
|
-
sessionId
|
|
6748
|
-
});
|
|
6749
|
-
return providerBillingOrQuotaMessage;
|
|
6750
|
-
}
|
|
6751
|
-
if (isDisconnectOrTimeoutError(error)) {
|
|
6752
|
-
incrementChatFailureCounter("timeout_disconnect", {
|
|
6753
|
-
sessionId,
|
|
6754
|
-
phase: "stream"
|
|
6755
|
-
});
|
|
6756
|
-
logChatDisconnectEvent({
|
|
6757
|
-
sessionId,
|
|
6758
|
-
phase: "stream",
|
|
6759
|
-
error
|
|
6760
|
-
});
|
|
6761
|
-
return recoverableDisconnectMessage;
|
|
7328
|
+
const classified = classifyChatError(error);
|
|
7329
|
+
if (classified.kind === "context_overflow") {
|
|
7330
|
+
noteOverflowForSession();
|
|
6762
7331
|
}
|
|
6763
|
-
|
|
7332
|
+
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), {
|
|
7333
|
+
sessionId,
|
|
7334
|
+
statusCode: classified.statusCode
|
|
7335
|
+
});
|
|
7336
|
+
logChatStreamErrorEvent({
|
|
7337
|
+
sessionId,
|
|
7338
|
+
phase: "stream",
|
|
7339
|
+
classified,
|
|
7340
|
+
error
|
|
7341
|
+
});
|
|
7342
|
+
return formatChatStreamError(classified);
|
|
6764
7343
|
};
|
|
6765
|
-
return await createAgentUIStreamResponse({
|
|
7344
|
+
return withSseHeartbeat(await createAgentUIStreamResponse({
|
|
6766
7345
|
agent: codingAgent,
|
|
6767
7346
|
uiMessages: providerMessages,
|
|
6768
7347
|
options: {
|
|
@@ -6797,24 +7376,27 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6797
7376
|
},
|
|
6798
7377
|
onError: mapStreamError,
|
|
6799
7378
|
onFinish: persistFinishedMessages
|
|
6800
|
-
});
|
|
7379
|
+
}));
|
|
6801
7380
|
} catch (error) {
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
});
|
|
7381
|
+
const classified = classifyChatError(error);
|
|
7382
|
+
if (classified.kind === "context_overflow") {
|
|
7383
|
+
noteContextOverflow(sessionId);
|
|
6806
7384
|
}
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
7385
|
+
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), {
|
|
7386
|
+
sessionId,
|
|
7387
|
+
phase: "pre-stream",
|
|
7388
|
+
statusCode: classified.statusCode
|
|
7389
|
+
});
|
|
7390
|
+
logChatStreamErrorEvent({
|
|
7391
|
+
sessionId,
|
|
7392
|
+
phase: "pre-stream",
|
|
7393
|
+
classified,
|
|
7394
|
+
error
|
|
7395
|
+
});
|
|
7396
|
+
if (classified.kind === "billing") {
|
|
6811
7397
|
return c.json({ error: providerBillingOrQuotaMessage }, 402);
|
|
6812
7398
|
}
|
|
6813
|
-
if (
|
|
6814
|
-
incrementChatFailureCounter("timeout_disconnect", {
|
|
6815
|
-
sessionId,
|
|
6816
|
-
phase: "pre-stream"
|
|
6817
|
-
});
|
|
7399
|
+
if (classified.kind === "network" || classified.kind === "aborted") {
|
|
6818
7400
|
logChatDisconnectEvent({
|
|
6819
7401
|
sessionId,
|
|
6820
7402
|
phase: "pre-stream",
|
|
@@ -6823,12 +7405,12 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6823
7405
|
return c.json({ error: recoverableDisconnectMessage }, 503);
|
|
6824
7406
|
}
|
|
6825
7407
|
return c.json({
|
|
6826
|
-
error:
|
|
7408
|
+
error: formatChatStreamError(classified),
|
|
6827
7409
|
details: Bun.env.NODE_ENV === "production" ? undefined : getErrorMessage(error)
|
|
6828
7410
|
}, 500);
|
|
6829
7411
|
}
|
|
6830
7412
|
}
|
|
6831
|
-
var
|
|
7413
|
+
var logger7, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, fastChatRecentMessageCount = 6;
|
|
6832
7414
|
var init_chat_stream = __esm(() => {
|
|
6833
7415
|
init_src2();
|
|
6834
7416
|
init_src();
|
|
@@ -6836,10 +7418,12 @@ var init_chat_stream = __esm(() => {
|
|
|
6836
7418
|
init_chat_interaction_store();
|
|
6837
7419
|
init_chat_store();
|
|
6838
7420
|
init_context_compaction();
|
|
7421
|
+
init_overflow_recovery();
|
|
7422
|
+
init_sse_heartbeat();
|
|
6839
7423
|
init_context_state_store();
|
|
6840
7424
|
init_session_auto_title();
|
|
6841
7425
|
init_runtime_config();
|
|
6842
|
-
|
|
7426
|
+
logger7 = createLogger("chat-stream");
|
|
6843
7427
|
providerBillingOrQuotaMessage = "The configured model provider rejected this request due to billing or quota limits. " + "Update provider credits/quota and retry.";
|
|
6844
7428
|
fastChatSystemPrompt = "You are Lightcode's friendly coding assistant. For casual conversation, reply briefly and naturally. " + "If the user asks for coding work, say you can help and ask them what they want to change.";
|
|
6845
7429
|
});
|
|
@@ -6850,16 +7434,16 @@ function internalErrorResponse(c, {
|
|
|
6850
7434
|
message,
|
|
6851
7435
|
error
|
|
6852
7436
|
}) {
|
|
6853
|
-
|
|
7437
|
+
logger8.error(event, { error: getErrorMessage(error) });
|
|
6854
7438
|
return c.json({
|
|
6855
7439
|
error: message,
|
|
6856
7440
|
details: Bun.env.NODE_ENV === "production" ? undefined : getErrorMessage(error)
|
|
6857
7441
|
}, 500);
|
|
6858
7442
|
}
|
|
6859
|
-
var
|
|
7443
|
+
var logger8;
|
|
6860
7444
|
var init_route_helpers = __esm(() => {
|
|
6861
7445
|
init_src();
|
|
6862
|
-
|
|
7446
|
+
logger8 = createLogger("routes");
|
|
6863
7447
|
});
|
|
6864
7448
|
|
|
6865
7449
|
// apps/server/src/routes/chat-routes.ts
|
|
@@ -7141,6 +7725,59 @@ var init_chat_routes = __esm(() => {
|
|
|
7141
7725
|
});
|
|
7142
7726
|
});
|
|
7143
7727
|
|
|
7728
|
+
// apps/server/src/routes/config-routes.ts
|
|
7729
|
+
import { Hono as Hono2 } from "hono";
|
|
7730
|
+
import { z as z35 } from "zod";
|
|
7731
|
+
var selectModelRequestSchema, configRoutes;
|
|
7732
|
+
var init_config_routes = __esm(() => {
|
|
7733
|
+
init_src2();
|
|
7734
|
+
init_src();
|
|
7735
|
+
init_openrouter_models();
|
|
7736
|
+
init_runtime_config();
|
|
7737
|
+
selectModelRequestSchema = z35.object({
|
|
7738
|
+
provider: lightcodeProviderSchema,
|
|
7739
|
+
model: z35.string().min(1).max(240)
|
|
7740
|
+
});
|
|
7741
|
+
configRoutes = new Hono2().get("/status", (c) => c.json(configStatus)).get("/models", async (c) => {
|
|
7742
|
+
const provider = c.req.query("provider") ?? "openrouter";
|
|
7743
|
+
if (provider !== "openrouter") {
|
|
7744
|
+
return c.json({ error: `Model listing is not supported for provider "${provider}".` }, 400);
|
|
7745
|
+
}
|
|
7746
|
+
try {
|
|
7747
|
+
const { models, fromCache } = await listOpenRouterModels({
|
|
7748
|
+
forceRefresh: c.req.query("refresh") === "1"
|
|
7749
|
+
});
|
|
7750
|
+
return c.json({
|
|
7751
|
+
generatedAt: new Date().toISOString(),
|
|
7752
|
+
provider,
|
|
7753
|
+
fromCache,
|
|
7754
|
+
models
|
|
7755
|
+
});
|
|
7756
|
+
} catch (error) {
|
|
7757
|
+
return c.json({
|
|
7758
|
+
error: `Unable to load OpenRouter models: ${getErrorMessage(error)}`
|
|
7759
|
+
}, 502);
|
|
7760
|
+
}
|
|
7761
|
+
}).put("/model", async (c) => {
|
|
7762
|
+
const parsed = selectModelRequestSchema.safeParse(await c.req.json().catch(() => null));
|
|
7763
|
+
if (!parsed.success) {
|
|
7764
|
+
return c.json({ error: "Invalid model selection payload." }, 400);
|
|
7765
|
+
}
|
|
7766
|
+
const { provider, model } = parsed.data;
|
|
7767
|
+
try {
|
|
7768
|
+
const capabilities = provider === "openrouter" ? await getOpenRouterModelCapabilities(model) : undefined;
|
|
7769
|
+
const status = applyModelSelection({ provider, model, capabilities });
|
|
7770
|
+
return c.json({
|
|
7771
|
+
ok: true,
|
|
7772
|
+
configStatus: status,
|
|
7773
|
+
capabilities: capabilities ?? null
|
|
7774
|
+
});
|
|
7775
|
+
} catch (error) {
|
|
7776
|
+
return c.json({ error: `Unable to switch model: ${getErrorMessage(error)}` }, 400);
|
|
7777
|
+
}
|
|
7778
|
+
});
|
|
7779
|
+
});
|
|
7780
|
+
|
|
7144
7781
|
// apps/server/src/lib/extension-runtime.ts
|
|
7145
7782
|
function getExtensionRuntimeStatus() {
|
|
7146
7783
|
const skills = listSkills({ cwd: process.cwd() });
|
|
@@ -7171,8 +7808,9 @@ var init_extension_runtime = __esm(() => {
|
|
|
7171
7808
|
});
|
|
7172
7809
|
|
|
7173
7810
|
// apps/server/src/routes/diagnostics-routes.ts
|
|
7174
|
-
import { existsSync as
|
|
7175
|
-
import
|
|
7811
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
7812
|
+
import path11 from "path";
|
|
7813
|
+
import { Hono as Hono3 } from "hono";
|
|
7176
7814
|
function combineStatuses(statuses) {
|
|
7177
7815
|
if (statuses.includes("error")) {
|
|
7178
7816
|
return "error";
|
|
@@ -7305,8 +7943,8 @@ function checkWorkspace() {
|
|
|
7305
7943
|
return {
|
|
7306
7944
|
id: "workspace",
|
|
7307
7945
|
label: "Workspace",
|
|
7308
|
-
status:
|
|
7309
|
-
summary:
|
|
7946
|
+
status: existsSync9(cwd) ? "ok" : "error",
|
|
7947
|
+
summary: existsSync9(cwd) ? `Workspace is accessible: ${cwd}` : `Workspace is missing: ${cwd}`,
|
|
7310
7948
|
details: []
|
|
7311
7949
|
};
|
|
7312
7950
|
}
|
|
@@ -7427,7 +8065,83 @@ async function buildPermissionsPayload() {
|
|
|
7427
8065
|
notes
|
|
7428
8066
|
});
|
|
7429
8067
|
}
|
|
7430
|
-
|
|
8068
|
+
async function buildConnectivityPayload() {
|
|
8069
|
+
const baseUrl = resolvedProviderModel.baseUrl ?? (resolvedProviderModel.provider === "anthropic" ? "https://api.anthropic.com/v1" : null);
|
|
8070
|
+
const proxyEnvironment = {
|
|
8071
|
+
httpsProxy: Boolean(Bun.env.HTTPS_PROXY ?? Bun.env.https_proxy),
|
|
8072
|
+
httpProxy: Boolean(Bun.env.HTTP_PROXY ?? Bun.env.http_proxy),
|
|
8073
|
+
noProxy: Boolean(Bun.env.NO_PROXY ?? Bun.env.no_proxy),
|
|
8074
|
+
extraCaCerts: Boolean(Bun.env.NODE_EXTRA_CA_CERTS)
|
|
8075
|
+
};
|
|
8076
|
+
if (!baseUrl) {
|
|
8077
|
+
return {
|
|
8078
|
+
generatedAt: new Date().toISOString(),
|
|
8079
|
+
provider: resolvedProviderModel.provider,
|
|
8080
|
+
target: null,
|
|
8081
|
+
reachable: false,
|
|
8082
|
+
statusCode: null,
|
|
8083
|
+
latencyMs: null,
|
|
8084
|
+
error: "No provider base URL is configured.",
|
|
8085
|
+
proxyEnvironment
|
|
8086
|
+
};
|
|
8087
|
+
}
|
|
8088
|
+
const target = new URL("models", baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
8089
|
+
const startedAt = performance.now();
|
|
8090
|
+
try {
|
|
8091
|
+
const response = await fetch(target, {
|
|
8092
|
+
signal: AbortSignal.timeout(connectivityProbeTimeoutMs)
|
|
8093
|
+
});
|
|
8094
|
+
return {
|
|
8095
|
+
generatedAt: new Date().toISOString(),
|
|
8096
|
+
provider: resolvedProviderModel.provider,
|
|
8097
|
+
target: target.toString(),
|
|
8098
|
+
reachable: true,
|
|
8099
|
+
statusCode: response.status,
|
|
8100
|
+
latencyMs: Math.round(performance.now() - startedAt),
|
|
8101
|
+
error: null,
|
|
8102
|
+
proxyEnvironment
|
|
8103
|
+
};
|
|
8104
|
+
} catch (error) {
|
|
8105
|
+
return {
|
|
8106
|
+
generatedAt: new Date().toISOString(),
|
|
8107
|
+
provider: resolvedProviderModel.provider,
|
|
8108
|
+
target: target.toString(),
|
|
8109
|
+
reachable: false,
|
|
8110
|
+
statusCode: null,
|
|
8111
|
+
latencyMs: Math.round(performance.now() - startedAt),
|
|
8112
|
+
error: getErrorMessage(error),
|
|
8113
|
+
proxyEnvironment
|
|
8114
|
+
};
|
|
8115
|
+
}
|
|
8116
|
+
}
|
|
8117
|
+
async function buildLogsTailPayload(requestedLines) {
|
|
8118
|
+
const directory = getLogDirectory();
|
|
8119
|
+
const lineCount = Math.min(Math.max(Number.isFinite(requestedLines) ? requestedLines : 200, 1), maxLogTailLines);
|
|
8120
|
+
let latestFile = null;
|
|
8121
|
+
try {
|
|
8122
|
+
const logFiles = readdirSync4(directory).filter((name) => /^server-\d{4}-\d{2}-\d{2}\.log$/.test(name)).sort();
|
|
8123
|
+
latestFile = logFiles.at(-1) ?? null;
|
|
8124
|
+
} catch {
|
|
8125
|
+
latestFile = null;
|
|
8126
|
+
}
|
|
8127
|
+
if (!latestFile) {
|
|
8128
|
+
return {
|
|
8129
|
+
generatedAt: new Date().toISOString(),
|
|
8130
|
+
file: null,
|
|
8131
|
+
lines: []
|
|
8132
|
+
};
|
|
8133
|
+
}
|
|
8134
|
+
const filePath = path11.join(directory, latestFile);
|
|
8135
|
+
const content = await Bun.file(filePath).text();
|
|
8136
|
+
const lines = content.split(`
|
|
8137
|
+
`).filter((line) => line.length > 0);
|
|
8138
|
+
return {
|
|
8139
|
+
generatedAt: new Date().toISOString(),
|
|
8140
|
+
file: filePath,
|
|
8141
|
+
lines: lines.slice(-lineCount)
|
|
8142
|
+
};
|
|
8143
|
+
}
|
|
8144
|
+
var connectivityProbeTimeoutMs = 1e4, maxLogTailLines = 1000, diagnosticsRoutes;
|
|
7431
8145
|
var init_diagnostics_routes = __esm(() => {
|
|
7432
8146
|
init_src2();
|
|
7433
8147
|
init_src();
|
|
@@ -7436,7 +8150,7 @@ var init_diagnostics_routes = __esm(() => {
|
|
|
7436
8150
|
init_prisma_client();
|
|
7437
8151
|
init_runtime_config();
|
|
7438
8152
|
init_extension_runtime();
|
|
7439
|
-
diagnosticsRoutes = new
|
|
8153
|
+
diagnosticsRoutes = new Hono3().get("/status", async (c) => c.json(await buildStatusPayload())).get("/logs/tail", async (c) => c.json(await buildLogsTailPayload(Number(c.req.query("lines") ?? 200)))).get("/connectivity", async (c) => c.json(await buildConnectivityPayload())).get("/doctor", async (c) => c.json(await buildDoctorPayload())).get("/tools", (c) => {
|
|
7440
8154
|
const toolDiagnostics = getToolDiagnostics();
|
|
7441
8155
|
return c.json(diagnosticsToolsResponseSchema.parse({
|
|
7442
8156
|
generatedAt: new Date().toISOString(),
|
|
@@ -7448,19 +8162,19 @@ var init_diagnostics_routes = __esm(() => {
|
|
|
7448
8162
|
|
|
7449
8163
|
// apps/server/src/routes/extension-routes.ts
|
|
7450
8164
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
7451
|
-
import { Hono as
|
|
7452
|
-
import { z as
|
|
8165
|
+
import { Hono as Hono4 } from "hono";
|
|
8166
|
+
import { z as z36 } from "zod";
|
|
7453
8167
|
var skillParamsSchema, mcpServerParamsSchema, extensionRoutes;
|
|
7454
8168
|
var init_extension_routes = __esm(() => {
|
|
7455
8169
|
init_src2();
|
|
7456
8170
|
init_extension_runtime();
|
|
7457
|
-
skillParamsSchema =
|
|
7458
|
-
name:
|
|
8171
|
+
skillParamsSchema = z36.object({
|
|
8172
|
+
name: z36.string().min(1).max(160)
|
|
7459
8173
|
});
|
|
7460
|
-
mcpServerParamsSchema =
|
|
7461
|
-
name:
|
|
8174
|
+
mcpServerParamsSchema = z36.object({
|
|
8175
|
+
name: z36.string().min(1).max(120)
|
|
7462
8176
|
});
|
|
7463
|
-
extensionRoutes = new
|
|
8177
|
+
extensionRoutes = new Hono4().get("/skills", (c) => {
|
|
7464
8178
|
return c.json(skillListOutputSchema.parse({
|
|
7465
8179
|
skills: listSkills({ cwd: process.cwd() })
|
|
7466
8180
|
}));
|
|
@@ -7481,18 +8195,15 @@ var init_extension_routes = __esm(() => {
|
|
|
7481
8195
|
});
|
|
7482
8196
|
|
|
7483
8197
|
// apps/server/src/routes/root-routes.ts
|
|
7484
|
-
import { Hono as
|
|
8198
|
+
import { Hono as Hono5 } from "hono";
|
|
7485
8199
|
var rootRoutes;
|
|
7486
8200
|
var init_root_routes = __esm(() => {
|
|
7487
8201
|
init_src();
|
|
7488
|
-
|
|
7489
|
-
rootRoutes = new Hono4().get("/", (c) => {
|
|
8202
|
+
rootRoutes = new Hono5().get("/", (c) => {
|
|
7490
8203
|
return c.json({
|
|
7491
8204
|
name: productName,
|
|
7492
8205
|
message: createGreeting("API client")
|
|
7493
8206
|
});
|
|
7494
|
-
}).get("/config/status", (c) => {
|
|
7495
|
-
return c.json(configStatus);
|
|
7496
8207
|
});
|
|
7497
8208
|
});
|
|
7498
8209
|
|
|
@@ -7501,24 +8212,25 @@ var exports_app = {};
|
|
|
7501
8212
|
__export(exports_app, {
|
|
7502
8213
|
app: () => app
|
|
7503
8214
|
});
|
|
7504
|
-
import { Hono as
|
|
8215
|
+
import { Hono as Hono6 } from "hono";
|
|
7505
8216
|
import { bodyLimit } from "hono/body-limit";
|
|
7506
8217
|
var maxRequestBodyBytes, app;
|
|
7507
8218
|
var init_app = __esm(() => {
|
|
7508
8219
|
init_chat_routes();
|
|
8220
|
+
init_config_routes();
|
|
7509
8221
|
init_diagnostics_routes();
|
|
7510
8222
|
init_extension_routes();
|
|
7511
8223
|
init_root_routes();
|
|
7512
8224
|
maxRequestBodyBytes = 32 * 1024 * 1024;
|
|
7513
|
-
app = new
|
|
8225
|
+
app = new Hono6().use(bodyLimit({
|
|
7514
8226
|
maxSize: maxRequestBodyBytes,
|
|
7515
8227
|
onError: (c) => c.json({ error: "Request body too large." }, 413)
|
|
7516
|
-
})).route("/", rootRoutes).route("/diagnostics", diagnosticsRoutes).route("/extensions", extensionRoutes).route("/sessions", sessionRoutes);
|
|
8228
|
+
})).route("/", rootRoutes).route("/config", configRoutes).route("/diagnostics", diagnosticsRoutes).route("/extensions", extensionRoutes).route("/sessions", sessionRoutes);
|
|
7517
8229
|
});
|
|
7518
8230
|
|
|
7519
8231
|
// apps/server/src/index.ts
|
|
7520
8232
|
init_src();
|
|
7521
|
-
var
|
|
8233
|
+
var logger9 = createLogger("server");
|
|
7522
8234
|
var httpIdleTimeoutSeconds = 30;
|
|
7523
8235
|
var shutdownForceExitMs = 5000;
|
|
7524
8236
|
function isChatStreamingRoute(request) {
|
|
@@ -7529,7 +8241,11 @@ function isChatStreamingRoute(request) {
|
|
|
7529
8241
|
return /^\/sessions\/[^/]+\/chat$/.test(pathname);
|
|
7530
8242
|
}
|
|
7531
8243
|
async function startServer() {
|
|
7532
|
-
const
|
|
8244
|
+
const logDirectory = enableFileLogSink(Bun.env);
|
|
8245
|
+
if (logDirectory) {
|
|
8246
|
+
logger9.info("file_log_sink_enabled", { directory: logDirectory });
|
|
8247
|
+
}
|
|
8248
|
+
const port = Number(Bun.env.PORT ?? 4983);
|
|
7533
8249
|
const hostname = Bun.env.LIGHTCODE_HOST ?? "127.0.0.1";
|
|
7534
8250
|
let app2;
|
|
7535
8251
|
try {
|
|
@@ -7547,24 +8263,44 @@ async function startServer() {
|
|
|
7547
8263
|
`));
|
|
7548
8264
|
process.exit(1);
|
|
7549
8265
|
}
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
async fetch(request, bunServer) {
|
|
7555
|
-
if (isChatStreamingRoute(request)) {
|
|
7556
|
-
bunServer.timeout(request, 0);
|
|
7557
|
-
}
|
|
7558
|
-
return app2.fetch(request);
|
|
7559
|
-
}
|
|
8266
|
+
Promise.resolve().then(() => (init_runtime_config(), exports_runtime_config)).then((runtime19) => runtime19.initializeModelCapabilities()).catch((error) => {
|
|
8267
|
+
logger9.warn("model_capability_init_failed", {
|
|
8268
|
+
error: getErrorMessage(error)
|
|
8269
|
+
});
|
|
7560
8270
|
});
|
|
8271
|
+
let server;
|
|
8272
|
+
try {
|
|
8273
|
+
server = Bun.serve({
|
|
8274
|
+
idleTimeout: httpIdleTimeoutSeconds,
|
|
8275
|
+
port,
|
|
8276
|
+
hostname,
|
|
8277
|
+
async fetch(request, bunServer) {
|
|
8278
|
+
if (isChatStreamingRoute(request)) {
|
|
8279
|
+
bunServer.timeout(request, 0);
|
|
8280
|
+
}
|
|
8281
|
+
return app2.fetch(request);
|
|
8282
|
+
}
|
|
8283
|
+
});
|
|
8284
|
+
} catch (error) {
|
|
8285
|
+
process.stderr.write([
|
|
8286
|
+
"",
|
|
8287
|
+
`Lightcode server could not bind ${hostname}:${port}:`,
|
|
8288
|
+
` ${getErrorMessage(error)}`,
|
|
8289
|
+
"",
|
|
8290
|
+
"Another process is likely using this port. Set PORT (or",
|
|
8291
|
+
"LIGHTCODE_API_URL for the CLI) to a free port and restart.",
|
|
8292
|
+
""
|
|
8293
|
+
].join(`
|
|
8294
|
+
`));
|
|
8295
|
+
process.exit(1);
|
|
8296
|
+
}
|
|
7561
8297
|
let shuttingDown = false;
|
|
7562
8298
|
const shutdown = async (signal) => {
|
|
7563
8299
|
if (shuttingDown) {
|
|
7564
8300
|
return;
|
|
7565
8301
|
}
|
|
7566
8302
|
shuttingDown = true;
|
|
7567
|
-
|
|
8303
|
+
logger9.info("server_shutdown", { signal });
|
|
7568
8304
|
const forceExitTimer = setTimeout(() => {
|
|
7569
8305
|
server.stop(true);
|
|
7570
8306
|
process.exit(0);
|