@kmugalkhod/lightcode 0.1.3 → 0.3.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 +1540 -542
- package/package.json +4 -2
- package/server.js +1141 -194
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;
|
|
@@ -3107,6 +3201,40 @@ var init_build_provider_view = __esm(() => {
|
|
|
3107
3201
|
init_tier1_prune();
|
|
3108
3202
|
});
|
|
3109
3203
|
|
|
3204
|
+
// packages/ai/src/context/normalize-provider-messages.ts
|
|
3205
|
+
import { isToolUIPart } from "ai";
|
|
3206
|
+
function resolveDanglingToolParts(messages, errorText = interruptedToolErrorText) {
|
|
3207
|
+
return messages.map((message) => {
|
|
3208
|
+
if (message.role !== "assistant") {
|
|
3209
|
+
return message;
|
|
3210
|
+
}
|
|
3211
|
+
let changed = false;
|
|
3212
|
+
const parts = message.parts.map((part) => {
|
|
3213
|
+
if (!isToolUIPart(part) || terminalToolStates.has(part.state)) {
|
|
3214
|
+
return part;
|
|
3215
|
+
}
|
|
3216
|
+
changed = true;
|
|
3217
|
+
return {
|
|
3218
|
+
...part,
|
|
3219
|
+
state: "output-error",
|
|
3220
|
+
errorText
|
|
3221
|
+
};
|
|
3222
|
+
});
|
|
3223
|
+
return changed ? { ...message, parts } : message;
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
function normalizeProviderMessages(messages) {
|
|
3227
|
+
return resolveDanglingToolParts(messages);
|
|
3228
|
+
}
|
|
3229
|
+
var terminalToolStates, interruptedToolErrorText = "Tool call was interrupted by a connection retry. Re-issue the tool call if it is still needed.";
|
|
3230
|
+
var init_normalize_provider_messages = __esm(() => {
|
|
3231
|
+
terminalToolStates = new Set([
|
|
3232
|
+
"output-available",
|
|
3233
|
+
"output-error",
|
|
3234
|
+
"output-denied"
|
|
3235
|
+
]);
|
|
3236
|
+
});
|
|
3237
|
+
|
|
3110
3238
|
// packages/ai/src/context/index.ts
|
|
3111
3239
|
var init_context = __esm(() => {
|
|
3112
3240
|
init_build_provider_view();
|
|
@@ -3115,22 +3243,23 @@ var init_context = __esm(() => {
|
|
|
3115
3243
|
init_estimate();
|
|
3116
3244
|
init_heuristic_summary();
|
|
3117
3245
|
init_message_parts();
|
|
3246
|
+
init_normalize_provider_messages();
|
|
3118
3247
|
init_tier1_prune();
|
|
3119
3248
|
});
|
|
3120
3249
|
|
|
3121
3250
|
// packages/ai/src/config/lightcode-config.ts
|
|
3122
|
-
import { existsSync as
|
|
3251
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3123
3252
|
import os2 from "os";
|
|
3124
|
-
import
|
|
3253
|
+
import path4 from "path";
|
|
3125
3254
|
import { z as z28 } from "zod";
|
|
3126
3255
|
function getDefaultUserConfigPath(env) {
|
|
3127
3256
|
if (env.LIGHTCODE_USER_CONFIG) {
|
|
3128
3257
|
return env.LIGHTCODE_USER_CONFIG;
|
|
3129
3258
|
}
|
|
3130
|
-
return
|
|
3259
|
+
return path4.join(os2.homedir(), ".lightcode", "settings.json");
|
|
3131
3260
|
}
|
|
3132
3261
|
function readConfigFile(scope, configPath) {
|
|
3133
|
-
if (!
|
|
3262
|
+
if (!existsSync3(configPath)) {
|
|
3134
3263
|
return {
|
|
3135
3264
|
config: {},
|
|
3136
3265
|
file: {
|
|
@@ -3213,6 +3342,12 @@ function readEnvConfig(env) {
|
|
|
3213
3342
|
summaryMaxChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS, "LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS")
|
|
3214
3343
|
};
|
|
3215
3344
|
const compactContextConfig = Object.fromEntries(Object.entries(contextConfig).filter(([, value]) => value !== undefined));
|
|
3345
|
+
const headroomConfig = {
|
|
3346
|
+
enabled: parseBooleanEnv(env.LIGHTCODE_HEADROOM_ENABLED, "LIGHTCODE_HEADROOM_ENABLED"),
|
|
3347
|
+
proxyUrl: env.LIGHTCODE_HEADROOM_PROXY_URL?.trim() || undefined,
|
|
3348
|
+
failOpen: parseBooleanEnv(env.LIGHTCODE_HEADROOM_FAIL_OPEN, "LIGHTCODE_HEADROOM_FAIL_OPEN")
|
|
3349
|
+
};
|
|
3350
|
+
const compactHeadroomConfig = Object.fromEntries(Object.entries(headroomConfig).filter(([, value]) => value !== undefined));
|
|
3216
3351
|
const rawConfig = {
|
|
3217
3352
|
provider: env.LIGHTCODE_PROVIDER,
|
|
3218
3353
|
model: env.LIGHTCODE_CHAT_MODEL,
|
|
@@ -3221,7 +3356,9 @@ function readEnvConfig(env) {
|
|
|
3221
3356
|
permissionMode: env.LIGHTCODE_PERMISSION_MODE,
|
|
3222
3357
|
maxOutputTokens: parseNumberEnv(env.LIGHTCODE_MAX_OUTPUT_TOKENS, "LIGHTCODE_MAX_OUTPUT_TOKENS"),
|
|
3223
3358
|
maxSteps: parseNumberEnv(env.LIGHTCODE_MAX_STEPS, "LIGHTCODE_MAX_STEPS"),
|
|
3224
|
-
|
|
3359
|
+
maxRetries: parseNumberEnv(env.LIGHTCODE_MAX_RETRIES, "LIGHTCODE_MAX_RETRIES"),
|
|
3360
|
+
context: Object.keys(compactContextConfig).length > 0 ? compactContextConfig : undefined,
|
|
3361
|
+
headroom: Object.keys(compactHeadroomConfig).length > 0 ? compactHeadroomConfig : undefined
|
|
3225
3362
|
};
|
|
3226
3363
|
const compactConfig = Object.fromEntries(Object.entries(rawConfig).filter(([, value]) => value !== undefined));
|
|
3227
3364
|
const parsedConfig = lightcodeConfigSchema.safeParse(compactConfig);
|
|
@@ -3245,13 +3382,37 @@ function mergeConfig(...configs) {
|
|
|
3245
3382
|
...mergedConfig.autoContinue,
|
|
3246
3383
|
...config2.autoContinue
|
|
3247
3384
|
} : mergedConfig.autoContinue,
|
|
3385
|
+
headroom: config2.headroom ? {
|
|
3386
|
+
...mergedConfig.headroom,
|
|
3387
|
+
...config2.headroom
|
|
3388
|
+
} : mergedConfig.headroom,
|
|
3248
3389
|
allowedTools: config2.allowedTools ?? mergedConfig.allowedTools
|
|
3249
3390
|
}), {});
|
|
3250
3391
|
}
|
|
3392
|
+
function updateUserSettings(update, env = process.env) {
|
|
3393
|
+
const configPath = getDefaultUserConfigPath(env);
|
|
3394
|
+
const existing = readConfigFile("user", configPath).config;
|
|
3395
|
+
const merged = { ...existing };
|
|
3396
|
+
for (const [key, value] of Object.entries(update)) {
|
|
3397
|
+
if (value === undefined || value === null) {
|
|
3398
|
+
delete merged[key];
|
|
3399
|
+
} else {
|
|
3400
|
+
Reflect.set(merged, key, value);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
const parsed = lightcodeConfigSchema.safeParse(merged);
|
|
3404
|
+
if (!parsed.success) {
|
|
3405
|
+
throw new LightcodeConfigError(`Invalid settings update: ${parsed.error.message}`);
|
|
3406
|
+
}
|
|
3407
|
+
mkdirSync3(path4.dirname(configPath), { recursive: true });
|
|
3408
|
+
writeFileSync2(configPath, `${JSON.stringify(parsed.data, null, 2)}
|
|
3409
|
+
`, "utf8");
|
|
3410
|
+
return { path: configPath, config: parsed.data };
|
|
3411
|
+
}
|
|
3251
3412
|
function loadLightcodeConfig({
|
|
3252
3413
|
cwd = process.cwd(),
|
|
3253
3414
|
env = process.env,
|
|
3254
|
-
projectConfigPath =
|
|
3415
|
+
projectConfigPath = path4.join(cwd, ".lightcode", "settings.json"),
|
|
3255
3416
|
userConfigPath = getDefaultUserConfigPath(env)
|
|
3256
3417
|
} = {}) {
|
|
3257
3418
|
const userConfig = readConfigFile("user", userConfigPath);
|
|
@@ -3265,6 +3426,10 @@ function loadLightcodeConfig({
|
|
|
3265
3426
|
autoContinue: {
|
|
3266
3427
|
...defaultAutoContinueConfig,
|
|
3267
3428
|
...mergedConfig.autoContinue
|
|
3429
|
+
},
|
|
3430
|
+
headroom: {
|
|
3431
|
+
...defaultHeadroomConfig,
|
|
3432
|
+
...mergedConfig.headroom
|
|
3268
3433
|
}
|
|
3269
3434
|
};
|
|
3270
3435
|
return lightcodeConfigLoadResultSchema.parse({
|
|
@@ -3272,7 +3437,7 @@ function loadLightcodeConfig({
|
|
|
3272
3437
|
loadedFiles: [userConfig.file, projectConfig.file]
|
|
3273
3438
|
});
|
|
3274
3439
|
}
|
|
3275
|
-
var lightcodeProviderSchema, autoContinueConfigSchema, resolvedAutoContinueConfigSchema, defaultAutoContinueConfig, lightcodeConfigSchema, lightcodeConfigDefaults, loadedConfigFileSchema, lightcodeResolvedConfigSchema, lightcodeConfigLoadResultSchema, lightcodeConfigStatusSchema, LightcodeConfigError;
|
|
3440
|
+
var lightcodeProviderSchema, autoContinueConfigSchema, resolvedAutoContinueConfigSchema, defaultAutoContinueConfig, headroomConfigSchema, resolvedHeadroomConfigSchema, defaultHeadroomConfig, lightcodeConfigSchema, lightcodeConfigDefaults, loadedConfigFileSchema, lightcodeResolvedConfigSchema, lightcodeConfigLoadResultSchema, lightcodeConfigStatusSchema, LightcodeConfigError;
|
|
3276
3441
|
var init_lightcode_config = __esm(() => {
|
|
3277
3442
|
init_coding_agent_modes();
|
|
3278
3443
|
init_coding_agent_modes();
|
|
@@ -3302,7 +3467,25 @@ var init_lightcode_config = __esm(() => {
|
|
|
3302
3467
|
enabled: true,
|
|
3303
3468
|
maxAutoContinues: 50,
|
|
3304
3469
|
maxErrorRetries: 5,
|
|
3305
|
-
stallTimeoutSeconds:
|
|
3470
|
+
stallTimeoutSeconds: 180
|
|
3471
|
+
};
|
|
3472
|
+
headroomConfigSchema = z28.object({
|
|
3473
|
+
enabled: z28.boolean().optional(),
|
|
3474
|
+
proxyUrl: z28.string().min(1).max(4096).optional(),
|
|
3475
|
+
providers: z28.array(lightcodeProviderSchema).optional(),
|
|
3476
|
+
failOpen: z28.boolean().optional()
|
|
3477
|
+
}).strict();
|
|
3478
|
+
resolvedHeadroomConfigSchema = z28.object({
|
|
3479
|
+
enabled: z28.boolean(),
|
|
3480
|
+
proxyUrl: z28.string().min(1).max(4096),
|
|
3481
|
+
providers: z28.array(lightcodeProviderSchema).nullable(),
|
|
3482
|
+
failOpen: z28.boolean()
|
|
3483
|
+
});
|
|
3484
|
+
defaultHeadroomConfig = {
|
|
3485
|
+
enabled: false,
|
|
3486
|
+
proxyUrl: "http://127.0.0.1:8787",
|
|
3487
|
+
providers: null,
|
|
3488
|
+
failOpen: true
|
|
3306
3489
|
};
|
|
3307
3490
|
lightcodeConfigSchema = z28.object({
|
|
3308
3491
|
provider: lightcodeProviderSchema.optional(),
|
|
@@ -3315,9 +3498,11 @@ var init_lightcode_config = __esm(() => {
|
|
|
3315
3498
|
sandbox: sandboxConfigSchema.optional(),
|
|
3316
3499
|
mcp: mcpConfigSchema.optional(),
|
|
3317
3500
|
context: contextOptimizerConfigSchema.optional(),
|
|
3318
|
-
maxOutputTokens: z28.number().int().min(1).max(
|
|
3501
|
+
maxOutputTokens: z28.number().int().min(1).max(1e6).optional(),
|
|
3319
3502
|
maxSteps: z28.number().int().min(1).max(100).optional(),
|
|
3320
|
-
|
|
3503
|
+
maxRetries: z28.number().int().min(0).max(10).optional(),
|
|
3504
|
+
autoContinue: autoContinueConfigSchema.optional(),
|
|
3505
|
+
headroom: headroomConfigSchema.optional()
|
|
3321
3506
|
}).strict();
|
|
3322
3507
|
lightcodeConfigDefaults = {
|
|
3323
3508
|
provider: "anthropic",
|
|
@@ -3325,7 +3510,9 @@ var init_lightcode_config = __esm(() => {
|
|
|
3325
3510
|
context: defaultContextOptimizerConfig,
|
|
3326
3511
|
maxOutputTokens: 32768,
|
|
3327
3512
|
maxSteps: 30,
|
|
3328
|
-
|
|
3513
|
+
maxRetries: 5,
|
|
3514
|
+
autoContinue: defaultAutoContinueConfig,
|
|
3515
|
+
headroom: defaultHeadroomConfig
|
|
3329
3516
|
};
|
|
3330
3517
|
loadedConfigFileSchema = z28.object({
|
|
3331
3518
|
scope: z28.enum(["user", "project"]),
|
|
@@ -3337,9 +3524,11 @@ var init_lightcode_config = __esm(() => {
|
|
|
3337
3524
|
provider: lightcodeProviderSchema,
|
|
3338
3525
|
defaultMode: codingAgentModeSchema,
|
|
3339
3526
|
context: resolvedContextOptimizerConfigSchema,
|
|
3340
|
-
maxOutputTokens: z28.number().int().min(1).max(
|
|
3527
|
+
maxOutputTokens: z28.number().int().min(1).max(1e6),
|
|
3341
3528
|
maxSteps: z28.number().int().min(1).max(100),
|
|
3342
|
-
|
|
3529
|
+
maxRetries: z28.number().int().min(0).max(10),
|
|
3530
|
+
autoContinue: resolvedAutoContinueConfigSchema,
|
|
3531
|
+
headroom: resolvedHeadroomConfigSchema.optional()
|
|
3343
3532
|
});
|
|
3344
3533
|
lightcodeConfigLoadResultSchema = z28.object({
|
|
3345
3534
|
config: lightcodeResolvedConfigSchema,
|
|
@@ -3353,10 +3542,15 @@ var init_lightcode_config = __esm(() => {
|
|
|
3353
3542
|
baseUrl: z28.string().nullable(),
|
|
3354
3543
|
defaultMode: codingAgentModeSchema,
|
|
3355
3544
|
permissionMode: permissionModeSchema.nullable(),
|
|
3356
|
-
maxOutputTokens: z28.number().int().min(1).max(
|
|
3545
|
+
maxOutputTokens: z28.number().int().min(1).max(1e6),
|
|
3357
3546
|
maxSteps: z28.number().int().min(1).max(100),
|
|
3358
3547
|
autoContinue: resolvedAutoContinueConfigSchema,
|
|
3359
3548
|
contextWindow: z28.number().int().positive(),
|
|
3549
|
+
headroom: z28.object({
|
|
3550
|
+
enabled: z28.boolean(),
|
|
3551
|
+
proxyUrl: z28.string().nullable(),
|
|
3552
|
+
routed: z28.boolean()
|
|
3553
|
+
}),
|
|
3360
3554
|
missingCredentialHints: z28.array(z28.string())
|
|
3361
3555
|
});
|
|
3362
3556
|
LightcodeConfigError = class LightcodeConfigError extends Error {
|
|
@@ -3596,6 +3790,21 @@ var init_runtime = __esm(() => {
|
|
|
3596
3790
|
init_schema13();
|
|
3597
3791
|
});
|
|
3598
3792
|
|
|
3793
|
+
// packages/ai/src/providers/clamp-output-tokens.ts
|
|
3794
|
+
function clampMaxOutputTokens(configured, modelMaxCompletionTokens) {
|
|
3795
|
+
if (typeof modelMaxCompletionTokens === "number" && Number.isFinite(modelMaxCompletionTokens) && modelMaxCompletionTokens > 0) {
|
|
3796
|
+
return Math.min(configured, modelMaxCompletionTokens);
|
|
3797
|
+
}
|
|
3798
|
+
return configured;
|
|
3799
|
+
}
|
|
3800
|
+
function resolveMaxOutputTokens(configured, defaultValue, modelMaxCompletionTokens) {
|
|
3801
|
+
const hasModelCap = typeof modelMaxCompletionTokens === "number" && Number.isFinite(modelMaxCompletionTokens) && modelMaxCompletionTokens > 0;
|
|
3802
|
+
if (configured === defaultValue) {
|
|
3803
|
+
return hasModelCap ? modelMaxCompletionTokens : configured;
|
|
3804
|
+
}
|
|
3805
|
+
return clampMaxOutputTokens(configured, modelMaxCompletionTokens);
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3599
3808
|
// packages/ai/src/providers/model-metadata.ts
|
|
3600
3809
|
function getModelContextWindow(provider, modelId) {
|
|
3601
3810
|
if (provider === "anthropic") {
|
|
@@ -4227,27 +4436,27 @@ var init_schema16 = __esm(() => {
|
|
|
4227
4436
|
});
|
|
4228
4437
|
|
|
4229
4438
|
// packages/ai/src/plugins/runtime.ts
|
|
4230
|
-
import { existsSync as
|
|
4439
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
|
|
4231
4440
|
import os3 from "os";
|
|
4232
|
-
import
|
|
4441
|
+
import path5 from "path";
|
|
4233
4442
|
function pluginRoots(cwd) {
|
|
4234
4443
|
return [
|
|
4235
|
-
|
|
4236
|
-
|
|
4444
|
+
path5.join(cwd, ".lightcode", "plugins"),
|
|
4445
|
+
path5.join(os3.homedir(), ".lightcode", "plugins")
|
|
4237
4446
|
];
|
|
4238
4447
|
}
|
|
4239
4448
|
function listPlugins({ cwd = process.cwd() } = {}) {
|
|
4240
4449
|
const plugins = [];
|
|
4241
4450
|
for (const root of pluginRoots(cwd)) {
|
|
4242
|
-
if (!
|
|
4451
|
+
if (!existsSync4(root)) {
|
|
4243
4452
|
continue;
|
|
4244
4453
|
}
|
|
4245
|
-
for (const entry of
|
|
4454
|
+
for (const entry of readdirSync2(root, { withFileTypes: true })) {
|
|
4246
4455
|
if (!entry.isDirectory()) {
|
|
4247
4456
|
continue;
|
|
4248
4457
|
}
|
|
4249
|
-
const manifestPath =
|
|
4250
|
-
if (!
|
|
4458
|
+
const manifestPath = path5.join(root, entry.name, "plugin.json");
|
|
4459
|
+
if (!existsSync4(manifestPath)) {
|
|
4251
4460
|
continue;
|
|
4252
4461
|
}
|
|
4253
4462
|
const manifest = pluginManifestSchema.parse(JSON.parse(readFileSync3(manifestPath, "utf8")));
|
|
@@ -4269,14 +4478,14 @@ var init_runtime2 = __esm(() => {
|
|
|
4269
4478
|
});
|
|
4270
4479
|
|
|
4271
4480
|
// packages/ai/src/skills/runtime.ts
|
|
4272
|
-
import { existsSync as
|
|
4481
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
4273
4482
|
import os4 from "os";
|
|
4274
|
-
import
|
|
4483
|
+
import path6 from "path";
|
|
4275
4484
|
function defaultSkillRoots(cwd) {
|
|
4276
4485
|
return [
|
|
4277
|
-
{ source: "project", path:
|
|
4278
|
-
{ source: "project", path:
|
|
4279
|
-
{ source: "user", path:
|
|
4486
|
+
{ source: "project", path: path6.join(cwd, ".lightcode", "skills") },
|
|
4487
|
+
{ source: "project", path: path6.join(cwd, ".agents", "skills") },
|
|
4488
|
+
{ source: "user", path: path6.join(os4.homedir(), ".lightcode", "skills") }
|
|
4280
4489
|
];
|
|
4281
4490
|
}
|
|
4282
4491
|
function normalizeSkillName(value) {
|
|
@@ -4298,7 +4507,7 @@ function parseBasicFrontmatter(content) {
|
|
|
4298
4507
|
]));
|
|
4299
4508
|
}
|
|
4300
4509
|
function findSkillFiles(root) {
|
|
4301
|
-
if (!
|
|
4510
|
+
if (!existsSync5(root.path)) {
|
|
4302
4511
|
return [];
|
|
4303
4512
|
}
|
|
4304
4513
|
const files = [];
|
|
@@ -4306,8 +4515,8 @@ function findSkillFiles(root) {
|
|
|
4306
4515
|
if (depth > 4) {
|
|
4307
4516
|
return;
|
|
4308
4517
|
}
|
|
4309
|
-
for (const entry of
|
|
4310
|
-
const entryPath =
|
|
4518
|
+
for (const entry of readdirSync3(directory)) {
|
|
4519
|
+
const entryPath = path6.join(directory, entry);
|
|
4311
4520
|
const stat = statSync(entryPath);
|
|
4312
4521
|
if (stat.isDirectory()) {
|
|
4313
4522
|
visit(entryPath, depth + 1);
|
|
@@ -4328,7 +4537,7 @@ function listSkills({
|
|
|
4328
4537
|
for (const skillPath of findSkillFiles(root)) {
|
|
4329
4538
|
const content = readFileSync4(skillPath, "utf8");
|
|
4330
4539
|
const frontmatter = parseBasicFrontmatter(content);
|
|
4331
|
-
const fallbackName =
|
|
4540
|
+
const fallbackName = path6.basename(path6.dirname(skillPath));
|
|
4332
4541
|
const name = normalizeSkillName(frontmatter.name ?? fallbackName);
|
|
4333
4542
|
if (!name || seen.has(name)) {
|
|
4334
4543
|
continue;
|
|
@@ -4403,6 +4612,7 @@ var init_subagent_schemas = __esm(() => {
|
|
|
4403
4612
|
// packages/ai/src/index.ts
|
|
4404
4613
|
var init_src2 = __esm(() => {
|
|
4405
4614
|
init_agent_tools();
|
|
4615
|
+
init_chat_error();
|
|
4406
4616
|
init_chat_interaction_schemas();
|
|
4407
4617
|
init_plan_block();
|
|
4408
4618
|
init_chat_schemas();
|
|
@@ -4433,20 +4643,20 @@ var init_src2 = __esm(() => {
|
|
|
4433
4643
|
|
|
4434
4644
|
// packages/db/src/local-database.ts
|
|
4435
4645
|
import { Database } from "bun:sqlite";
|
|
4436
|
-
import { existsSync as
|
|
4646
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
4437
4647
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
4438
4648
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
4439
|
-
import { homedir, platform } from "os";
|
|
4649
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
4440
4650
|
function getDefaultLightcodeDataDir() {
|
|
4441
4651
|
const configuredHome = process.env.LIGHTCODE_HOME?.trim();
|
|
4442
4652
|
if (configuredHome) {
|
|
4443
4653
|
return resolve(configuredHome);
|
|
4444
4654
|
}
|
|
4445
|
-
if (
|
|
4655
|
+
if (platform2() === "win32") {
|
|
4446
4656
|
const appData = process.env.APPDATA || process.env.LOCALAPPDATA;
|
|
4447
|
-
return appData ? join(appData, appDirectoryName) : join(
|
|
4657
|
+
return appData ? join(appData, appDirectoryName) : join(homedir2(), "AppData", "Roaming", appDirectoryName);
|
|
4448
4658
|
}
|
|
4449
|
-
return join(
|
|
4659
|
+
return join(homedir2(), `.${appDirectoryName}`);
|
|
4450
4660
|
}
|
|
4451
4661
|
function getDefaultDatabasePath() {
|
|
4452
4662
|
const configuredPath = process.env.LIGHTCODE_DATABASE_PATH?.trim();
|
|
@@ -4456,11 +4666,9 @@ function getDefaultDatabaseUrl() {
|
|
|
4456
4666
|
return pathToFileURL(getDefaultDatabasePath()).href;
|
|
4457
4667
|
}
|
|
4458
4668
|
function resolveDatabaseUrl() {
|
|
4459
|
-
const configuredUrl = process.env.
|
|
4460
|
-
const supportedConfiguredUrl = configuredUrl
|
|
4461
|
-
|
|
4462
|
-
process.env.DATABASE_URL = databaseUrl;
|
|
4463
|
-
return databaseUrl;
|
|
4669
|
+
const configuredUrl = process.env.LIGHTCODE_DATABASE_URL?.trim();
|
|
4670
|
+
const supportedConfiguredUrl = configuredUrl && (configuredUrl.startsWith("file:") || configuredUrl.startsWith("libsql:") || configuredUrl.startsWith("http://") || configuredUrl.startsWith("https://")) ? configuredUrl : null;
|
|
4671
|
+
return supportedConfiguredUrl ?? getDefaultDatabaseUrl();
|
|
4464
4672
|
}
|
|
4465
4673
|
function filePathFromDatabaseUrl(databaseUrl) {
|
|
4466
4674
|
if (!databaseUrl.startsWith("file:")) {
|
|
@@ -4474,8 +4682,8 @@ function filePathFromDatabaseUrl(databaseUrl) {
|
|
|
4474
4682
|
}
|
|
4475
4683
|
function ensureParentDirectory(filePath) {
|
|
4476
4684
|
const parentDirectory = dirname(filePath);
|
|
4477
|
-
if (!
|
|
4478
|
-
|
|
4685
|
+
if (!existsSync6(parentDirectory)) {
|
|
4686
|
+
mkdirSync4(parentDirectory, { recursive: true });
|
|
4479
4687
|
}
|
|
4480
4688
|
}
|
|
4481
4689
|
function initializeLocalDatabase(databaseUrl) {
|
|
@@ -4611,14 +4819,10 @@ var init_local_database = () => {};
|
|
|
4611
4819
|
// apps/server/src/lib/chat-interaction-store.ts
|
|
4612
4820
|
import { Database as Database2 } from "bun:sqlite";
|
|
4613
4821
|
function getDatabase() {
|
|
4614
|
-
if (!
|
|
4822
|
+
if (!_db) {
|
|
4615
4823
|
const dbUrl = resolveDatabaseUrl();
|
|
4616
4824
|
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);
|
|
4825
|
+
const dbPath = filePathFromDatabaseUrl(dbUrl) ?? getDefaultDatabasePath();
|
|
4622
4826
|
_db = new Database2(dbPath);
|
|
4623
4827
|
}
|
|
4624
4828
|
return _db;
|
|
@@ -4769,7 +4973,7 @@ async function summarizePendingChatInteractions({
|
|
|
4769
4973
|
recoverable: total
|
|
4770
4974
|
};
|
|
4771
4975
|
}
|
|
4772
|
-
var ChatInteractionNotFoundError, _db = null
|
|
4976
|
+
var ChatInteractionNotFoundError, _db = null;
|
|
4773
4977
|
var init_chat_interaction_store = __esm(() => {
|
|
4774
4978
|
init_src2();
|
|
4775
4979
|
init_local_database();
|
|
@@ -4786,6 +4990,28 @@ var init_chat_interaction_store = __esm(() => {
|
|
|
4786
4990
|
});
|
|
4787
4991
|
|
|
4788
4992
|
// apps/server/src/lib/chat-observability.ts
|
|
4993
|
+
import { APICallError, RetryError } from "ai";
|
|
4994
|
+
function chatFailureClassForErrorKind(kind) {
|
|
4995
|
+
switch (kind) {
|
|
4996
|
+
case "auth":
|
|
4997
|
+
return "provider_auth";
|
|
4998
|
+
case "billing":
|
|
4999
|
+
return "provider_billing_quota";
|
|
5000
|
+
case "rate_limit":
|
|
5001
|
+
return "provider_rate_limit";
|
|
5002
|
+
case "invalid_request":
|
|
5003
|
+
return "provider_invalid_request";
|
|
5004
|
+
case "context_overflow":
|
|
5005
|
+
return "context_overflow";
|
|
5006
|
+
case "provider_unavailable":
|
|
5007
|
+
return "provider_unavailable";
|
|
5008
|
+
case "network":
|
|
5009
|
+
case "aborted":
|
|
5010
|
+
return "timeout_disconnect";
|
|
5011
|
+
default:
|
|
5012
|
+
return "provider_unknown";
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
4789
5015
|
function normalizeErrorMessage(message) {
|
|
4790
5016
|
return message.toLowerCase();
|
|
4791
5017
|
}
|
|
@@ -4797,6 +5023,76 @@ function isProviderBillingOrQuotaError(error) {
|
|
|
4797
5023
|
const message = normalizeErrorMessage(getErrorMessage(error));
|
|
4798
5024
|
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
5025
|
}
|
|
5026
|
+
function isContextOverflowMessage(message) {
|
|
5027
|
+
return contextOverflowPattern.test(message);
|
|
5028
|
+
}
|
|
5029
|
+
function isAbortError(error) {
|
|
5030
|
+
return error instanceof Error && error.name === "AbortError" || normalizeErrorMessage(getErrorMessage(error)).includes("aborted");
|
|
5031
|
+
}
|
|
5032
|
+
function classifyByStatusCode(statusCode, message) {
|
|
5033
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
5034
|
+
return { kind: "auth", retryable: false };
|
|
5035
|
+
}
|
|
5036
|
+
if (statusCode === 402) {
|
|
5037
|
+
return { kind: "billing", retryable: false };
|
|
5038
|
+
}
|
|
5039
|
+
if (statusCode === 429) {
|
|
5040
|
+
return { kind: "rate_limit", retryable: true };
|
|
5041
|
+
}
|
|
5042
|
+
if (statusCode === 400 || statusCode === 422 || statusCode === 413) {
|
|
5043
|
+
if (isContextOverflowMessage(message)) {
|
|
5044
|
+
return { kind: "context_overflow", retryable: true };
|
|
5045
|
+
}
|
|
5046
|
+
if (isProviderBillingOrQuotaError(message)) {
|
|
5047
|
+
return { kind: "billing", retryable: false };
|
|
5048
|
+
}
|
|
5049
|
+
return { kind: "invalid_request", retryable: false };
|
|
5050
|
+
}
|
|
5051
|
+
if (retryableStatusCodes.has(statusCode) || statusCode >= 500) {
|
|
5052
|
+
return { kind: "provider_unavailable", retryable: true };
|
|
5053
|
+
}
|
|
5054
|
+
return { kind: "unknown", retryable: false };
|
|
5055
|
+
}
|
|
5056
|
+
function classifyChatError(error) {
|
|
5057
|
+
if (RetryError.isInstance(error) && error.lastError) {
|
|
5058
|
+
return classifyChatError(error.lastError);
|
|
5059
|
+
}
|
|
5060
|
+
if (APICallError.isInstance(error)) {
|
|
5061
|
+
const responseBody = error.responseBody ?? "";
|
|
5062
|
+
const message2 = responseBody.length > 0 ? responseBody : error.message;
|
|
5063
|
+
if (typeof error.statusCode === "number") {
|
|
5064
|
+
const { kind, retryable } = classifyByStatusCode(error.statusCode, message2);
|
|
5065
|
+
return {
|
|
5066
|
+
kind,
|
|
5067
|
+
statusCode: error.statusCode,
|
|
5068
|
+
retryable,
|
|
5069
|
+
message: message2
|
|
5070
|
+
};
|
|
5071
|
+
}
|
|
5072
|
+
return {
|
|
5073
|
+
kind: error.isRetryable ? "network" : "unknown",
|
|
5074
|
+
retryable: error.isRetryable,
|
|
5075
|
+
message: message2
|
|
5076
|
+
};
|
|
5077
|
+
}
|
|
5078
|
+
const message = getErrorMessage(error);
|
|
5079
|
+
if (isAbortError(error)) {
|
|
5080
|
+
return { kind: "aborted", retryable: false, message };
|
|
5081
|
+
}
|
|
5082
|
+
if (isContextOverflowMessage(message)) {
|
|
5083
|
+
return { kind: "context_overflow", retryable: true, message };
|
|
5084
|
+
}
|
|
5085
|
+
if (isProviderBillingOrQuotaError(error)) {
|
|
5086
|
+
return { kind: "billing", retryable: false, message };
|
|
5087
|
+
}
|
|
5088
|
+
if (isProviderSchemaRejectionError(error)) {
|
|
5089
|
+
return { kind: "invalid_request", retryable: false, message };
|
|
5090
|
+
}
|
|
5091
|
+
if (isDisconnectOrTimeoutError(error)) {
|
|
5092
|
+
return { kind: "network", retryable: true, message };
|
|
5093
|
+
}
|
|
5094
|
+
return { kind: "unknown", retryable: false, message };
|
|
5095
|
+
}
|
|
4800
5096
|
function isDisconnectOrTimeoutError(error) {
|
|
4801
5097
|
const message = normalizeErrorMessage(getErrorMessage(error));
|
|
4802
5098
|
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 +5118,22 @@ function incrementChatFailureCounter(failureClass, context2 = {}) {
|
|
|
4822
5118
|
...context2
|
|
4823
5119
|
});
|
|
4824
5120
|
}
|
|
5121
|
+
function logChatStreamErrorEvent({
|
|
5122
|
+
sessionId,
|
|
5123
|
+
phase,
|
|
5124
|
+
classified,
|
|
5125
|
+
error
|
|
5126
|
+
}) {
|
|
5127
|
+
logger2.error("chat_stream_error", {
|
|
5128
|
+
sessionId,
|
|
5129
|
+
phase,
|
|
5130
|
+
kind: classified.kind,
|
|
5131
|
+
statusCode: classified.statusCode,
|
|
5132
|
+
retryable: classified.retryable,
|
|
5133
|
+
message: classified.message,
|
|
5134
|
+
rawMessage: getErrorMessage(error)
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
4825
5137
|
function logChatDisconnectEvent({
|
|
4826
5138
|
sessionId,
|
|
4827
5139
|
phase,
|
|
@@ -4834,17 +5146,25 @@ function logChatDisconnectEvent({
|
|
|
4834
5146
|
message: getErrorMessage(error)
|
|
4835
5147
|
});
|
|
4836
5148
|
}
|
|
4837
|
-
var chatFailureCounters, logger2;
|
|
5149
|
+
var chatFailureCounters, logger2, retryableStatusCodes, contextOverflowPattern;
|
|
4838
5150
|
var init_chat_observability = __esm(() => {
|
|
4839
5151
|
init_src();
|
|
4840
5152
|
chatFailureCounters = {
|
|
4841
5153
|
provider_schema_rejection: 0,
|
|
4842
5154
|
provider_billing_quota: 0,
|
|
5155
|
+
provider_auth: 0,
|
|
5156
|
+
provider_rate_limit: 0,
|
|
5157
|
+
provider_invalid_request: 0,
|
|
5158
|
+
provider_unavailable: 0,
|
|
5159
|
+
context_overflow: 0,
|
|
5160
|
+
provider_unknown: 0,
|
|
4843
5161
|
timeout_disconnect: 0,
|
|
4844
5162
|
stale_finish_skip: 0,
|
|
4845
5163
|
db_unique_conflict: 0
|
|
4846
5164
|
};
|
|
4847
5165
|
logger2 = createLogger("chat");
|
|
5166
|
+
retryableStatusCodes = new Set([408, 409, 429, 500, 502, 503, 504]);
|
|
5167
|
+
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
5168
|
});
|
|
4849
5169
|
|
|
4850
5170
|
// packages/db/generated/prisma/internal/class.ts
|
|
@@ -5057,7 +5377,7 @@ var init_prismaNamespace = __esm(() => {
|
|
|
5057
5377
|
var init_enums = () => {};
|
|
5058
5378
|
|
|
5059
5379
|
// packages/db/generated/prisma/client.ts
|
|
5060
|
-
import * as
|
|
5380
|
+
import * as path7 from "path";
|
|
5061
5381
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5062
5382
|
var PrismaClient;
|
|
5063
5383
|
var init_client = __esm(() => {
|
|
@@ -5065,7 +5385,7 @@ var init_client = __esm(() => {
|
|
|
5065
5385
|
init_prismaNamespace();
|
|
5066
5386
|
init_enums();
|
|
5067
5387
|
init_enums();
|
|
5068
|
-
globalThis["__dirname"] =
|
|
5388
|
+
globalThis["__dirname"] = path7.dirname(fileURLToPath2(import.meta.url));
|
|
5069
5389
|
PrismaClient = getPrismaClientClass();
|
|
5070
5390
|
});
|
|
5071
5391
|
|
|
@@ -5643,28 +5963,28 @@ function mergeFinishedMessagesIntoFullHistory({
|
|
|
5643
5963
|
}
|
|
5644
5964
|
|
|
5645
5965
|
// packages/ai/src/common/resolve-within-workspace.ts
|
|
5646
|
-
import
|
|
5966
|
+
import path8 from "path";
|
|
5647
5967
|
import {
|
|
5648
|
-
existsSync as
|
|
5968
|
+
existsSync as existsSync7,
|
|
5649
5969
|
lstatSync,
|
|
5650
5970
|
realpathSync,
|
|
5651
5971
|
statSync as statSync2
|
|
5652
5972
|
} from "fs";
|
|
5653
5973
|
function stripTrailingSeparator(value) {
|
|
5654
|
-
const parsed =
|
|
5974
|
+
const parsed = path8.parse(value);
|
|
5655
5975
|
if (value === parsed.root) {
|
|
5656
5976
|
return value;
|
|
5657
5977
|
}
|
|
5658
5978
|
return value.replace(/[\\/]+$/, "");
|
|
5659
5979
|
}
|
|
5660
5980
|
function normalizeForComparison(value) {
|
|
5661
|
-
const normalized = stripTrailingSeparator(
|
|
5981
|
+
const normalized = stripTrailingSeparator(path8.resolve(value));
|
|
5662
5982
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
5663
5983
|
}
|
|
5664
5984
|
function isWithinWorkspace(absolutePath, context2) {
|
|
5665
5985
|
const workspace = normalizeForComparison(context2.root);
|
|
5666
5986
|
const target = normalizeForComparison(absolutePath);
|
|
5667
|
-
return target === workspace || target.startsWith(`${workspace}${
|
|
5987
|
+
return target === workspace || target.startsWith(`${workspace}${path8.sep}`);
|
|
5668
5988
|
}
|
|
5669
5989
|
function assertWithinWorkspace(absolutePath, context2, input) {
|
|
5670
5990
|
if (!isWithinWorkspace(absolutePath, context2)) {
|
|
@@ -5679,17 +5999,17 @@ function getExistingRealPath(absolutePath) {
|
|
|
5679
5999
|
}
|
|
5680
6000
|
}
|
|
5681
6001
|
function findNearestExistingParent(absolutePath, context2, input) {
|
|
5682
|
-
let currentPath =
|
|
6002
|
+
let currentPath = path8.dirname(absolutePath);
|
|
5683
6003
|
while (true) {
|
|
5684
6004
|
assertWithinWorkspace(currentPath, context2, input);
|
|
5685
|
-
if (
|
|
6005
|
+
if (existsSync7(currentPath)) {
|
|
5686
6006
|
const realParentPath = getExistingRealPath(currentPath);
|
|
5687
6007
|
if (!realParentPath) {
|
|
5688
6008
|
throw new Error(`Unable to resolve existing parent for path: ${input}`);
|
|
5689
6009
|
}
|
|
5690
6010
|
return realParentPath;
|
|
5691
6011
|
}
|
|
5692
|
-
const parentPath =
|
|
6012
|
+
const parentPath = path8.dirname(currentPath);
|
|
5693
6013
|
if (parentPath === currentPath) {
|
|
5694
6014
|
throw new Error(`Unable to find an existing workspace parent for path: ${input}`);
|
|
5695
6015
|
}
|
|
@@ -5709,7 +6029,7 @@ function getDefaultWorkspaceContext() {
|
|
|
5709
6029
|
}
|
|
5710
6030
|
function resolveWithinWorkspace(input, options = {}) {
|
|
5711
6031
|
const context2 = options.context ?? defaultWorkspaceContext;
|
|
5712
|
-
const resolved =
|
|
6032
|
+
const resolved = path8.resolve(context2.root, input);
|
|
5713
6033
|
assertWithinWorkspace(resolved, context2, input);
|
|
5714
6034
|
const realPath = getExistingRealPath(resolved);
|
|
5715
6035
|
if (realPath) {
|
|
@@ -5852,7 +6172,7 @@ var init_runtime11 = __esm(() => {
|
|
|
5852
6172
|
|
|
5853
6173
|
// packages/ai/src/todo-write/runtime.ts
|
|
5854
6174
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
5855
|
-
import
|
|
6175
|
+
import path9 from "path";
|
|
5856
6176
|
import { z as z33 } from "zod";
|
|
5857
6177
|
function safeTodoSessionId(sessionId) {
|
|
5858
6178
|
const trimmed = sessionId?.trim();
|
|
@@ -5862,7 +6182,7 @@ function safeTodoSessionId(sessionId) {
|
|
|
5862
6182
|
return trimmed.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120);
|
|
5863
6183
|
}
|
|
5864
6184
|
function getTodoFilePath(sessionId, workspaceContext) {
|
|
5865
|
-
return resolveWithinWorkspace(
|
|
6185
|
+
return resolveWithinWorkspace(path9.join(".lightcode", "todos", `${safeTodoSessionId(sessionId)}.json`), {
|
|
5866
6186
|
context: workspaceContext,
|
|
5867
6187
|
allowMissing: true
|
|
5868
6188
|
});
|
|
@@ -6066,6 +6386,7 @@ ${transcript}`
|
|
|
6066
6386
|
system: COMPACTION_SYSTEM_PROMPT,
|
|
6067
6387
|
prompt,
|
|
6068
6388
|
maxOutputTokens: COMPACTION_MAX_OUTPUT_TOKENS,
|
|
6389
|
+
maxRetries: 2,
|
|
6069
6390
|
abortSignal: AbortSignal.timeout(COMPACTION_TIMEOUT_MS)
|
|
6070
6391
|
});
|
|
6071
6392
|
const text = result.text.trim();
|
|
@@ -6114,7 +6435,7 @@ function composeSummary({
|
|
|
6114
6435
|
`).trim();
|
|
6115
6436
|
const bodyBudget = Math.max(MIN_SUMMARY_BODY_CHARS, summaryMaxChars - (pinnedText ? pinnedText.length + 2 : 0));
|
|
6116
6437
|
const compressedBody = compressSummary(body, bodyBudget);
|
|
6117
|
-
return [compressedBody, pinnedText].filter(Boolean).join(`
|
|
6438
|
+
return [compressedBody, pinnedText, compactionDirectResumeInstruction].filter(Boolean).join(`
|
|
6118
6439
|
|
|
6119
6440
|
`);
|
|
6120
6441
|
}
|
|
@@ -6192,13 +6513,227 @@ Unfinished work, known follow-ups, and the immediate next step.
|
|
|
6192
6513
|
## Tools and commands that mattered
|
|
6193
6514
|
Important commands run and their relevant outcomes (test results, errors, etc.).
|
|
6194
6515
|
|
|
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
|
|
6516
|
+
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
6517
|
var init_context_compaction = __esm(() => {
|
|
6197
6518
|
init_src2();
|
|
6198
6519
|
init_runtime17();
|
|
6199
6520
|
init_src();
|
|
6200
6521
|
init_context_state_store();
|
|
6201
6522
|
logger3 = createLogger("context-compaction");
|
|
6523
|
+
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.";
|
|
6524
|
+
});
|
|
6525
|
+
|
|
6526
|
+
// apps/server/src/lib/overflow-recovery.ts
|
|
6527
|
+
function noteContextOverflow(sessionId) {
|
|
6528
|
+
overflowRecoveryRounds.set(sessionId, (overflowRecoveryRounds.get(sessionId) ?? 0) + 1);
|
|
6529
|
+
}
|
|
6530
|
+
function clearContextOverflowRounds(sessionId) {
|
|
6531
|
+
overflowRecoveryRounds.delete(sessionId);
|
|
6532
|
+
}
|
|
6533
|
+
function getOverflowPreserveRecentMessages(sessionId) {
|
|
6534
|
+
const rounds = overflowRecoveryRounds.get(sessionId) ?? 0;
|
|
6535
|
+
if (rounds === 0) {
|
|
6536
|
+
return null;
|
|
6537
|
+
}
|
|
6538
|
+
return overflowPreserveSchedule[Math.min(rounds, overflowPreserveSchedule.length) - 1];
|
|
6539
|
+
}
|
|
6540
|
+
var overflowPreserveSchedule, overflowRecoveryRounds;
|
|
6541
|
+
var init_overflow_recovery = __esm(() => {
|
|
6542
|
+
overflowPreserveSchedule = [4, 2, 1, 0];
|
|
6543
|
+
overflowRecoveryRounds = new Map;
|
|
6544
|
+
});
|
|
6545
|
+
|
|
6546
|
+
// apps/server/src/lib/sse-heartbeat.ts
|
|
6547
|
+
function endsWithEventBoundary(chunk) {
|
|
6548
|
+
const length = chunk.length;
|
|
6549
|
+
if (length >= 2 && chunk[length - 1] === 10 && chunk[length - 2] === 10) {
|
|
6550
|
+
return true;
|
|
6551
|
+
}
|
|
6552
|
+
return length >= 4 && chunk[length - 1] === 10 && chunk[length - 2] === 13 && chunk[length - 3] === 10 && chunk[length - 4] === 13;
|
|
6553
|
+
}
|
|
6554
|
+
function withSseHeartbeat(response, intervalMs = defaultHeartbeatIntervalMs) {
|
|
6555
|
+
const body = response.body;
|
|
6556
|
+
if (!body) {
|
|
6557
|
+
return response;
|
|
6558
|
+
}
|
|
6559
|
+
let atEventBoundary = true;
|
|
6560
|
+
let heartbeatTimer = null;
|
|
6561
|
+
let lastChunkAt = Date.now();
|
|
6562
|
+
const stopHeartbeat = () => {
|
|
6563
|
+
if (heartbeatTimer) {
|
|
6564
|
+
clearInterval(heartbeatTimer);
|
|
6565
|
+
heartbeatTimer = null;
|
|
6566
|
+
}
|
|
6567
|
+
};
|
|
6568
|
+
const transform = new TransformStream({
|
|
6569
|
+
start(controller) {
|
|
6570
|
+
heartbeatTimer = setInterval(() => {
|
|
6571
|
+
if (!atEventBoundary || Date.now() - lastChunkAt < intervalMs) {
|
|
6572
|
+
return;
|
|
6573
|
+
}
|
|
6574
|
+
try {
|
|
6575
|
+
controller.enqueue(heartbeatFrame);
|
|
6576
|
+
lastChunkAt = Date.now();
|
|
6577
|
+
} catch {
|
|
6578
|
+
stopHeartbeat();
|
|
6579
|
+
}
|
|
6580
|
+
}, Math.min(intervalMs, 5000));
|
|
6581
|
+
},
|
|
6582
|
+
transform(chunk, controller) {
|
|
6583
|
+
atEventBoundary = endsWithEventBoundary(chunk);
|
|
6584
|
+
lastChunkAt = Date.now();
|
|
6585
|
+
controller.enqueue(chunk);
|
|
6586
|
+
},
|
|
6587
|
+
flush() {
|
|
6588
|
+
stopHeartbeat();
|
|
6589
|
+
}
|
|
6590
|
+
});
|
|
6591
|
+
const heartbeatBody = body.pipeThrough(transform);
|
|
6592
|
+
return new Response(heartbeatBody, {
|
|
6593
|
+
status: response.status,
|
|
6594
|
+
statusText: response.statusText,
|
|
6595
|
+
headers: response.headers
|
|
6596
|
+
});
|
|
6597
|
+
}
|
|
6598
|
+
var encoder, heartbeatFrame, defaultHeartbeatIntervalMs = 15000;
|
|
6599
|
+
var init_sse_heartbeat = __esm(() => {
|
|
6600
|
+
encoder = new TextEncoder;
|
|
6601
|
+
heartbeatFrame = encoder.encode(`: heartbeat
|
|
6602
|
+
|
|
6603
|
+
`);
|
|
6604
|
+
});
|
|
6605
|
+
|
|
6606
|
+
// apps/server/src/lib/openrouter-models.ts
|
|
6607
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
6608
|
+
import path10 from "path";
|
|
6609
|
+
import { z as z34 } from "zod";
|
|
6610
|
+
function getCacheFilePath() {
|
|
6611
|
+
return path10.join(getLightcodeDataDir(), "cache", "openrouter-models.json");
|
|
6612
|
+
}
|
|
6613
|
+
function readCache() {
|
|
6614
|
+
try {
|
|
6615
|
+
const cachePath = getCacheFilePath();
|
|
6616
|
+
if (!existsSync8(cachePath)) {
|
|
6617
|
+
return null;
|
|
6618
|
+
}
|
|
6619
|
+
return modelsCacheSchema.parse(JSON.parse(readFileSync5(cachePath, "utf8")));
|
|
6620
|
+
} catch {
|
|
6621
|
+
return null;
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
function writeCache(cache) {
|
|
6625
|
+
try {
|
|
6626
|
+
const cachePath = getCacheFilePath();
|
|
6627
|
+
mkdirSync5(path10.dirname(cachePath), { recursive: true });
|
|
6628
|
+
writeFileSync3(cachePath, JSON.stringify(cache), "utf8");
|
|
6629
|
+
} catch (error) {
|
|
6630
|
+
logger4.warn("openrouter_models_cache_write_failed", {
|
|
6631
|
+
error: getErrorMessage(error)
|
|
6632
|
+
});
|
|
6633
|
+
}
|
|
6634
|
+
}
|
|
6635
|
+
function summarizeRawModel(raw2) {
|
|
6636
|
+
if (typeof raw2 !== "object" || raw2 === null) {
|
|
6637
|
+
return null;
|
|
6638
|
+
}
|
|
6639
|
+
const id = Reflect.get(raw2, "id");
|
|
6640
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
6641
|
+
return null;
|
|
6642
|
+
}
|
|
6643
|
+
const name = Reflect.get(raw2, "name");
|
|
6644
|
+
const contextLength = Reflect.get(raw2, "context_length");
|
|
6645
|
+
const topProvider = Reflect.get(raw2, "top_provider");
|
|
6646
|
+
const topProviderMaxCompletion = typeof topProvider === "object" && topProvider !== null ? Reflect.get(topProvider, "max_completion_tokens") : undefined;
|
|
6647
|
+
const rawMaxCompletion = typeof topProviderMaxCompletion === "number" ? topProviderMaxCompletion : Reflect.get(raw2, "max_completion_tokens");
|
|
6648
|
+
const supportedParameters = Reflect.get(raw2, "supported_parameters");
|
|
6649
|
+
const parameterList = Array.isArray(supportedParameters) ? supportedParameters.filter((entry) => typeof entry === "string") : [];
|
|
6650
|
+
const pricingRaw = Reflect.get(raw2, "pricing");
|
|
6651
|
+
const pricing = typeof pricingRaw === "object" && pricingRaw !== null ? {
|
|
6652
|
+
prompt: typeof Reflect.get(pricingRaw, "prompt") === "string" ? Reflect.get(pricingRaw, "prompt") : null,
|
|
6653
|
+
completion: typeof Reflect.get(pricingRaw, "completion") === "string" ? Reflect.get(pricingRaw, "completion") : null
|
|
6654
|
+
} : null;
|
|
6655
|
+
return {
|
|
6656
|
+
id,
|
|
6657
|
+
name: typeof name === "string" && name.length > 0 ? name : id,
|
|
6658
|
+
contextLength: typeof contextLength === "number" && contextLength > 0 ? Math.round(contextLength) : null,
|
|
6659
|
+
maxCompletionTokens: typeof rawMaxCompletion === "number" && rawMaxCompletion > 0 ? Math.round(rawMaxCompletion) : null,
|
|
6660
|
+
supportsTools: parameterList.includes("tools"),
|
|
6661
|
+
supportsReasoning: parameterList.includes("reasoning"),
|
|
6662
|
+
pricing
|
|
6663
|
+
};
|
|
6664
|
+
}
|
|
6665
|
+
async function fetchModelsFromApi() {
|
|
6666
|
+
const response = await fetch(openRouterModelsUrl, {
|
|
6667
|
+
signal: AbortSignal.timeout(fetchTimeoutMs)
|
|
6668
|
+
});
|
|
6669
|
+
if (!response.ok) {
|
|
6670
|
+
throw new Error(`OpenRouter models API returned HTTP ${response.status}.`);
|
|
6671
|
+
}
|
|
6672
|
+
const payload = await response.json();
|
|
6673
|
+
const models = (payload.data ?? []).map(summarizeRawModel).filter((model) => model !== null);
|
|
6674
|
+
if (models.length === 0) {
|
|
6675
|
+
throw new Error("OpenRouter models API returned no models.");
|
|
6676
|
+
}
|
|
6677
|
+
return models;
|
|
6678
|
+
}
|
|
6679
|
+
async function listOpenRouterModels({
|
|
6680
|
+
forceRefresh = false
|
|
6681
|
+
} = {}) {
|
|
6682
|
+
const cache = readCache();
|
|
6683
|
+
if (!forceRefresh && cache && Date.now() - cache.fetchedAt < cacheTtlMs) {
|
|
6684
|
+
return { models: cache.models, fromCache: true };
|
|
6685
|
+
}
|
|
6686
|
+
try {
|
|
6687
|
+
const models = await fetchModelsFromApi();
|
|
6688
|
+
writeCache({ fetchedAt: Date.now(), models });
|
|
6689
|
+
return { models, fromCache: false };
|
|
6690
|
+
} catch (error) {
|
|
6691
|
+
logger4.warn("openrouter_models_fetch_failed", {
|
|
6692
|
+
error: getErrorMessage(error)
|
|
6693
|
+
});
|
|
6694
|
+
if (cache) {
|
|
6695
|
+
return { models: cache.models, fromCache: true };
|
|
6696
|
+
}
|
|
6697
|
+
throw error;
|
|
6698
|
+
}
|
|
6699
|
+
}
|
|
6700
|
+
async function getOpenRouterModelCapabilities(modelId) {
|
|
6701
|
+
try {
|
|
6702
|
+
const { models } = await listOpenRouterModels();
|
|
6703
|
+
const model = models.find((entry) => entry.id === modelId);
|
|
6704
|
+
if (!model) {
|
|
6705
|
+
return;
|
|
6706
|
+
}
|
|
6707
|
+
return {
|
|
6708
|
+
supportsNativeTools: model.supportsTools,
|
|
6709
|
+
contextLength: model.contextLength ?? undefined,
|
|
6710
|
+
maxCompletionTokens: model.maxCompletionTokens
|
|
6711
|
+
};
|
|
6712
|
+
} catch {
|
|
6713
|
+
return;
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6716
|
+
var logger4, openRouterModelsUrl = "https://openrouter.ai/api/v1/models", cacheTtlMs, fetchTimeoutMs = 15000, openRouterModelSummarySchema, modelsCacheSchema;
|
|
6717
|
+
var init_openrouter_models = __esm(() => {
|
|
6718
|
+
init_src();
|
|
6719
|
+
logger4 = createLogger("openrouter-models");
|
|
6720
|
+
cacheTtlMs = 24 * 60 * 60 * 1000;
|
|
6721
|
+
openRouterModelSummarySchema = z34.object({
|
|
6722
|
+
id: z34.string(),
|
|
6723
|
+
name: z34.string(),
|
|
6724
|
+
contextLength: z34.number().int().positive().nullable(),
|
|
6725
|
+
maxCompletionTokens: z34.number().int().positive().nullable().default(null),
|
|
6726
|
+
supportsTools: z34.boolean(),
|
|
6727
|
+
supportsReasoning: z34.boolean(),
|
|
6728
|
+
pricing: z34.object({
|
|
6729
|
+
prompt: z34.string().nullable(),
|
|
6730
|
+
completion: z34.string().nullable()
|
|
6731
|
+
}).nullable()
|
|
6732
|
+
});
|
|
6733
|
+
modelsCacheSchema = z34.object({
|
|
6734
|
+
fetchedAt: z34.number(),
|
|
6735
|
+
models: z34.array(openRouterModelSummarySchema)
|
|
6736
|
+
});
|
|
6202
6737
|
});
|
|
6203
6738
|
|
|
6204
6739
|
// apps/server/src/lib/provider-registry.ts
|
|
@@ -6233,6 +6768,16 @@ function getAnthropicProviderOptions(modelId) {
|
|
|
6233
6768
|
}
|
|
6234
6769
|
};
|
|
6235
6770
|
}
|
|
6771
|
+
function resolveHeadroomRouting(config5, realBaseUrl) {
|
|
6772
|
+
const headroom = config5.headroom;
|
|
6773
|
+
if (!headroom?.enabled) {
|
|
6774
|
+
return { effectiveBaseUrl: realBaseUrl, headroomRouted: false };
|
|
6775
|
+
}
|
|
6776
|
+
if (headroom.providers && !headroom.providers.includes(config5.provider)) {
|
|
6777
|
+
return { effectiveBaseUrl: realBaseUrl, headroomRouted: false };
|
|
6778
|
+
}
|
|
6779
|
+
return { effectiveBaseUrl: headroom.proxyUrl, headroomRouted: true };
|
|
6780
|
+
}
|
|
6236
6781
|
function resolveAnthropicModel({
|
|
6237
6782
|
config: config5,
|
|
6238
6783
|
env,
|
|
@@ -6243,19 +6788,22 @@ function resolveAnthropicModel({
|
|
|
6243
6788
|
const apiKey = getEnvValue(env, "ANTHROPIC_API_KEY") ?? storedCredentials.anthropicApiKey;
|
|
6244
6789
|
const authToken = getEnvValue(env, "ANTHROPIC_AUTH_TOKEN");
|
|
6245
6790
|
const baseUrl = config5.baseUrl ?? getEnvValue(env, "ANTHROPIC_BASE_URL") ?? null;
|
|
6791
|
+
const { effectiveBaseUrl, headroomRouted } = resolveHeadroomRouting(config5, baseUrl);
|
|
6246
6792
|
const missingCredentialHints = apiKey || authToken ? [] : [
|
|
6247
6793
|
"Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN to call Anthropic models."
|
|
6248
6794
|
];
|
|
6249
6795
|
const provider = createAnthropic({
|
|
6250
6796
|
apiKey,
|
|
6251
6797
|
authToken,
|
|
6252
|
-
baseURL:
|
|
6798
|
+
baseURL: effectiveBaseUrl ?? undefined
|
|
6253
6799
|
});
|
|
6254
6800
|
return {
|
|
6255
6801
|
provider: "anthropic",
|
|
6256
6802
|
configuredModel,
|
|
6257
6803
|
resolvedModelId: modelId,
|
|
6258
6804
|
baseUrl,
|
|
6805
|
+
effectiveBaseUrl,
|
|
6806
|
+
headroomRouted,
|
|
6259
6807
|
model: provider(modelId),
|
|
6260
6808
|
providerOptions: getAnthropicProviderOptions(modelId),
|
|
6261
6809
|
contextWindow: getModelContextWindow("anthropic", modelId),
|
|
@@ -6265,7 +6813,8 @@ function resolveAnthropicModel({
|
|
|
6265
6813
|
function resolveOpenAITransportModel({
|
|
6266
6814
|
config: config5,
|
|
6267
6815
|
env,
|
|
6268
|
-
storedCredentials
|
|
6816
|
+
storedCredentials,
|
|
6817
|
+
capabilities
|
|
6269
6818
|
}) {
|
|
6270
6819
|
const isOpenCodeZen = config5.provider === "opencode-zen";
|
|
6271
6820
|
const isOpenRouter = config5.provider === "openrouter";
|
|
@@ -6277,6 +6826,7 @@ function resolveOpenAITransportModel({
|
|
|
6277
6826
|
if (!baseUrl) {
|
|
6278
6827
|
throw new Error('OpenAI-compatible provider requires "baseUrl" in config or LIGHTCODE_OPENAI_COMPATIBLE_BASE_URL.');
|
|
6279
6828
|
}
|
|
6829
|
+
const { effectiveBaseUrl, headroomRouted } = resolveHeadroomRouting(config5, baseUrl);
|
|
6280
6830
|
const apiKey = isOpenCodeZen ? getEnvValue(env, "OPENCODE_API_KEY", "LIGHTCODE_OPENAI_COMPATIBLE_API_KEY", "OPENAI_API_KEY") ?? storedCredentials.opencodeApiKey : isOpenRouter ? getEnvValue(env, "OPENROUTER_API_KEY", "LIGHTCODE_OPENAI_COMPATIBLE_API_KEY", "OPENAI_API_KEY") ?? storedCredentials.openrouterApiKey : getEnvValue(env, "LIGHTCODE_OPENAI_COMPATIBLE_API_KEY", "OPENAI_API_KEY") ?? storedCredentials.openaiCompatibleApiKey;
|
|
6281
6831
|
const headers = {};
|
|
6282
6832
|
if (isOpenRouter) {
|
|
@@ -6291,7 +6841,7 @@ function resolveOpenAITransportModel({
|
|
|
6291
6841
|
}
|
|
6292
6842
|
const provider = createOpenAICompatible({
|
|
6293
6843
|
name: config5.provider,
|
|
6294
|
-
baseURL: baseUrl,
|
|
6844
|
+
baseURL: effectiveBaseUrl ?? baseUrl,
|
|
6295
6845
|
apiKey,
|
|
6296
6846
|
headers
|
|
6297
6847
|
});
|
|
@@ -6300,21 +6850,30 @@ function resolveOpenAITransportModel({
|
|
|
6300
6850
|
configuredModel,
|
|
6301
6851
|
resolvedModelId: configuredModel,
|
|
6302
6852
|
baseUrl,
|
|
6853
|
+
effectiveBaseUrl,
|
|
6854
|
+
headroomRouted,
|
|
6303
6855
|
model: withXmlToolCallSupport(provider(configuredModel)),
|
|
6304
6856
|
needsToolCallDiscipline: true,
|
|
6305
|
-
contextWindow: getModelContextWindow(config5.provider, configuredModel),
|
|
6857
|
+
contextWindow: capabilities?.contextLength ?? getModelContextWindow(config5.provider, configuredModel),
|
|
6306
6858
|
missingCredentialHints: apiKey ? [] : isOpenCodeZen ? ["Set OPENCODE_API_KEY to call OpenCode Zen models."] : isOpenRouter ? ["Set OPENROUTER_API_KEY to call OpenRouter models."] : [
|
|
6307
6859
|
"Set LIGHTCODE_OPENAI_COMPATIBLE_API_KEY or OPENAI_API_KEY if your OpenAI-compatible endpoint requires authentication."
|
|
6308
|
-
]
|
|
6860
|
+
],
|
|
6861
|
+
maxCompletionTokens: capabilities?.maxCompletionTokens ?? null
|
|
6309
6862
|
};
|
|
6310
6863
|
}
|
|
6311
6864
|
function resolveConfiguredProviderModel({
|
|
6312
6865
|
config: config5,
|
|
6313
|
-
env = Bun.env
|
|
6866
|
+
env = Bun.env,
|
|
6867
|
+
capabilities
|
|
6314
6868
|
}) {
|
|
6315
6869
|
const storedCredentials = readStoredCredentials(env);
|
|
6316
6870
|
if (config5.provider === "openai-compatible" || config5.provider === "opencode-zen" || config5.provider === "openrouter") {
|
|
6317
|
-
return resolveOpenAITransportModel({
|
|
6871
|
+
return resolveOpenAITransportModel({
|
|
6872
|
+
config: config5,
|
|
6873
|
+
env,
|
|
6874
|
+
storedCredentials,
|
|
6875
|
+
capabilities
|
|
6876
|
+
});
|
|
6318
6877
|
}
|
|
6319
6878
|
return resolveAnthropicModel({ config: config5, env, storedCredentials });
|
|
6320
6879
|
}
|
|
@@ -6331,13 +6890,18 @@ function createConfigStatus({
|
|
|
6331
6890
|
baseUrl: resolvedProviderModel.baseUrl,
|
|
6332
6891
|
defaultMode: config5.defaultMode,
|
|
6333
6892
|
permissionMode: config5.permissionMode ?? null,
|
|
6334
|
-
maxOutputTokens: config5.maxOutputTokens,
|
|
6893
|
+
maxOutputTokens: resolveMaxOutputTokens(config5.maxOutputTokens, lightcodeConfigDefaults.maxOutputTokens, resolvedProviderModel.maxCompletionTokens),
|
|
6335
6894
|
maxSteps: config5.maxSteps,
|
|
6336
6895
|
autoContinue: config5.autoContinue,
|
|
6337
6896
|
contextWindow: resolveContextWindowTokens({
|
|
6338
6897
|
config: config5.context,
|
|
6339
6898
|
modelContextWindow: resolvedProviderModel.contextWindow
|
|
6340
6899
|
}),
|
|
6900
|
+
headroom: {
|
|
6901
|
+
enabled: config5.headroom?.enabled ?? false,
|
|
6902
|
+
proxyUrl: config5.headroom?.proxyUrl ?? null,
|
|
6903
|
+
routed: resolvedProviderModel.headroomRouted
|
|
6904
|
+
},
|
|
6341
6905
|
missingCredentialHints: resolvedProviderModel.missingCredentialHints
|
|
6342
6906
|
});
|
|
6343
6907
|
}
|
|
@@ -6352,11 +6916,130 @@ var init_provider_registry = __esm(() => {
|
|
|
6352
6916
|
});
|
|
6353
6917
|
|
|
6354
6918
|
// apps/server/src/lib/runtime-config.ts
|
|
6355
|
-
var
|
|
6919
|
+
var exports_runtime_config = {};
|
|
6920
|
+
__export(exports_runtime_config, {
|
|
6921
|
+
resolvedProviderModel: () => resolvedProviderModel,
|
|
6922
|
+
lightcodeConfigResult: () => lightcodeConfigResult,
|
|
6923
|
+
initializeModelCapabilities: () => initializeModelCapabilities,
|
|
6924
|
+
disableHeadroomRouting: () => disableHeadroomRouting,
|
|
6925
|
+
configStatus: () => configStatus,
|
|
6926
|
+
codingAgent: () => codingAgent,
|
|
6927
|
+
chatModelId: () => chatModelId,
|
|
6928
|
+
applyModelSelection: () => applyModelSelection
|
|
6929
|
+
});
|
|
6930
|
+
function buildCodingAgent(config5, model) {
|
|
6931
|
+
return createCodingAgent({
|
|
6932
|
+
model: model.model,
|
|
6933
|
+
promptOverride: codingAgentPromptOverride,
|
|
6934
|
+
providerOptions: model.providerOptions,
|
|
6935
|
+
maxSteps: config5.maxSteps,
|
|
6936
|
+
maxRetries: config5.maxRetries,
|
|
6937
|
+
maxOutputTokens: resolveMaxOutputTokens(config5.maxOutputTokens, lightcodeConfigDefaults.maxOutputTokens, model.maxCompletionTokens),
|
|
6938
|
+
includeToolDiscipline: model.needsToolCallDiscipline ?? false
|
|
6939
|
+
});
|
|
6940
|
+
}
|
|
6941
|
+
function applyModelSelection({
|
|
6942
|
+
provider,
|
|
6943
|
+
model,
|
|
6944
|
+
capabilities,
|
|
6945
|
+
persist = true
|
|
6946
|
+
}) {
|
|
6947
|
+
const nextConfig = {
|
|
6948
|
+
...lightcodeConfigResult.config,
|
|
6949
|
+
provider,
|
|
6950
|
+
model
|
|
6951
|
+
};
|
|
6952
|
+
const nextResolved = resolveConfiguredProviderModel({
|
|
6953
|
+
config: nextConfig,
|
|
6954
|
+
env: Bun.env,
|
|
6955
|
+
capabilities
|
|
6956
|
+
});
|
|
6957
|
+
const nextAgent = buildCodingAgent(nextConfig, nextResolved);
|
|
6958
|
+
if (persist) {
|
|
6959
|
+
const { path: path11 } = updateUserSettings({ provider, model }, Bun.env);
|
|
6960
|
+
logger5.info("model_selection_persisted", { path: path11, provider, model });
|
|
6961
|
+
}
|
|
6962
|
+
lightcodeConfigResult = {
|
|
6963
|
+
...lightcodeConfigResult,
|
|
6964
|
+
config: nextConfig
|
|
6965
|
+
};
|
|
6966
|
+
resolvedProviderModel = nextResolved;
|
|
6967
|
+
chatModelId = nextResolved.resolvedModelId;
|
|
6968
|
+
codingAgent = nextAgent;
|
|
6969
|
+
configStatus = createConfigStatus({
|
|
6970
|
+
config: nextConfig,
|
|
6971
|
+
loadedFiles: lightcodeConfigResult.loadedFiles,
|
|
6972
|
+
resolvedProviderModel: nextResolved
|
|
6973
|
+
});
|
|
6974
|
+
logger5.info("model_selection_applied", {
|
|
6975
|
+
provider,
|
|
6976
|
+
model,
|
|
6977
|
+
contextWindow: nextResolved.contextWindow,
|
|
6978
|
+
supportsNativeTools: capabilities?.supportsNativeTools ?? null
|
|
6979
|
+
});
|
|
6980
|
+
return configStatus;
|
|
6981
|
+
}
|
|
6982
|
+
function disableHeadroomRouting() {
|
|
6983
|
+
if (!lightcodeConfigResult.config.headroom?.enabled) {
|
|
6984
|
+
return;
|
|
6985
|
+
}
|
|
6986
|
+
const nextConfig = {
|
|
6987
|
+
...lightcodeConfigResult.config,
|
|
6988
|
+
headroom: {
|
|
6989
|
+
...lightcodeConfigResult.config.headroom ?? lightcodeConfigDefaults.headroom,
|
|
6990
|
+
enabled: false
|
|
6991
|
+
}
|
|
6992
|
+
};
|
|
6993
|
+
const nextResolved = resolveConfiguredProviderModel({
|
|
6994
|
+
config: nextConfig,
|
|
6995
|
+
env: Bun.env
|
|
6996
|
+
});
|
|
6997
|
+
const nextAgent = buildCodingAgent(nextConfig, nextResolved);
|
|
6998
|
+
lightcodeConfigResult = {
|
|
6999
|
+
...lightcodeConfigResult,
|
|
7000
|
+
config: nextConfig
|
|
7001
|
+
};
|
|
7002
|
+
resolvedProviderModel = nextResolved;
|
|
7003
|
+
chatModelId = nextResolved.resolvedModelId;
|
|
7004
|
+
codingAgent = nextAgent;
|
|
7005
|
+
configStatus = createConfigStatus({
|
|
7006
|
+
config: nextConfig,
|
|
7007
|
+
loadedFiles: lightcodeConfigResult.loadedFiles,
|
|
7008
|
+
resolvedProviderModel: nextResolved
|
|
7009
|
+
});
|
|
7010
|
+
logger5.info("headroom_routing_disabled", {
|
|
7011
|
+
provider: nextResolved.provider,
|
|
7012
|
+
model: nextResolved.resolvedModelId
|
|
7013
|
+
});
|
|
7014
|
+
}
|
|
7015
|
+
async function initializeModelCapabilities() {
|
|
7016
|
+
if (lightcodeConfigResult.config.provider !== "openrouter") {
|
|
7017
|
+
return;
|
|
7018
|
+
}
|
|
7019
|
+
const modelId = lightcodeConfigResult.config.model;
|
|
7020
|
+
if (!modelId) {
|
|
7021
|
+
return;
|
|
7022
|
+
}
|
|
7023
|
+
const capabilities = await getOpenRouterModelCapabilities(modelId);
|
|
7024
|
+
if (!capabilities || chatModelId !== modelId) {
|
|
7025
|
+
return;
|
|
7026
|
+
}
|
|
7027
|
+
applyModelSelection({
|
|
7028
|
+
provider: "openrouter",
|
|
7029
|
+
model: modelId,
|
|
7030
|
+
capabilities,
|
|
7031
|
+
persist: false
|
|
7032
|
+
});
|
|
7033
|
+
}
|
|
7034
|
+
var logger5, codingAgentPromptOverride, lightcodeConfigResult, resolvedProviderModel, configStatus, chatModelId, codingAgent;
|
|
6356
7035
|
var init_runtime_config = __esm(() => {
|
|
6357
7036
|
init_src2();
|
|
7037
|
+
init_src();
|
|
7038
|
+
init_openrouter_models();
|
|
6358
7039
|
init_provider_registry();
|
|
7040
|
+
logger5 = createLogger("runtime-config");
|
|
6359
7041
|
assertProviderToolSchemaBudget();
|
|
7042
|
+
codingAgentPromptOverride = Bun.env.LIGHTCODE_CODING_AGENT_SYSTEM_PROMPT;
|
|
6360
7043
|
lightcodeConfigResult = loadLightcodeConfig({
|
|
6361
7044
|
cwd: process.cwd(),
|
|
6362
7045
|
env: Bun.env
|
|
@@ -6371,15 +7054,7 @@ var init_runtime_config = __esm(() => {
|
|
|
6371
7054
|
resolvedProviderModel
|
|
6372
7055
|
});
|
|
6373
7056
|
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
|
-
});
|
|
7057
|
+
codingAgent = buildCodingAgent(lightcodeConfigResult.config, resolvedProviderModel);
|
|
6383
7058
|
});
|
|
6384
7059
|
|
|
6385
7060
|
// apps/server/src/lib/session-auto-title.ts
|
|
@@ -6415,7 +7090,7 @@ Response: ${firstAssistantText.slice(0, AUTO_TITLE_INPUT_PREVIEW_CHARS)}`,
|
|
|
6415
7090
|
});
|
|
6416
7091
|
const title = sanitizeGeneratedTitle(result.text);
|
|
6417
7092
|
if (title && await applyGeneratedSessionTitle(sessionId, title)) {
|
|
6418
|
-
|
|
7093
|
+
logger6.info("session_auto_titled", { sessionId, title, model: chatModelId });
|
|
6419
7094
|
}
|
|
6420
7095
|
}
|
|
6421
7096
|
function maybeScheduleSessionAutoTitle({
|
|
@@ -6423,20 +7098,20 @@ function maybeScheduleSessionAutoTitle({
|
|
|
6423
7098
|
messages
|
|
6424
7099
|
}) {
|
|
6425
7100
|
generateSessionTitle({ sessionId, messages }).catch((error) => {
|
|
6426
|
-
|
|
7101
|
+
logger6.warn("session_auto_title_failed", {
|
|
6427
7102
|
sessionId,
|
|
6428
7103
|
error: getErrorMessage(error)
|
|
6429
7104
|
});
|
|
6430
7105
|
});
|
|
6431
7106
|
}
|
|
6432
|
-
var
|
|
7107
|
+
var logger6, AUTO_TITLE_TIMEOUT_MS = 15000, AUTO_TITLE_MAX_OUTPUT_TOKENS = 24, AUTO_TITLE_INPUT_PREVIEW_CHARS = 600;
|
|
6433
7108
|
var init_session_auto_title = __esm(() => {
|
|
6434
7109
|
init_src2();
|
|
6435
7110
|
init_src();
|
|
6436
7111
|
init_chat_store();
|
|
6437
7112
|
init_prisma_client();
|
|
6438
7113
|
init_runtime_config();
|
|
6439
|
-
|
|
7114
|
+
logger6 = createLogger("session-auto-title");
|
|
6440
7115
|
});
|
|
6441
7116
|
|
|
6442
7117
|
// apps/server/src/lib/chat-stream.ts
|
|
@@ -6485,7 +7160,7 @@ async function countPendingChatInteractions(sessionId) {
|
|
|
6485
7160
|
});
|
|
6486
7161
|
return pendingInteractions.interactions.length;
|
|
6487
7162
|
} catch (error) {
|
|
6488
|
-
|
|
7163
|
+
logger7.warn("context_compaction_pending_interactions_unavailable", {
|
|
6489
7164
|
sessionId,
|
|
6490
7165
|
error: getErrorMessage(error)
|
|
6491
7166
|
});
|
|
@@ -6496,7 +7171,7 @@ async function loadSessionContextStateSafe(sessionId) {
|
|
|
6496
7171
|
try {
|
|
6497
7172
|
return await getSessionContextState(sessionId);
|
|
6498
7173
|
} catch (error) {
|
|
6499
|
-
|
|
7174
|
+
logger7.warn("context_state_unavailable", {
|
|
6500
7175
|
sessionId,
|
|
6501
7176
|
error: getErrorMessage(error)
|
|
6502
7177
|
});
|
|
@@ -6545,7 +7220,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6545
7220
|
staleSkip: false
|
|
6546
7221
|
});
|
|
6547
7222
|
} catch (error) {
|
|
6548
|
-
|
|
7223
|
+
logger7.error("chat_pre_stream_persist_failed", {
|
|
6549
7224
|
sessionId,
|
|
6550
7225
|
error: getErrorMessage(error)
|
|
6551
7226
|
});
|
|
@@ -6585,7 +7260,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6585
7260
|
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
6586
7261
|
pendingInteractionCount
|
|
6587
7262
|
});
|
|
6588
|
-
|
|
7263
|
+
logger7.info("context_auto_compacted", {
|
|
6589
7264
|
sessionId,
|
|
6590
7265
|
tier: compaction.state.tier,
|
|
6591
7266
|
usedFallback: compaction.usedFallback,
|
|
@@ -6594,13 +7269,70 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6594
7269
|
estimatedTokensAfter: view.estimate.tokens
|
|
6595
7270
|
});
|
|
6596
7271
|
} catch (error) {
|
|
6597
|
-
|
|
7272
|
+
logger7.error("context_compaction_failed", {
|
|
6598
7273
|
sessionId,
|
|
6599
7274
|
error: getErrorMessage(error)
|
|
6600
7275
|
});
|
|
6601
7276
|
}
|
|
6602
7277
|
}
|
|
6603
|
-
const
|
|
7278
|
+
const overflowPreserve = getOverflowPreserveRecentMessages(sessionId);
|
|
7279
|
+
if (overflowPreserve !== null) {
|
|
7280
|
+
const overflowConfig = {
|
|
7281
|
+
...contextConfig,
|
|
7282
|
+
preserveRecentMessages: Math.min(overflowPreserve, contextConfig.preserveRecentMessages)
|
|
7283
|
+
};
|
|
7284
|
+
try {
|
|
7285
|
+
const overflowView = buildProviderView({
|
|
7286
|
+
messages: validatedMessages,
|
|
7287
|
+
contextState,
|
|
7288
|
+
config: overflowConfig,
|
|
7289
|
+
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
7290
|
+
pendingInteractionCount: 0
|
|
7291
|
+
});
|
|
7292
|
+
if (overflowView.coveredMessages.length > 0) {
|
|
7293
|
+
const compaction = await compactSessionContext({
|
|
7294
|
+
sessionId,
|
|
7295
|
+
coveredMessages: overflowView.coveredMessages,
|
|
7296
|
+
previousState: contextState,
|
|
7297
|
+
model: resolvedProviderModel.model,
|
|
7298
|
+
modelId: chatModelId,
|
|
7299
|
+
cwd,
|
|
7300
|
+
config: overflowConfig,
|
|
7301
|
+
estimatedTokens: overflowView.estimate.tokens
|
|
7302
|
+
});
|
|
7303
|
+
contextState = compaction.state;
|
|
7304
|
+
}
|
|
7305
|
+
view = buildProviderView({
|
|
7306
|
+
messages: validatedMessages,
|
|
7307
|
+
contextState,
|
|
7308
|
+
config: overflowConfig,
|
|
7309
|
+
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
7310
|
+
pendingInteractionCount: 0
|
|
7311
|
+
});
|
|
7312
|
+
logger7.info("context_overflow_recovery", {
|
|
7313
|
+
sessionId,
|
|
7314
|
+
preserveRecentMessages: overflowConfig.preserveRecentMessages,
|
|
7315
|
+
estimatedTokens: view.estimate.tokens,
|
|
7316
|
+
contextWindow: view.contextWindow,
|
|
7317
|
+
withinWindow: view.estimate.tokens < view.contextWindow,
|
|
7318
|
+
compactedMessages: view.coveredMessages.length
|
|
7319
|
+
});
|
|
7320
|
+
} catch (error) {
|
|
7321
|
+
logger7.error("context_overflow_recovery_failed", {
|
|
7322
|
+
sessionId,
|
|
7323
|
+
error: getErrorMessage(error)
|
|
7324
|
+
});
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
const providerMessages = normalizeProviderMessages(view.providerMessages);
|
|
7328
|
+
let overflowNotedThisRequest = false;
|
|
7329
|
+
const noteOverflowForSession = () => {
|
|
7330
|
+
if (overflowNotedThisRequest) {
|
|
7331
|
+
return;
|
|
7332
|
+
}
|
|
7333
|
+
overflowNotedThisRequest = true;
|
|
7334
|
+
noteContextOverflow(sessionId);
|
|
7335
|
+
};
|
|
6604
7336
|
const persistFinishedMessages = async ({
|
|
6605
7337
|
isAborted,
|
|
6606
7338
|
isContinuation,
|
|
@@ -6610,6 +7342,9 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6610
7342
|
if (isAborted) {
|
|
6611
7343
|
return;
|
|
6612
7344
|
}
|
|
7345
|
+
if (!overflowNotedThisRequest) {
|
|
7346
|
+
clearContextOverflowRounds(sessionId);
|
|
7347
|
+
}
|
|
6613
7348
|
const fullHistory = mergeFinishedMessagesIntoFullHistory({
|
|
6614
7349
|
fullMessages: validatedMessages,
|
|
6615
7350
|
providerMessageCount: providerMessages.length,
|
|
@@ -6619,14 +7354,14 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6619
7354
|
});
|
|
6620
7355
|
const normalizedMessages = removeTrailingEmptyAssistantMessages(fullHistory);
|
|
6621
7356
|
if (normalizedMessages.length !== fullHistory.length) {
|
|
6622
|
-
|
|
7357
|
+
logger7.warn("chat_finish_empty_assistant_skipped", { sessionId });
|
|
6623
7358
|
return;
|
|
6624
7359
|
}
|
|
6625
7360
|
const validatedFinishResult = await safeValidateUIMessages2({
|
|
6626
7361
|
messages: normalizedMessages
|
|
6627
7362
|
});
|
|
6628
7363
|
if (!validatedFinishResult.success) {
|
|
6629
|
-
|
|
7364
|
+
logger7.warn("chat_invalid_finish_payload", {
|
|
6630
7365
|
sessionId,
|
|
6631
7366
|
message: validatedFinishResult.error.message
|
|
6632
7367
|
});
|
|
@@ -6660,7 +7395,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6660
7395
|
messages: validatedFinishResult.data
|
|
6661
7396
|
});
|
|
6662
7397
|
} catch (error) {
|
|
6663
|
-
|
|
7398
|
+
logger7.error("chat_finish_persist_failed", {
|
|
6664
7399
|
sessionId,
|
|
6665
7400
|
error: getErrorMessage(error)
|
|
6666
7401
|
});
|
|
@@ -6692,7 +7427,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6692
7427
|
mode,
|
|
6693
7428
|
allowedTools
|
|
6694
7429
|
})) {
|
|
6695
|
-
|
|
7430
|
+
logger7.info("chat_fast_path", {
|
|
6696
7431
|
sessionId,
|
|
6697
7432
|
model: chatModelId
|
|
6698
7433
|
});
|
|
@@ -6701,6 +7436,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6701
7436
|
system: fastChatSystemPrompt,
|
|
6702
7437
|
messages: buildFastChatModelMessages(providerMessages),
|
|
6703
7438
|
maxOutputTokens: Math.min(lightcodeConfigResult.config.maxOutputTokens, fastChatMaxOutputTokens),
|
|
7439
|
+
maxRetries: lightcodeConfigResult.config.maxRetries,
|
|
6704
7440
|
providerOptions: resolvedProviderModel.providerOptions,
|
|
6705
7441
|
abortSignal: c.req.raw.signal
|
|
6706
7442
|
});
|
|
@@ -6719,50 +7455,46 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6719
7455
|
}
|
|
6720
7456
|
}
|
|
6721
7457
|
});
|
|
6722
|
-
return result.toUIMessageStreamResponse({
|
|
7458
|
+
return withSseHeartbeat(result.toUIMessageStreamResponse({
|
|
6723
7459
|
originalMessages: providerMessages,
|
|
6724
7460
|
generateMessageId: generateId3,
|
|
6725
7461
|
sendReasoning: false,
|
|
6726
7462
|
messageMetadata: buildUsageMessageMetadata,
|
|
6727
7463
|
onError: (error) => {
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
});
|
|
7464
|
+
const classified = classifyChatError(error);
|
|
7465
|
+
if (classified.kind === "context_overflow") {
|
|
7466
|
+
noteOverflowForSession();
|
|
6732
7467
|
}
|
|
6733
|
-
|
|
7468
|
+
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), { sessionId, statusCode: classified.statusCode });
|
|
7469
|
+
logChatStreamErrorEvent({
|
|
7470
|
+
sessionId,
|
|
7471
|
+
phase: "stream",
|
|
7472
|
+
classified,
|
|
7473
|
+
error
|
|
7474
|
+
});
|
|
7475
|
+
return formatChatStreamError(classified);
|
|
6734
7476
|
},
|
|
6735
7477
|
onFinish: persistFinishedMessages
|
|
6736
|
-
});
|
|
7478
|
+
}));
|
|
6737
7479
|
}
|
|
6738
7480
|
const mapStreamError = (error) => {
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
});
|
|
6743
|
-
return genericRecoverableMessage;
|
|
7481
|
+
const classified = classifyChatError(error);
|
|
7482
|
+
if (classified.kind === "context_overflow") {
|
|
7483
|
+
noteOverflowForSession();
|
|
6744
7484
|
}
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
logChatDisconnectEvent({
|
|
6757
|
-
sessionId,
|
|
6758
|
-
phase: "stream",
|
|
6759
|
-
error
|
|
6760
|
-
});
|
|
6761
|
-
return recoverableDisconnectMessage;
|
|
6762
|
-
}
|
|
6763
|
-
return genericRecoverableMessage;
|
|
7485
|
+
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), {
|
|
7486
|
+
sessionId,
|
|
7487
|
+
statusCode: classified.statusCode
|
|
7488
|
+
});
|
|
7489
|
+
logChatStreamErrorEvent({
|
|
7490
|
+
sessionId,
|
|
7491
|
+
phase: "stream",
|
|
7492
|
+
classified,
|
|
7493
|
+
error
|
|
7494
|
+
});
|
|
7495
|
+
return formatChatStreamError(classified);
|
|
6764
7496
|
};
|
|
6765
|
-
return await createAgentUIStreamResponse({
|
|
7497
|
+
return withSseHeartbeat(await createAgentUIStreamResponse({
|
|
6766
7498
|
agent: codingAgent,
|
|
6767
7499
|
uiMessages: providerMessages,
|
|
6768
7500
|
options: {
|
|
@@ -6797,24 +7529,27 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6797
7529
|
},
|
|
6798
7530
|
onError: mapStreamError,
|
|
6799
7531
|
onFinish: persistFinishedMessages
|
|
6800
|
-
});
|
|
7532
|
+
}));
|
|
6801
7533
|
} catch (error) {
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
});
|
|
7534
|
+
const classified = classifyChatError(error);
|
|
7535
|
+
if (classified.kind === "context_overflow") {
|
|
7536
|
+
noteContextOverflow(sessionId);
|
|
6806
7537
|
}
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
7538
|
+
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), {
|
|
7539
|
+
sessionId,
|
|
7540
|
+
phase: "pre-stream",
|
|
7541
|
+
statusCode: classified.statusCode
|
|
7542
|
+
});
|
|
7543
|
+
logChatStreamErrorEvent({
|
|
7544
|
+
sessionId,
|
|
7545
|
+
phase: "pre-stream",
|
|
7546
|
+
classified,
|
|
7547
|
+
error
|
|
7548
|
+
});
|
|
7549
|
+
if (classified.kind === "billing") {
|
|
6811
7550
|
return c.json({ error: providerBillingOrQuotaMessage }, 402);
|
|
6812
7551
|
}
|
|
6813
|
-
if (
|
|
6814
|
-
incrementChatFailureCounter("timeout_disconnect", {
|
|
6815
|
-
sessionId,
|
|
6816
|
-
phase: "pre-stream"
|
|
6817
|
-
});
|
|
7552
|
+
if (classified.kind === "network" || classified.kind === "aborted") {
|
|
6818
7553
|
logChatDisconnectEvent({
|
|
6819
7554
|
sessionId,
|
|
6820
7555
|
phase: "pre-stream",
|
|
@@ -6823,12 +7558,12 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
6823
7558
|
return c.json({ error: recoverableDisconnectMessage }, 503);
|
|
6824
7559
|
}
|
|
6825
7560
|
return c.json({
|
|
6826
|
-
error:
|
|
7561
|
+
error: formatChatStreamError(classified),
|
|
6827
7562
|
details: Bun.env.NODE_ENV === "production" ? undefined : getErrorMessage(error)
|
|
6828
7563
|
}, 500);
|
|
6829
7564
|
}
|
|
6830
7565
|
}
|
|
6831
|
-
var
|
|
7566
|
+
var logger7, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, fastChatRecentMessageCount = 6;
|
|
6832
7567
|
var init_chat_stream = __esm(() => {
|
|
6833
7568
|
init_src2();
|
|
6834
7569
|
init_src();
|
|
@@ -6836,10 +7571,12 @@ var init_chat_stream = __esm(() => {
|
|
|
6836
7571
|
init_chat_interaction_store();
|
|
6837
7572
|
init_chat_store();
|
|
6838
7573
|
init_context_compaction();
|
|
7574
|
+
init_overflow_recovery();
|
|
7575
|
+
init_sse_heartbeat();
|
|
6839
7576
|
init_context_state_store();
|
|
6840
7577
|
init_session_auto_title();
|
|
6841
7578
|
init_runtime_config();
|
|
6842
|
-
|
|
7579
|
+
logger7 = createLogger("chat-stream");
|
|
6843
7580
|
providerBillingOrQuotaMessage = "The configured model provider rejected this request due to billing or quota limits. " + "Update provider credits/quota and retry.";
|
|
6844
7581
|
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
7582
|
});
|
|
@@ -6850,16 +7587,16 @@ function internalErrorResponse(c, {
|
|
|
6850
7587
|
message,
|
|
6851
7588
|
error
|
|
6852
7589
|
}) {
|
|
6853
|
-
|
|
7590
|
+
logger8.error(event, { error: getErrorMessage(error) });
|
|
6854
7591
|
return c.json({
|
|
6855
7592
|
error: message,
|
|
6856
7593
|
details: Bun.env.NODE_ENV === "production" ? undefined : getErrorMessage(error)
|
|
6857
7594
|
}, 500);
|
|
6858
7595
|
}
|
|
6859
|
-
var
|
|
7596
|
+
var logger8;
|
|
6860
7597
|
var init_route_helpers = __esm(() => {
|
|
6861
7598
|
init_src();
|
|
6862
|
-
|
|
7599
|
+
logger8 = createLogger("routes");
|
|
6863
7600
|
});
|
|
6864
7601
|
|
|
6865
7602
|
// apps/server/src/routes/chat-routes.ts
|
|
@@ -7141,6 +7878,59 @@ var init_chat_routes = __esm(() => {
|
|
|
7141
7878
|
});
|
|
7142
7879
|
});
|
|
7143
7880
|
|
|
7881
|
+
// apps/server/src/routes/config-routes.ts
|
|
7882
|
+
import { Hono as Hono2 } from "hono";
|
|
7883
|
+
import { z as z35 } from "zod";
|
|
7884
|
+
var selectModelRequestSchema, configRoutes;
|
|
7885
|
+
var init_config_routes = __esm(() => {
|
|
7886
|
+
init_src2();
|
|
7887
|
+
init_src();
|
|
7888
|
+
init_openrouter_models();
|
|
7889
|
+
init_runtime_config();
|
|
7890
|
+
selectModelRequestSchema = z35.object({
|
|
7891
|
+
provider: lightcodeProviderSchema,
|
|
7892
|
+
model: z35.string().min(1).max(240)
|
|
7893
|
+
});
|
|
7894
|
+
configRoutes = new Hono2().get("/status", (c) => c.json(configStatus)).get("/models", async (c) => {
|
|
7895
|
+
const provider = c.req.query("provider") ?? "openrouter";
|
|
7896
|
+
if (provider !== "openrouter") {
|
|
7897
|
+
return c.json({ error: `Model listing is not supported for provider "${provider}".` }, 400);
|
|
7898
|
+
}
|
|
7899
|
+
try {
|
|
7900
|
+
const { models, fromCache } = await listOpenRouterModels({
|
|
7901
|
+
forceRefresh: c.req.query("refresh") === "1"
|
|
7902
|
+
});
|
|
7903
|
+
return c.json({
|
|
7904
|
+
generatedAt: new Date().toISOString(),
|
|
7905
|
+
provider,
|
|
7906
|
+
fromCache,
|
|
7907
|
+
models
|
|
7908
|
+
});
|
|
7909
|
+
} catch (error) {
|
|
7910
|
+
return c.json({
|
|
7911
|
+
error: `Unable to load OpenRouter models: ${getErrorMessage(error)}`
|
|
7912
|
+
}, 502);
|
|
7913
|
+
}
|
|
7914
|
+
}).put("/model", async (c) => {
|
|
7915
|
+
const parsed = selectModelRequestSchema.safeParse(await c.req.json().catch(() => null));
|
|
7916
|
+
if (!parsed.success) {
|
|
7917
|
+
return c.json({ error: "Invalid model selection payload." }, 400);
|
|
7918
|
+
}
|
|
7919
|
+
const { provider, model } = parsed.data;
|
|
7920
|
+
try {
|
|
7921
|
+
const capabilities = provider === "openrouter" ? await getOpenRouterModelCapabilities(model) : undefined;
|
|
7922
|
+
const status = applyModelSelection({ provider, model, capabilities });
|
|
7923
|
+
return c.json({
|
|
7924
|
+
ok: true,
|
|
7925
|
+
configStatus: status,
|
|
7926
|
+
capabilities: capabilities ?? null
|
|
7927
|
+
});
|
|
7928
|
+
} catch (error) {
|
|
7929
|
+
return c.json({ error: `Unable to switch model: ${getErrorMessage(error)}` }, 400);
|
|
7930
|
+
}
|
|
7931
|
+
});
|
|
7932
|
+
});
|
|
7933
|
+
|
|
7144
7934
|
// apps/server/src/lib/extension-runtime.ts
|
|
7145
7935
|
function getExtensionRuntimeStatus() {
|
|
7146
7936
|
const skills = listSkills({ cwd: process.cwd() });
|
|
@@ -7171,8 +7961,9 @@ var init_extension_runtime = __esm(() => {
|
|
|
7171
7961
|
});
|
|
7172
7962
|
|
|
7173
7963
|
// apps/server/src/routes/diagnostics-routes.ts
|
|
7174
|
-
import { existsSync as
|
|
7175
|
-
import
|
|
7964
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
7965
|
+
import path11 from "path";
|
|
7966
|
+
import { Hono as Hono3 } from "hono";
|
|
7176
7967
|
function combineStatuses(statuses) {
|
|
7177
7968
|
if (statuses.includes("error")) {
|
|
7178
7969
|
return "error";
|
|
@@ -7305,8 +8096,8 @@ function checkWorkspace() {
|
|
|
7305
8096
|
return {
|
|
7306
8097
|
id: "workspace",
|
|
7307
8098
|
label: "Workspace",
|
|
7308
|
-
status:
|
|
7309
|
-
summary:
|
|
8099
|
+
status: existsSync9(cwd) ? "ok" : "error",
|
|
8100
|
+
summary: existsSync9(cwd) ? `Workspace is accessible: ${cwd}` : `Workspace is missing: ${cwd}`,
|
|
7310
8101
|
details: []
|
|
7311
8102
|
};
|
|
7312
8103
|
}
|
|
@@ -7427,7 +8218,83 @@ async function buildPermissionsPayload() {
|
|
|
7427
8218
|
notes
|
|
7428
8219
|
});
|
|
7429
8220
|
}
|
|
7430
|
-
|
|
8221
|
+
async function buildConnectivityPayload() {
|
|
8222
|
+
const baseUrl = resolvedProviderModel.baseUrl ?? (resolvedProviderModel.provider === "anthropic" ? "https://api.anthropic.com/v1" : null);
|
|
8223
|
+
const proxyEnvironment = {
|
|
8224
|
+
httpsProxy: Boolean(Bun.env.HTTPS_PROXY ?? Bun.env.https_proxy),
|
|
8225
|
+
httpProxy: Boolean(Bun.env.HTTP_PROXY ?? Bun.env.http_proxy),
|
|
8226
|
+
noProxy: Boolean(Bun.env.NO_PROXY ?? Bun.env.no_proxy),
|
|
8227
|
+
extraCaCerts: Boolean(Bun.env.NODE_EXTRA_CA_CERTS)
|
|
8228
|
+
};
|
|
8229
|
+
if (!baseUrl) {
|
|
8230
|
+
return {
|
|
8231
|
+
generatedAt: new Date().toISOString(),
|
|
8232
|
+
provider: resolvedProviderModel.provider,
|
|
8233
|
+
target: null,
|
|
8234
|
+
reachable: false,
|
|
8235
|
+
statusCode: null,
|
|
8236
|
+
latencyMs: null,
|
|
8237
|
+
error: "No provider base URL is configured.",
|
|
8238
|
+
proxyEnvironment
|
|
8239
|
+
};
|
|
8240
|
+
}
|
|
8241
|
+
const target = new URL("models", baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
8242
|
+
const startedAt = performance.now();
|
|
8243
|
+
try {
|
|
8244
|
+
const response = await fetch(target, {
|
|
8245
|
+
signal: AbortSignal.timeout(connectivityProbeTimeoutMs)
|
|
8246
|
+
});
|
|
8247
|
+
return {
|
|
8248
|
+
generatedAt: new Date().toISOString(),
|
|
8249
|
+
provider: resolvedProviderModel.provider,
|
|
8250
|
+
target: target.toString(),
|
|
8251
|
+
reachable: true,
|
|
8252
|
+
statusCode: response.status,
|
|
8253
|
+
latencyMs: Math.round(performance.now() - startedAt),
|
|
8254
|
+
error: null,
|
|
8255
|
+
proxyEnvironment
|
|
8256
|
+
};
|
|
8257
|
+
} catch (error) {
|
|
8258
|
+
return {
|
|
8259
|
+
generatedAt: new Date().toISOString(),
|
|
8260
|
+
provider: resolvedProviderModel.provider,
|
|
8261
|
+
target: target.toString(),
|
|
8262
|
+
reachable: false,
|
|
8263
|
+
statusCode: null,
|
|
8264
|
+
latencyMs: Math.round(performance.now() - startedAt),
|
|
8265
|
+
error: getErrorMessage(error),
|
|
8266
|
+
proxyEnvironment
|
|
8267
|
+
};
|
|
8268
|
+
}
|
|
8269
|
+
}
|
|
8270
|
+
async function buildLogsTailPayload(requestedLines) {
|
|
8271
|
+
const directory = getLogDirectory();
|
|
8272
|
+
const lineCount = Math.min(Math.max(Number.isFinite(requestedLines) ? requestedLines : 200, 1), maxLogTailLines);
|
|
8273
|
+
let latestFile = null;
|
|
8274
|
+
try {
|
|
8275
|
+
const logFiles = readdirSync4(directory).filter((name) => /^server-\d{4}-\d{2}-\d{2}\.log$/.test(name)).sort();
|
|
8276
|
+
latestFile = logFiles.at(-1) ?? null;
|
|
8277
|
+
} catch {
|
|
8278
|
+
latestFile = null;
|
|
8279
|
+
}
|
|
8280
|
+
if (!latestFile) {
|
|
8281
|
+
return {
|
|
8282
|
+
generatedAt: new Date().toISOString(),
|
|
8283
|
+
file: null,
|
|
8284
|
+
lines: []
|
|
8285
|
+
};
|
|
8286
|
+
}
|
|
8287
|
+
const filePath = path11.join(directory, latestFile);
|
|
8288
|
+
const content = await Bun.file(filePath).text();
|
|
8289
|
+
const lines = content.split(`
|
|
8290
|
+
`).filter((line) => line.length > 0);
|
|
8291
|
+
return {
|
|
8292
|
+
generatedAt: new Date().toISOString(),
|
|
8293
|
+
file: filePath,
|
|
8294
|
+
lines: lines.slice(-lineCount)
|
|
8295
|
+
};
|
|
8296
|
+
}
|
|
8297
|
+
var connectivityProbeTimeoutMs = 1e4, maxLogTailLines = 1000, diagnosticsRoutes;
|
|
7431
8298
|
var init_diagnostics_routes = __esm(() => {
|
|
7432
8299
|
init_src2();
|
|
7433
8300
|
init_src();
|
|
@@ -7436,7 +8303,7 @@ var init_diagnostics_routes = __esm(() => {
|
|
|
7436
8303
|
init_prisma_client();
|
|
7437
8304
|
init_runtime_config();
|
|
7438
8305
|
init_extension_runtime();
|
|
7439
|
-
diagnosticsRoutes = new
|
|
8306
|
+
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
8307
|
const toolDiagnostics = getToolDiagnostics();
|
|
7441
8308
|
return c.json(diagnosticsToolsResponseSchema.parse({
|
|
7442
8309
|
generatedAt: new Date().toISOString(),
|
|
@@ -7448,19 +8315,19 @@ var init_diagnostics_routes = __esm(() => {
|
|
|
7448
8315
|
|
|
7449
8316
|
// apps/server/src/routes/extension-routes.ts
|
|
7450
8317
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
7451
|
-
import { Hono as
|
|
7452
|
-
import { z as
|
|
8318
|
+
import { Hono as Hono4 } from "hono";
|
|
8319
|
+
import { z as z36 } from "zod";
|
|
7453
8320
|
var skillParamsSchema, mcpServerParamsSchema, extensionRoutes;
|
|
7454
8321
|
var init_extension_routes = __esm(() => {
|
|
7455
8322
|
init_src2();
|
|
7456
8323
|
init_extension_runtime();
|
|
7457
|
-
skillParamsSchema =
|
|
7458
|
-
name:
|
|
8324
|
+
skillParamsSchema = z36.object({
|
|
8325
|
+
name: z36.string().min(1).max(160)
|
|
7459
8326
|
});
|
|
7460
|
-
mcpServerParamsSchema =
|
|
7461
|
-
name:
|
|
8327
|
+
mcpServerParamsSchema = z36.object({
|
|
8328
|
+
name: z36.string().min(1).max(120)
|
|
7462
8329
|
});
|
|
7463
|
-
extensionRoutes = new
|
|
8330
|
+
extensionRoutes = new Hono4().get("/skills", (c) => {
|
|
7464
8331
|
return c.json(skillListOutputSchema.parse({
|
|
7465
8332
|
skills: listSkills({ cwd: process.cwd() })
|
|
7466
8333
|
}));
|
|
@@ -7481,18 +8348,15 @@ var init_extension_routes = __esm(() => {
|
|
|
7481
8348
|
});
|
|
7482
8349
|
|
|
7483
8350
|
// apps/server/src/routes/root-routes.ts
|
|
7484
|
-
import { Hono as
|
|
8351
|
+
import { Hono as Hono5 } from "hono";
|
|
7485
8352
|
var rootRoutes;
|
|
7486
8353
|
var init_root_routes = __esm(() => {
|
|
7487
8354
|
init_src();
|
|
7488
|
-
|
|
7489
|
-
rootRoutes = new Hono4().get("/", (c) => {
|
|
8355
|
+
rootRoutes = new Hono5().get("/", (c) => {
|
|
7490
8356
|
return c.json({
|
|
7491
8357
|
name: productName,
|
|
7492
8358
|
message: createGreeting("API client")
|
|
7493
8359
|
});
|
|
7494
|
-
}).get("/config/status", (c) => {
|
|
7495
|
-
return c.json(configStatus);
|
|
7496
8360
|
});
|
|
7497
8361
|
});
|
|
7498
8362
|
|
|
@@ -7501,24 +8365,78 @@ var exports_app = {};
|
|
|
7501
8365
|
__export(exports_app, {
|
|
7502
8366
|
app: () => app
|
|
7503
8367
|
});
|
|
7504
|
-
import { Hono as
|
|
8368
|
+
import { Hono as Hono6 } from "hono";
|
|
7505
8369
|
import { bodyLimit } from "hono/body-limit";
|
|
7506
8370
|
var maxRequestBodyBytes, app;
|
|
7507
8371
|
var init_app = __esm(() => {
|
|
7508
8372
|
init_chat_routes();
|
|
8373
|
+
init_config_routes();
|
|
7509
8374
|
init_diagnostics_routes();
|
|
7510
8375
|
init_extension_routes();
|
|
7511
8376
|
init_root_routes();
|
|
7512
8377
|
maxRequestBodyBytes = 32 * 1024 * 1024;
|
|
7513
|
-
app = new
|
|
8378
|
+
app = new Hono6().use(bodyLimit({
|
|
7514
8379
|
maxSize: maxRequestBodyBytes,
|
|
7515
8380
|
onError: (c) => c.json({ error: "Request body too large." }, 413)
|
|
7516
|
-
})).route("/", rootRoutes).route("/diagnostics", diagnosticsRoutes).route("/extensions", extensionRoutes).route("/sessions", sessionRoutes);
|
|
8381
|
+
})).route("/", rootRoutes).route("/config", configRoutes).route("/diagnostics", diagnosticsRoutes).route("/extensions", extensionRoutes).route("/sessions", sessionRoutes);
|
|
8382
|
+
});
|
|
8383
|
+
|
|
8384
|
+
// apps/server/src/lib/headroom-proxy.ts
|
|
8385
|
+
var exports_headroom_proxy = {};
|
|
8386
|
+
__export(exports_headroom_proxy, {
|
|
8387
|
+
ensureHeadroomRouting: () => ensureHeadroomRouting,
|
|
8388
|
+
checkHeadroomHealth: () => checkHeadroomHealth
|
|
8389
|
+
});
|
|
8390
|
+
async function checkHeadroomHealth(proxyUrl) {
|
|
8391
|
+
try {
|
|
8392
|
+
await fetch(new URL("/health", proxyUrl), {
|
|
8393
|
+
signal: AbortSignal.timeout(healthCheckTimeoutMs)
|
|
8394
|
+
});
|
|
8395
|
+
return "healthy";
|
|
8396
|
+
} catch {
|
|
8397
|
+
return "down";
|
|
8398
|
+
}
|
|
8399
|
+
}
|
|
8400
|
+
async function ensureHeadroomRouting() {
|
|
8401
|
+
const headroom = lightcodeConfigResult.config.headroom;
|
|
8402
|
+
if (!headroom?.enabled) {
|
|
8403
|
+
return;
|
|
8404
|
+
}
|
|
8405
|
+
let health;
|
|
8406
|
+
try {
|
|
8407
|
+
health = await checkHeadroomHealth(headroom.proxyUrl);
|
|
8408
|
+
} catch (error) {
|
|
8409
|
+
health = "down";
|
|
8410
|
+
logger9.warn("headroom_health_probe_failed", {
|
|
8411
|
+
proxyUrl: headroom.proxyUrl,
|
|
8412
|
+
error: getErrorMessage(error)
|
|
8413
|
+
});
|
|
8414
|
+
}
|
|
8415
|
+
if (health === "healthy") {
|
|
8416
|
+
logger9.info("headroom_routing_active", { proxyUrl: headroom.proxyUrl });
|
|
8417
|
+
return;
|
|
8418
|
+
}
|
|
8419
|
+
if (headroom.failOpen) {
|
|
8420
|
+
logger9.warn("headroom_unreachable_failing_open", {
|
|
8421
|
+
proxyUrl: headroom.proxyUrl
|
|
8422
|
+
});
|
|
8423
|
+
disableHeadroomRouting();
|
|
8424
|
+
return;
|
|
8425
|
+
}
|
|
8426
|
+
logger9.error("headroom_unreachable_fail_closed", {
|
|
8427
|
+
proxyUrl: headroom.proxyUrl
|
|
8428
|
+
});
|
|
8429
|
+
}
|
|
8430
|
+
var logger9, healthCheckTimeoutMs = 1500;
|
|
8431
|
+
var init_headroom_proxy = __esm(() => {
|
|
8432
|
+
init_src();
|
|
8433
|
+
init_runtime_config();
|
|
8434
|
+
logger9 = createLogger("headroom-proxy");
|
|
7517
8435
|
});
|
|
7518
8436
|
|
|
7519
8437
|
// apps/server/src/index.ts
|
|
7520
8438
|
init_src();
|
|
7521
|
-
var
|
|
8439
|
+
var logger10 = createLogger("server");
|
|
7522
8440
|
var httpIdleTimeoutSeconds = 30;
|
|
7523
8441
|
var shutdownForceExitMs = 5000;
|
|
7524
8442
|
function isChatStreamingRoute(request) {
|
|
@@ -7529,7 +8447,11 @@ function isChatStreamingRoute(request) {
|
|
|
7529
8447
|
return /^\/sessions\/[^/]+\/chat$/.test(pathname);
|
|
7530
8448
|
}
|
|
7531
8449
|
async function startServer() {
|
|
7532
|
-
const
|
|
8450
|
+
const logDirectory = enableFileLogSink(Bun.env);
|
|
8451
|
+
if (logDirectory) {
|
|
8452
|
+
logger10.info("file_log_sink_enabled", { directory: logDirectory });
|
|
8453
|
+
}
|
|
8454
|
+
const port = Number(Bun.env.PORT ?? 4983);
|
|
7533
8455
|
const hostname = Bun.env.LIGHTCODE_HOST ?? "127.0.0.1";
|
|
7534
8456
|
let app2;
|
|
7535
8457
|
try {
|
|
@@ -7547,24 +8469,49 @@ async function startServer() {
|
|
|
7547
8469
|
`));
|
|
7548
8470
|
process.exit(1);
|
|
7549
8471
|
}
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
}
|
|
8472
|
+
Promise.resolve().then(() => (init_runtime_config(), exports_runtime_config)).then((runtime19) => runtime19.initializeModelCapabilities()).catch((error) => {
|
|
8473
|
+
logger10.warn("model_capability_init_failed", {
|
|
8474
|
+
error: getErrorMessage(error)
|
|
8475
|
+
});
|
|
8476
|
+
});
|
|
8477
|
+
Promise.resolve().then(() => (init_headroom_proxy(), exports_headroom_proxy)).then((headroom) => headroom.ensureHeadroomRouting()).catch((error) => {
|
|
8478
|
+
logger10.warn("headroom_init_failed", {
|
|
8479
|
+
error: getErrorMessage(error)
|
|
8480
|
+
});
|
|
7560
8481
|
});
|
|
8482
|
+
let server;
|
|
8483
|
+
try {
|
|
8484
|
+
server = Bun.serve({
|
|
8485
|
+
idleTimeout: httpIdleTimeoutSeconds,
|
|
8486
|
+
port,
|
|
8487
|
+
hostname,
|
|
8488
|
+
async fetch(request, bunServer) {
|
|
8489
|
+
if (isChatStreamingRoute(request)) {
|
|
8490
|
+
bunServer.timeout(request, 0);
|
|
8491
|
+
}
|
|
8492
|
+
return app2.fetch(request);
|
|
8493
|
+
}
|
|
8494
|
+
});
|
|
8495
|
+
} catch (error) {
|
|
8496
|
+
process.stderr.write([
|
|
8497
|
+
"",
|
|
8498
|
+
`Lightcode server could not bind ${hostname}:${port}:`,
|
|
8499
|
+
` ${getErrorMessage(error)}`,
|
|
8500
|
+
"",
|
|
8501
|
+
"Another process is likely using this port. Set PORT (or",
|
|
8502
|
+
"LIGHTCODE_API_URL for the CLI) to a free port and restart.",
|
|
8503
|
+
""
|
|
8504
|
+
].join(`
|
|
8505
|
+
`));
|
|
8506
|
+
process.exit(1);
|
|
8507
|
+
}
|
|
7561
8508
|
let shuttingDown = false;
|
|
7562
8509
|
const shutdown = async (signal) => {
|
|
7563
8510
|
if (shuttingDown) {
|
|
7564
8511
|
return;
|
|
7565
8512
|
}
|
|
7566
8513
|
shuttingDown = true;
|
|
7567
|
-
|
|
8514
|
+
logger10.info("server_shutdown", { signal });
|
|
7568
8515
|
const forceExitTimer = setTimeout(() => {
|
|
7569
8516
|
server.stop(true);
|
|
7570
8517
|
process.exit(0);
|