@kmugalkhod/lightcode 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/cli.js +1575 -935
- package/lightcode.cjs +63 -18
- package/package.json +2 -1
- package/server.js +695 -238
package/server.js
CHANGED
|
@@ -747,9 +747,10 @@ var init_schema = __esm(() => {
|
|
|
747
747
|
|
|
748
748
|
// packages/ai/src/edit-file/schema.ts
|
|
749
749
|
import { z as z6 } from "zod";
|
|
750
|
-
var editFileDescription
|
|
750
|
+
var editFileDescription, editFileInputSchema, editFileProviderInputSchema, editFileOutputSchema;
|
|
751
751
|
var init_schema2 = __esm(() => {
|
|
752
752
|
init_base_schemas();
|
|
753
|
+
editFileDescription = "Edit a file via search and replace. Use this for focused, small edits. " + "Line endings (CRLF/LF) and surrounding whitespace are matched tolerantly, so " + "copy the lines as shown by read_file. The search text must match exactly one " + "place \u2014 include enough surrounding context to make it unique, or set " + "replaceAll: true to change every occurrence.";
|
|
753
754
|
editFileInputSchema = z6.object({
|
|
754
755
|
path: boundedPathSchema,
|
|
755
756
|
search: z6.string().min(1),
|
|
@@ -1549,7 +1550,8 @@ var init_agent_tools = __esm(() => {
|
|
|
1549
1550
|
permissionMode: permissionModeSchema.optional(),
|
|
1550
1551
|
allowedTools: z21.array(codingAgentToolNameSchema).optional(),
|
|
1551
1552
|
permissionRules: permissionRulesSchema.optional(),
|
|
1552
|
-
sandbox: sandboxConfigSchema.optional()
|
|
1553
|
+
sandbox: sandboxConfigSchema.optional(),
|
|
1554
|
+
environmentContext: z21.string().optional()
|
|
1553
1555
|
});
|
|
1554
1556
|
});
|
|
1555
1557
|
|
|
@@ -1883,7 +1885,8 @@ function buildCodingAgentSystemPrompt({
|
|
|
1883
1885
|
cwd,
|
|
1884
1886
|
override,
|
|
1885
1887
|
mode = defaultCodingAgentMode,
|
|
1886
|
-
includeToolDiscipline = false
|
|
1888
|
+
includeToolDiscipline = false,
|
|
1889
|
+
environmentContext
|
|
1887
1890
|
}) {
|
|
1888
1891
|
const promptTemplate = override?.trim() ? override : defaultCodingAgentSystemPrompt;
|
|
1889
1892
|
const normalizedPrompt = normalizePromptTemplate(promptTemplate, cwd);
|
|
@@ -1894,12 +1897,14 @@ function buildCodingAgentSystemPrompt({
|
|
|
1894
1897
|
` + `Mode purpose: ${modeDefinition.purpose}
|
|
1895
1898
|
` + `${modeDefinition.instructions}` + (includeToolDiscipline ? `
|
|
1896
1899
|
|
|
1897
|
-
${toolCallingDisciplineAddendum}` : "")
|
|
1900
|
+
${toolCallingDisciplineAddendum}` : "") + (environmentContext?.trim() ? `
|
|
1901
|
+
|
|
1902
|
+
${environmentContext.trim()}` : "");
|
|
1898
1903
|
}
|
|
1899
1904
|
var cwdPromptPlaceholder = "{cwd}", defaultCodingAgentSystemPrompt, toolCallingDisciplineAddendum;
|
|
1900
1905
|
var init_coding_agent_prompt = __esm(() => {
|
|
1901
1906
|
init_coding_agent_modes();
|
|
1902
|
-
defaultCodingAgentSystemPrompt = "You are a coding agent. Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under
|
|
1907
|
+
defaultCodingAgentSystemPrompt = "You are a coding agent working inside the user's project at this working directory: {cwd}. " + 'You can already see the project \u2014 proactively read it. When the user asks you to review, explain, or work on "my code"/"this", inspect the working directory yourself with list_files, glob_search, grep, and read_file; NEVER ask the user to paste or share code that lives in this directory \u2014 open it. ' + "Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under the working directory. " + "Prefer dedicated tools \u2014 glob_search for paths, grep for text search, structured git tools for repository inspection \u2014 and bash only when those are not enough. " + `Use todo_write to keep multi-step work visible and current, inspect context before risky operations, and emit a brief, natural progress note before major tool actions and after important findings.
|
|
1903
1908
|
|
|
1904
1909
|
` + `You are an agent \u2014 keep working until the request is fully resolved. Never stop with a partial answer; if you say you will do something, do it in the same turn by calling a tool. Do not finish while any todo is pending or in_progress \u2014 end your turn only when the task is complete or you are blocked on input that only the user can provide.
|
|
1905
1910
|
|
|
@@ -2221,78 +2226,7 @@ function selectCodingAgentIntentTools({
|
|
|
2221
2226
|
if (!normalizedText || casualPromptPattern.test(normalizedText)) {
|
|
2222
2227
|
return [];
|
|
2223
2228
|
}
|
|
2224
|
-
const
|
|
2225
|
-
const hasCodeIntent = includesAny(normalizedText, [
|
|
2226
|
-
"file",
|
|
2227
|
-
"folder",
|
|
2228
|
-
"code",
|
|
2229
|
-
"repo",
|
|
2230
|
-
"project",
|
|
2231
|
-
"implement",
|
|
2232
|
-
"fix",
|
|
2233
|
-
"add",
|
|
2234
|
-
"build",
|
|
2235
|
-
"refactor",
|
|
2236
|
-
"change",
|
|
2237
|
-
"create",
|
|
2238
|
-
"update",
|
|
2239
|
-
"edit",
|
|
2240
|
-
"delete",
|
|
2241
|
-
"run",
|
|
2242
|
-
"test",
|
|
2243
|
-
"typecheck",
|
|
2244
|
-
"debug",
|
|
2245
|
-
"error",
|
|
2246
|
-
"issue",
|
|
2247
|
-
"problem",
|
|
2248
|
-
"fails",
|
|
2249
|
-
"failed",
|
|
2250
|
-
"failing",
|
|
2251
|
-
"bug",
|
|
2252
|
-
"crash",
|
|
2253
|
-
"slow",
|
|
2254
|
-
"slowness",
|
|
2255
|
-
"not working",
|
|
2256
|
-
"epic",
|
|
2257
|
-
"ticket"
|
|
2258
|
-
]);
|
|
2259
|
-
const hasReadIntent = includesAny(normalizedText, [
|
|
2260
|
-
"read",
|
|
2261
|
-
"list",
|
|
2262
|
-
"find",
|
|
2263
|
-
"search",
|
|
2264
|
-
"grep",
|
|
2265
|
-
"glob",
|
|
2266
|
-
"inspect",
|
|
2267
|
-
"analyse",
|
|
2268
|
-
"analyze",
|
|
2269
|
-
"check"
|
|
2270
|
-
]);
|
|
2271
|
-
const hasWriteIntent = mode === "build" && includesAny(normalizedText, [
|
|
2272
|
-
"write",
|
|
2273
|
-
"edit",
|
|
2274
|
-
"change",
|
|
2275
|
-
"create",
|
|
2276
|
-
"implement",
|
|
2277
|
-
"fix",
|
|
2278
|
-
"update",
|
|
2279
|
-
"delete",
|
|
2280
|
-
"remove",
|
|
2281
|
-
"rename"
|
|
2282
|
-
]);
|
|
2283
|
-
const hasShellIntent = mode === "build" && includesAny(normalizedText, [
|
|
2284
|
-
"run",
|
|
2285
|
-
"command",
|
|
2286
|
-
"terminal",
|
|
2287
|
-
"shell",
|
|
2288
|
-
"bash",
|
|
2289
|
-
"powershell",
|
|
2290
|
-
"bun",
|
|
2291
|
-
"tests",
|
|
2292
|
-
"run test",
|
|
2293
|
-
"run tests",
|
|
2294
|
-
"typecheck"
|
|
2295
|
-
]);
|
|
2229
|
+
const situational = [];
|
|
2296
2230
|
const hasGitIntent = includesAny(normalizedText, [
|
|
2297
2231
|
"git",
|
|
2298
2232
|
"diff",
|
|
@@ -2303,95 +2237,64 @@ function selectCodingAgentIntentTools({
|
|
|
2303
2237
|
"revision",
|
|
2304
2238
|
"changes"
|
|
2305
2239
|
]);
|
|
2306
|
-
const hasTodoIntent = includesAny(normalizedText, [
|
|
2307
|
-
"todo",
|
|
2308
|
-
"tasks",
|
|
2309
|
-
"checklist",
|
|
2310
|
-
"steps",
|
|
2311
|
-
"epic",
|
|
2312
|
-
"ticket"
|
|
2313
|
-
]);
|
|
2314
|
-
const hasWebIntent = includesAny(normalizedText, [
|
|
2315
|
-
"http://",
|
|
2316
|
-
"https://",
|
|
2317
|
-
"url",
|
|
2318
|
-
"fetch",
|
|
2319
|
-
"website",
|
|
2320
|
-
"web",
|
|
2321
|
-
"online",
|
|
2322
|
-
"search internet",
|
|
2323
|
-
"latest"
|
|
2324
|
-
]);
|
|
2325
|
-
const explicitToolSearch = includesAny(normalizedText, [
|
|
2326
|
-
"tool_search",
|
|
2327
|
-
"tool search",
|
|
2328
|
-
"find tool",
|
|
2329
|
-
"available tool"
|
|
2330
|
-
]);
|
|
2331
|
-
const hasSkillIntent = includesAny(normalizedText, [
|
|
2332
|
-
"skill",
|
|
2333
|
-
"skills",
|
|
2334
|
-
"load skill"
|
|
2335
|
-
]);
|
|
2336
|
-
const hasMcpIntent = includesAny(normalizedText, [
|
|
2337
|
-
"mcp",
|
|
2338
|
-
"resource",
|
|
2339
|
-
"resources",
|
|
2340
|
-
"mcp tool",
|
|
2341
|
-
"mcp server"
|
|
2342
|
-
]);
|
|
2343
|
-
if (hasCodeIntent || hasReadIntent && !hasGitIntent && !hasWebIntent && !explicitToolSearch) {
|
|
2344
|
-
for (const toolName of baseReadTools) {
|
|
2345
|
-
pushUniqueTool(selectedTools, toolName);
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
2240
|
if (hasGitIntent) {
|
|
2349
2241
|
if (includesAny(normalizedText, ["status", "state", "dirty", "working tree"]) || !includesAny(normalizedText, ["diff", "log", "show", "history", "commit"])) {
|
|
2350
|
-
pushUniqueTool(
|
|
2242
|
+
pushUniqueTool(situational, "git_status");
|
|
2351
2243
|
}
|
|
2352
2244
|
if (includesAny(normalizedText, ["diff", "changes", "patch"])) {
|
|
2353
|
-
pushUniqueTool(
|
|
2245
|
+
pushUniqueTool(situational, "git_diff");
|
|
2354
2246
|
}
|
|
2355
2247
|
if (includesAny(normalizedText, ["log", "history", "commits"])) {
|
|
2356
|
-
pushUniqueTool(
|
|
2248
|
+
pushUniqueTool(situational, "git_log");
|
|
2357
2249
|
}
|
|
2358
2250
|
if (includesAny(normalizedText, ["show", "revision", "commit"])) {
|
|
2359
|
-
pushUniqueTool(
|
|
2251
|
+
pushUniqueTool(situational, "git_show");
|
|
2360
2252
|
}
|
|
2361
2253
|
}
|
|
2362
|
-
if (
|
|
2363
|
-
pushUniqueTool(
|
|
2254
|
+
if (mode === "build" && includesAny(normalizedText, ["todo", "tasks", "checklist", "steps", "epic", "ticket"])) {
|
|
2255
|
+
pushUniqueTool(situational, "todo_write");
|
|
2364
2256
|
}
|
|
2365
|
-
if (
|
|
2366
|
-
pushUniqueTool(
|
|
2257
|
+
if (includesAny(normalizedText, ["tool_search", "tool search", "find tool", "available tool"])) {
|
|
2258
|
+
pushUniqueTool(situational, "tool_search");
|
|
2367
2259
|
}
|
|
2368
|
-
if (
|
|
2369
|
-
pushUniqueTool(
|
|
2260
|
+
if (includesAny(normalizedText, ["skill", "skills", "load skill"])) {
|
|
2261
|
+
pushUniqueTool(situational, "skill");
|
|
2370
2262
|
}
|
|
2371
|
-
if (
|
|
2372
|
-
pushUniqueTool(
|
|
2263
|
+
if (includesAny(normalizedText, ["mcp", "resource", "resources", "mcp tool", "mcp server"])) {
|
|
2264
|
+
pushUniqueTool(situational, "list_mcp_resources");
|
|
2373
2265
|
if (includesAny(normalizedText, ["read", "resource", "resources"])) {
|
|
2374
|
-
pushUniqueTool(
|
|
2266
|
+
pushUniqueTool(situational, "read_mcp_resource");
|
|
2375
2267
|
}
|
|
2376
2268
|
if (mode === "build" && includesAny(normalizedText, ["call", "execute", "run"])) {
|
|
2377
|
-
pushUniqueTool(
|
|
2269
|
+
pushUniqueTool(situational, "call_mcp_tool");
|
|
2378
2270
|
}
|
|
2379
2271
|
}
|
|
2380
|
-
if (
|
|
2381
|
-
|
|
2382
|
-
|
|
2272
|
+
if (includesAny(normalizedText, [
|
|
2273
|
+
"http://",
|
|
2274
|
+
"https://",
|
|
2275
|
+
"url",
|
|
2276
|
+
"fetch",
|
|
2277
|
+
"website",
|
|
2278
|
+
"web",
|
|
2279
|
+
"online",
|
|
2280
|
+
"search internet",
|
|
2281
|
+
"latest"
|
|
2282
|
+
])) {
|
|
2283
|
+
pushUniqueTool(situational, "web_fetch");
|
|
2284
|
+
pushUniqueTool(situational, "web_search");
|
|
2383
2285
|
}
|
|
2384
|
-
if (
|
|
2385
|
-
pushUniqueTool(
|
|
2286
|
+
if (mode === "plan" && includesAny(normalizedText, ["question", "ask", "clarify"])) {
|
|
2287
|
+
pushUniqueTool(situational, "request_user_input");
|
|
2386
2288
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2289
|
+
const ordered = [...baseReadTools, ...situational];
|
|
2290
|
+
if (mode === "build") {
|
|
2291
|
+
ordered.push("write_file", "edit_file", "bash");
|
|
2390
2292
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
2293
|
+
const selected = [];
|
|
2294
|
+
for (const toolName of ordered) {
|
|
2295
|
+
pushUniqueTool(selected, toolName);
|
|
2393
2296
|
}
|
|
2394
|
-
return
|
|
2297
|
+
return selected.slice(0, maxProviderActiveTools);
|
|
2395
2298
|
}
|
|
2396
2299
|
function limitProviderActiveTools(tools, maxTools = maxProviderActiveTools) {
|
|
2397
2300
|
if (tools.length <= maxTools) {
|
|
@@ -2594,7 +2497,8 @@ function createCodingAgent({
|
|
|
2594
2497
|
cwd: options.cwd,
|
|
2595
2498
|
override: promptOverride,
|
|
2596
2499
|
mode,
|
|
2597
|
-
includeToolDiscipline
|
|
2500
|
+
includeToolDiscipline,
|
|
2501
|
+
environmentContext: options.environmentContext
|
|
2598
2502
|
})
|
|
2599
2503
|
};
|
|
2600
2504
|
}
|
|
@@ -2661,17 +2565,33 @@ function normalizeContextOptimizerConfig(config) {
|
|
|
2661
2565
|
}
|
|
2662
2566
|
function resolveContextWindowTokens({
|
|
2663
2567
|
config,
|
|
2664
|
-
modelContextWindow
|
|
2568
|
+
modelContextWindow,
|
|
2569
|
+
endpointLimitTokens
|
|
2570
|
+
}) {
|
|
2571
|
+
const resolved = config.contextWindowOverride ?? modelContextWindow ?? DEFAULT_CONTEXT_WINDOW_TOKENS;
|
|
2572
|
+
if (typeof endpointLimitTokens === "number" && Number.isFinite(endpointLimitTokens) && endpointLimitTokens > 0) {
|
|
2573
|
+
return Math.min(resolved, endpointLimitTokens);
|
|
2574
|
+
}
|
|
2575
|
+
return resolved;
|
|
2576
|
+
}
|
|
2577
|
+
function resolveInputBudgetTokens({
|
|
2578
|
+
contextWindow,
|
|
2579
|
+
reservedOutputTokens,
|
|
2580
|
+
safetyFraction = DEFAULT_INPUT_BUDGET_SAFETY_FRACTION
|
|
2665
2581
|
}) {
|
|
2666
|
-
|
|
2582
|
+
const reserved = typeof reservedOutputTokens === "number" && Number.isFinite(reservedOutputTokens) && reservedOutputTokens > 0 ? reservedOutputTokens : 0;
|
|
2583
|
+
const clampedFraction = Math.min(1, Math.max(0.1, safetyFraction));
|
|
2584
|
+
const usable = Math.max(0, contextWindow - reserved);
|
|
2585
|
+
return Math.max(0, Math.floor(usable * clampedFraction));
|
|
2667
2586
|
}
|
|
2668
|
-
var DEFAULT_CONTEXT_WINDOW_TOKENS = 128000, contextOptimizerConfigSchema, resolvedContextOptimizerConfigSchema, defaultContextOptimizerConfig;
|
|
2587
|
+
var DEFAULT_CONTEXT_WINDOW_TOKENS = 128000, contextOptimizerConfigSchema, resolvedContextOptimizerConfigSchema, defaultContextOptimizerConfig, DEFAULT_INPUT_BUDGET_SAFETY_FRACTION = 0.92;
|
|
2669
2588
|
var init_config3 = __esm(() => {
|
|
2670
2589
|
contextOptimizerConfigSchema = z26.object({
|
|
2671
2590
|
autoCompact: z26.boolean().optional(),
|
|
2672
2591
|
compactAtFraction: z26.number().min(0.3).max(0.95).optional(),
|
|
2673
2592
|
pruneAtFraction: z26.number().min(0.2).max(0.9).optional(),
|
|
2674
2593
|
preserveRecentMessages: z26.number().int().min(2).max(100).optional(),
|
|
2594
|
+
maxCoverageTokensPerCompaction: z26.number().int().min(2000).max(200000).optional(),
|
|
2675
2595
|
contextWindowOverride: z26.number().int().min(8000).max(1e7).optional(),
|
|
2676
2596
|
summaryMaxChars: z26.number().int().min(200).max(50000).optional(),
|
|
2677
2597
|
aggressivePruneWhenUncached: z26.boolean().optional(),
|
|
@@ -2685,6 +2605,7 @@ var init_config3 = __esm(() => {
|
|
|
2685
2605
|
compactAtFraction: z26.number().min(0.3).max(0.95),
|
|
2686
2606
|
pruneAtFraction: z26.number().min(0.2).max(0.9),
|
|
2687
2607
|
preserveRecentMessages: z26.number().int().min(2).max(100),
|
|
2608
|
+
maxCoverageTokensPerCompaction: z26.number().int().min(2000).max(200000),
|
|
2688
2609
|
contextWindowOverride: z26.number().int().min(8000).max(1e7).nullable(),
|
|
2689
2610
|
summaryMaxChars: z26.number().int().min(200).max(50000),
|
|
2690
2611
|
aggressivePruneWhenUncached: z26.boolean(),
|
|
@@ -2697,6 +2618,7 @@ var init_config3 = __esm(() => {
|
|
|
2697
2618
|
compactAtFraction: 0.7,
|
|
2698
2619
|
pruneAtFraction: 0.6,
|
|
2699
2620
|
preserveRecentMessages: 6,
|
|
2621
|
+
maxCoverageTokensPerCompaction: 12000,
|
|
2700
2622
|
contextWindowOverride: null,
|
|
2701
2623
|
summaryMaxChars: 4000,
|
|
2702
2624
|
aggressivePruneWhenUncached: true,
|
|
@@ -2776,10 +2698,44 @@ var init_estimate = __esm(() => {
|
|
|
2776
2698
|
totalTokens: z27.number().nonnegative().optional()
|
|
2777
2699
|
}),
|
|
2778
2700
|
modelId: z27.string().optional(),
|
|
2779
|
-
finishReason: languageModelFinishReasonSchema.optional()
|
|
2701
|
+
finishReason: languageModelFinishReasonSchema.optional(),
|
|
2702
|
+
context: z27.object({
|
|
2703
|
+
inputTokens: z27.number().nonnegative(),
|
|
2704
|
+
contextWindow: z27.number().positive(),
|
|
2705
|
+
compactedMessages: z27.number().nonnegative().optional()
|
|
2706
|
+
}).optional()
|
|
2780
2707
|
}).loose();
|
|
2781
2708
|
});
|
|
2782
2709
|
|
|
2710
|
+
// packages/ai/src/common/output-utils.ts
|
|
2711
|
+
function omissionMarker(omitted) {
|
|
2712
|
+
return `
|
|
2713
|
+
|
|
2714
|
+
... [${omitted} characters omitted; re-read a line range with read_file or raise maxChars] ...
|
|
2715
|
+
|
|
2716
|
+
`;
|
|
2717
|
+
}
|
|
2718
|
+
function truncateText(text, maxChars = DEFAULT_TOOL_TEXT_OUTPUT_CHARS) {
|
|
2719
|
+
if (text.length <= maxChars) {
|
|
2720
|
+
return {
|
|
2721
|
+
text,
|
|
2722
|
+
truncated: false
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
const markerReserve = omissionMarker(text.length).length;
|
|
2726
|
+
const usable = Math.max(0, maxChars - markerReserve);
|
|
2727
|
+
const headChars = Math.floor(usable * 0.7);
|
|
2728
|
+
const tailChars = usable - headChars;
|
|
2729
|
+
const head = text.slice(0, headChars);
|
|
2730
|
+
const tail = tailChars > 0 ? text.slice(text.length - tailChars) : "";
|
|
2731
|
+
const omitted = text.length - head.length - tail.length;
|
|
2732
|
+
return {
|
|
2733
|
+
text: `${head}${omissionMarker(omitted)}${tail}`,
|
|
2734
|
+
truncated: true
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
var init_output_utils = () => {};
|
|
2738
|
+
|
|
2783
2739
|
// packages/ai/src/context/heuristic-summary.ts
|
|
2784
2740
|
function truncateInline(text, maxChars) {
|
|
2785
2741
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
@@ -3129,6 +3085,127 @@ var init_tier1_prune = __esm(() => {
|
|
|
3129
3085
|
init_message_parts();
|
|
3130
3086
|
});
|
|
3131
3087
|
|
|
3088
|
+
// packages/ai/src/context/fit-to-budget.ts
|
|
3089
|
+
function estimateStructuralTokens(messages) {
|
|
3090
|
+
return messages.reduce((total, message) => total + estimateMessageTokens(message), 0);
|
|
3091
|
+
}
|
|
3092
|
+
function getPartState2(part) {
|
|
3093
|
+
const state = isRecord3(part) ? Reflect.get(part, "state") : undefined;
|
|
3094
|
+
return typeof state === "string" ? state : null;
|
|
3095
|
+
}
|
|
3096
|
+
function elideOutput(part, originalChars) {
|
|
3097
|
+
const stub = {
|
|
3098
|
+
lightcodeElided: true,
|
|
3099
|
+
note: "Tool output elided to fit the model's context budget. Re-run the tool if this output is needed again.",
|
|
3100
|
+
originalChars
|
|
3101
|
+
};
|
|
3102
|
+
return { ...part, output: stub };
|
|
3103
|
+
}
|
|
3104
|
+
function elideToolOutputsInMessage(message) {
|
|
3105
|
+
let elided = 0;
|
|
3106
|
+
const parts = message.parts.map((part) => {
|
|
3107
|
+
if (!getToolNameFromPart(part) || getPartState2(part) !== "output-available") {
|
|
3108
|
+
return part;
|
|
3109
|
+
}
|
|
3110
|
+
const output = isRecord3(part) ? Reflect.get(part, "output") : undefined;
|
|
3111
|
+
if (output === undefined || isElidedToolOutput(output)) {
|
|
3112
|
+
return part;
|
|
3113
|
+
}
|
|
3114
|
+
elided += 1;
|
|
3115
|
+
return elideOutput(part, safeStringify(output).length);
|
|
3116
|
+
});
|
|
3117
|
+
return elided > 0 ? { message: { ...message, parts }, elided } : { message, elided: 0 };
|
|
3118
|
+
}
|
|
3119
|
+
function truncateTextPartsInMessage(message, capChars) {
|
|
3120
|
+
let truncated = 0;
|
|
3121
|
+
const parts = message.parts.map((part) => {
|
|
3122
|
+
if (!isRecord3(part)) {
|
|
3123
|
+
return part;
|
|
3124
|
+
}
|
|
3125
|
+
const text = Reflect.get(part, "text");
|
|
3126
|
+
if (typeof text !== "string" || text.length <= capChars) {
|
|
3127
|
+
return part;
|
|
3128
|
+
}
|
|
3129
|
+
const result = truncateText(text, capChars);
|
|
3130
|
+
if (!result.truncated) {
|
|
3131
|
+
return part;
|
|
3132
|
+
}
|
|
3133
|
+
truncated += 1;
|
|
3134
|
+
return { ...part, text: result.text };
|
|
3135
|
+
});
|
|
3136
|
+
return truncated > 0 ? { message: { ...message, parts }, truncated } : { message, truncated: 0 };
|
|
3137
|
+
}
|
|
3138
|
+
function fitMessagesToBudget(messages, options) {
|
|
3139
|
+
const budget = Math.max(0, Math.floor(options.inputBudgetTokens));
|
|
3140
|
+
const preserveRecentMessages = Math.max(0, options.preserveRecentMessages);
|
|
3141
|
+
let working = [...messages];
|
|
3142
|
+
let elidedToolOutputs = 0;
|
|
3143
|
+
let droppedMessages = 0;
|
|
3144
|
+
let truncatedTextParts = 0;
|
|
3145
|
+
const fits = () => estimateStructuralTokens(working) <= budget;
|
|
3146
|
+
if (fits()) {
|
|
3147
|
+
return {
|
|
3148
|
+
messages: working,
|
|
3149
|
+
elidedToolOutputs: 0,
|
|
3150
|
+
droppedMessages: 0,
|
|
3151
|
+
truncatedTextParts: 0,
|
|
3152
|
+
estimatedTokens: estimateStructuralTokens(working),
|
|
3153
|
+
fitted: false,
|
|
3154
|
+
withinBudget: true
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
const preserveFrom = Math.max(0, working.length - preserveRecentMessages);
|
|
3158
|
+
for (let index = 0;index < preserveFrom && !fits(); index += 1) {
|
|
3159
|
+
const { message, elided } = elideToolOutputsInMessage(working[index]);
|
|
3160
|
+
if (elided > 0) {
|
|
3161
|
+
working[index] = message;
|
|
3162
|
+
elidedToolOutputs += elided;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
const firstIsSummary = isSummaryMessage(working[0]);
|
|
3166
|
+
let dropFrom = firstIsSummary ? 1 : 0;
|
|
3167
|
+
while (!fits() && working.length - dropFrom > preserveRecentMessages && dropFrom < working.length) {
|
|
3168
|
+
working.splice(dropFrom, 1);
|
|
3169
|
+
droppedMessages += 1;
|
|
3170
|
+
}
|
|
3171
|
+
if (!fits()) {
|
|
3172
|
+
working = working.map((message) => {
|
|
3173
|
+
const { message: elidedMessage, elided } = elideToolOutputsInMessage(message);
|
|
3174
|
+
elidedToolOutputs += elided;
|
|
3175
|
+
return elidedMessage;
|
|
3176
|
+
});
|
|
3177
|
+
let cap = MAX_TOOL_TEXT_OUTPUT_CHARS;
|
|
3178
|
+
while (!fits() && cap >= MIN_TRUNCATION_CAP_CHARS) {
|
|
3179
|
+
working = working.map((message) => {
|
|
3180
|
+
const { message: truncatedMessage, truncated } = truncateTextPartsInMessage(message, cap);
|
|
3181
|
+
truncatedTextParts += truncated;
|
|
3182
|
+
return truncatedMessage;
|
|
3183
|
+
});
|
|
3184
|
+
cap = Math.floor(cap / 2);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
return {
|
|
3188
|
+
messages: working,
|
|
3189
|
+
elidedToolOutputs,
|
|
3190
|
+
droppedMessages,
|
|
3191
|
+
truncatedTextParts,
|
|
3192
|
+
estimatedTokens: estimateStructuralTokens(working),
|
|
3193
|
+
fitted: elidedToolOutputs > 0 || droppedMessages > 0 || truncatedTextParts > 0,
|
|
3194
|
+
withinBudget: fits()
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
3197
|
+
function isSummaryMessage(message) {
|
|
3198
|
+
return message?.id === contextSummaryMessageId;
|
|
3199
|
+
}
|
|
3200
|
+
var MIN_TRUNCATION_CAP_CHARS = 256;
|
|
3201
|
+
var init_fit_to_budget = __esm(() => {
|
|
3202
|
+
init_output_utils();
|
|
3203
|
+
init_estimate();
|
|
3204
|
+
init_heuristic_summary();
|
|
3205
|
+
init_message_parts();
|
|
3206
|
+
init_tier1_prune();
|
|
3207
|
+
});
|
|
3208
|
+
|
|
3132
3209
|
// packages/ai/src/context/build-provider-view.ts
|
|
3133
3210
|
function computeCompactionCutoffIndex(messages, preserveRecentMessages, afterAnchorIndex) {
|
|
3134
3211
|
const candidate = messages.length - preserveRecentMessages;
|
|
@@ -3142,11 +3219,32 @@ function computeCompactionCutoffIndex(messages, preserveRecentMessages, afterAnc
|
|
|
3142
3219
|
}
|
|
3143
3220
|
return afterAnchorIndex;
|
|
3144
3221
|
}
|
|
3222
|
+
function capCoverageToTokenBudget(coveredMessages, maxTokens) {
|
|
3223
|
+
if (coveredMessages.length <= 1) {
|
|
3224
|
+
return [...coveredMessages];
|
|
3225
|
+
}
|
|
3226
|
+
let total = 0;
|
|
3227
|
+
const capped = [];
|
|
3228
|
+
for (const message of coveredMessages) {
|
|
3229
|
+
const messageTokens = estimateStructuralTokens([message]);
|
|
3230
|
+
if (capped.length > 0 && total + messageTokens > maxTokens) {
|
|
3231
|
+
break;
|
|
3232
|
+
}
|
|
3233
|
+
capped.push(message);
|
|
3234
|
+
total += messageTokens;
|
|
3235
|
+
}
|
|
3236
|
+
return capped;
|
|
3237
|
+
}
|
|
3145
3238
|
function buildProviderView(options) {
|
|
3146
3239
|
const { messages, contextState, config } = options;
|
|
3147
3240
|
const contextWindow = resolveContextWindowTokens({
|
|
3148
3241
|
config,
|
|
3149
|
-
modelContextWindow: options.modelContextWindow
|
|
3242
|
+
modelContextWindow: options.modelContextWindow,
|
|
3243
|
+
endpointLimitTokens: options.endpointLimitTokens
|
|
3244
|
+
});
|
|
3245
|
+
const inputBudgetTokens = resolveInputBudgetTokens({
|
|
3246
|
+
contextWindow,
|
|
3247
|
+
reservedOutputTokens: options.reservedOutputTokens
|
|
3150
3248
|
});
|
|
3151
3249
|
let anchorResolved = false;
|
|
3152
3250
|
let afterAnchorIndex = 0;
|
|
@@ -3170,7 +3268,8 @@ function buildProviderView(options) {
|
|
|
3170
3268
|
let tier1 = null;
|
|
3171
3269
|
const uncachedActive = options.cacheActive === false && config.aggressivePruneWhenUncached;
|
|
3172
3270
|
const pruneFraction = uncachedActive ? config.uncachedPruneAtFraction : config.pruneAtFraction;
|
|
3173
|
-
|
|
3271
|
+
const triggerTokens = (currentEstimateTokens) => Math.max(currentEstimateTokens, estimateStructuralTokens(providerMessages), typeof options.measuredInputTokens === "number" ? options.measuredInputTokens : 0);
|
|
3272
|
+
if (triggerTokens(estimate.tokens) > pruneFraction * inputBudgetTokens) {
|
|
3174
3273
|
tier1 = pruneToolOutputs(providerMessages, {
|
|
3175
3274
|
preserveRecentMessages: config.preserveRecentMessages,
|
|
3176
3275
|
minOutputChars: uncachedActive ? config.uncachedPruneMinOutputChars : undefined,
|
|
@@ -3185,15 +3284,15 @@ function buildProviderView(options) {
|
|
|
3185
3284
|
}
|
|
3186
3285
|
}
|
|
3187
3286
|
const compactionCutoffIndex = computeCompactionCutoffIndex(messages, config.preserveRecentMessages, afterAnchorIndex);
|
|
3188
|
-
const coveredMessages = messages.slice(afterAnchorIndex, compactionCutoffIndex);
|
|
3189
|
-
const overCompactThreshold = estimate.tokens > config.compactAtFraction *
|
|
3287
|
+
const coveredMessages = capCoverageToTokenBudget(messages.slice(afterAnchorIndex, compactionCutoffIndex), config.maxCoverageTokensPerCompaction);
|
|
3288
|
+
const overCompactThreshold = triggerTokens(estimate.tokens) > config.compactAtFraction * inputBudgetTokens;
|
|
3190
3289
|
let compactionBlockedReason = null;
|
|
3191
3290
|
if (overCompactThreshold) {
|
|
3192
3291
|
if (coveredMessages.length === 0) {
|
|
3193
3292
|
compactionBlockedReason = "not_enough_messages";
|
|
3194
3293
|
} else if ((options.pendingInteractionCount ?? 0) > 0) {
|
|
3195
3294
|
compactionBlockedReason = "pending_interactions";
|
|
3196
|
-
} else if (hasUnresolvedToolWork(
|
|
3295
|
+
} else if (hasUnresolvedToolWork(coveredMessages)) {
|
|
3197
3296
|
compactionBlockedReason = "unresolved_tool_work";
|
|
3198
3297
|
}
|
|
3199
3298
|
}
|
|
@@ -3201,6 +3300,7 @@ function buildProviderView(options) {
|
|
|
3201
3300
|
providerMessages,
|
|
3202
3301
|
estimate,
|
|
3203
3302
|
contextWindow,
|
|
3303
|
+
inputBudgetTokens,
|
|
3204
3304
|
needsCompaction: overCompactThreshold && compactionBlockedReason === null,
|
|
3205
3305
|
compactionBlockedReason,
|
|
3206
3306
|
anchorResolved,
|
|
@@ -3212,6 +3312,7 @@ function buildProviderView(options) {
|
|
|
3212
3312
|
var init_build_provider_view = __esm(() => {
|
|
3213
3313
|
init_config3();
|
|
3214
3314
|
init_estimate();
|
|
3315
|
+
init_fit_to_budget();
|
|
3215
3316
|
init_heuristic_summary();
|
|
3216
3317
|
init_message_parts();
|
|
3217
3318
|
init_tier1_prune();
|
|
@@ -3257,6 +3358,7 @@ var init_context = __esm(() => {
|
|
|
3257
3358
|
init_config3();
|
|
3258
3359
|
init_context_state();
|
|
3259
3360
|
init_estimate();
|
|
3361
|
+
init_fit_to_budget();
|
|
3260
3362
|
init_heuristic_summary();
|
|
3261
3363
|
init_message_parts();
|
|
3262
3364
|
init_normalize_provider_messages();
|
|
@@ -3486,8 +3588,8 @@ var init_lightcode_config = __esm(() => {
|
|
|
3486
3588
|
defaultAutoContinueConfig = {
|
|
3487
3589
|
enabled: true,
|
|
3488
3590
|
maxAutoContinues: 50,
|
|
3489
|
-
maxErrorRetries:
|
|
3490
|
-
stallTimeoutSeconds:
|
|
3591
|
+
maxErrorRetries: 8,
|
|
3592
|
+
stallTimeoutSeconds: 300
|
|
3491
3593
|
};
|
|
3492
3594
|
headroomConfigSchema = z28.object({
|
|
3493
3595
|
enabled: z28.boolean().optional(),
|
|
@@ -3530,7 +3632,7 @@ var init_lightcode_config = __esm(() => {
|
|
|
3530
3632
|
context: defaultContextOptimizerConfig,
|
|
3531
3633
|
maxOutputTokens: 32768,
|
|
3532
3634
|
maxSteps: 30,
|
|
3533
|
-
maxRetries:
|
|
3635
|
+
maxRetries: 3,
|
|
3534
3636
|
autoContinue: defaultAutoContinueConfig,
|
|
3535
3637
|
headroom: defaultHeadroomConfig
|
|
3536
3638
|
};
|
|
@@ -4044,6 +4146,10 @@ async function extractToolCalls(text, knownToolNames) {
|
|
|
4044
4146
|
while ((match = DEEPSEEK_WRAPPER_RE.exec(text)) !== null) {
|
|
4045
4147
|
segments.push({ start: match.index, end: match.index + match[0].length, call: null });
|
|
4046
4148
|
}
|
|
4149
|
+
FUNCTION_CALLS_WRAPPER_RE.lastIndex = 0;
|
|
4150
|
+
while ((match = FUNCTION_CALLS_WRAPPER_RE.exec(text)) !== null) {
|
|
4151
|
+
segments.push({ start: match.index, end: match.index + match[0].length, call: null });
|
|
4152
|
+
}
|
|
4047
4153
|
const covered = (idx) => segments.some((s) => idx >= s.start && idx < s.end);
|
|
4048
4154
|
const orphanIdx = findOrphanMarker(text, covered);
|
|
4049
4155
|
if (orphanIdx !== -1) {
|
|
@@ -4131,7 +4237,7 @@ async function parseBareTrailingJson(text, knownToolNames) {
|
|
|
4131
4237
|
return null;
|
|
4132
4238
|
return { start: idx, payload };
|
|
4133
4239
|
}
|
|
4134
|
-
var TOOL_CALL_OPEN_MARKERS, MAX_MARKER_LENGTH, MARKER_CONFIRMATION_WINDOW = 256, BLOCK_CLOSE_MARKERS, CLOSED_BLOCK_RES, DEEPSEEK_WRAPPER_RE, PARAMETER_TAG_RE, PARAMETER_EQ_RE;
|
|
4240
|
+
var TOOL_CALL_OPEN_MARKERS, MAX_MARKER_LENGTH, MARKER_CONFIRMATION_WINDOW = 256, BLOCK_CLOSE_MARKERS, CLOSED_BLOCK_RES, DEEPSEEK_WRAPPER_RE, FUNCTION_CALLS_WRAPPER_RE, PARAMETER_TAG_RE, PARAMETER_EQ_RE;
|
|
4135
4241
|
var init_xml_tool_call_parser = __esm(() => {
|
|
4136
4242
|
init_tool_call_json_repair();
|
|
4137
4243
|
TOOL_CALL_OPEN_MARKERS = [
|
|
@@ -4158,6 +4264,7 @@ var init_xml_tool_call_parser = __esm(() => {
|
|
|
4158
4264
|
deepseek: /<\uFF5Ctool\u2581call\u2581begin\uFF5C>\s*(?:function)?\s*(?:<\uFF5Ctool\u2581sep\uFF5C>)?\s*([\w.-]+)\s*\n?([\s\S]*?)<\uFF5Ctool\u2581call\u2581end\uFF5C>/g
|
|
4159
4265
|
};
|
|
4160
4266
|
DEEPSEEK_WRAPPER_RE = /<\uFF5Ctool\u2581calls\u2581(?:begin|end)\uFF5C>/g;
|
|
4267
|
+
FUNCTION_CALLS_WRAPPER_RE = /<\/?function_calls\s*>/gi;
|
|
4161
4268
|
PARAMETER_TAG_RE = /<parameter\s+name=["']?([\w.-]+)["']?\s*>([\s\S]*?)(?:<\/parameter>|(?=<parameter\s)|$)/gi;
|
|
4162
4269
|
PARAMETER_EQ_RE = /<parameter=([\w.-]+)>([\s\S]*?)(?=<parameter=|<\/function>|$)/gi;
|
|
4163
4270
|
});
|
|
@@ -5046,6 +5153,14 @@ function isProviderBillingOrQuotaError(error) {
|
|
|
5046
5153
|
function isContextOverflowMessage(message) {
|
|
5047
5154
|
return contextOverflowPattern.test(message);
|
|
5048
5155
|
}
|
|
5156
|
+
function extractContextLimitTokens(message) {
|
|
5157
|
+
const match = contextLimitPattern.exec(message);
|
|
5158
|
+
if (!match) {
|
|
5159
|
+
return;
|
|
5160
|
+
}
|
|
5161
|
+
const value = Number.parseInt(match[1], 10);
|
|
5162
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
5163
|
+
}
|
|
5049
5164
|
function isAbortError(error) {
|
|
5050
5165
|
return error instanceof Error && error.name === "AbortError" || normalizeErrorMessage(getErrorMessage(error)).includes("aborted");
|
|
5051
5166
|
}
|
|
@@ -5086,7 +5201,8 @@ function classifyChatError(error) {
|
|
|
5086
5201
|
kind,
|
|
5087
5202
|
statusCode: error.statusCode,
|
|
5088
5203
|
retryable,
|
|
5089
|
-
message: message2
|
|
5204
|
+
message: message2,
|
|
5205
|
+
contextLimitTokens: kind === "context_overflow" ? extractContextLimitTokens(message2) : undefined
|
|
5090
5206
|
};
|
|
5091
5207
|
}
|
|
5092
5208
|
return {
|
|
@@ -5100,7 +5216,12 @@ function classifyChatError(error) {
|
|
|
5100
5216
|
return { kind: "aborted", retryable: false, message };
|
|
5101
5217
|
}
|
|
5102
5218
|
if (isContextOverflowMessage(message)) {
|
|
5103
|
-
return {
|
|
5219
|
+
return {
|
|
5220
|
+
kind: "context_overflow",
|
|
5221
|
+
retryable: true,
|
|
5222
|
+
message,
|
|
5223
|
+
contextLimitTokens: extractContextLimitTokens(message)
|
|
5224
|
+
};
|
|
5104
5225
|
}
|
|
5105
5226
|
if (isProviderBillingOrQuotaError(error)) {
|
|
5106
5227
|
return { kind: "billing", retryable: false, message };
|
|
@@ -5166,7 +5287,7 @@ function logChatDisconnectEvent({
|
|
|
5166
5287
|
message: getErrorMessage(error)
|
|
5167
5288
|
});
|
|
5168
5289
|
}
|
|
5169
|
-
var chatFailureCounters, logger2, retryableStatusCodes, contextOverflowPattern;
|
|
5290
|
+
var chatFailureCounters, logger2, retryableStatusCodes, contextOverflowPattern, contextLimitPattern;
|
|
5170
5291
|
var init_chat_observability = __esm(() => {
|
|
5171
5292
|
init_src();
|
|
5172
5293
|
chatFailureCounters = {
|
|
@@ -5185,6 +5306,7 @@ var init_chat_observability = __esm(() => {
|
|
|
5185
5306
|
logger2 = createLogger("chat");
|
|
5186
5307
|
retryableStatusCodes = new Set([408, 409, 429, 500, 502, 503, 504]);
|
|
5187
5308
|
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;
|
|
5309
|
+
contextLimitPattern = /(?:maximum context (?:length|window)|max(?:imum)? input (?:length|tokens)|context (?:length|window) of)\D{0,40}?(\d{4,})/i;
|
|
5188
5310
|
});
|
|
5189
5311
|
|
|
5190
5312
|
// packages/db/generated/prisma/internal/class.ts
|
|
@@ -6080,9 +6202,6 @@ var init_resolve_within_workspace = __esm(() => {
|
|
|
6080
6202
|
WORKSPACE = defaultWorkspaceContext.root;
|
|
6081
6203
|
});
|
|
6082
6204
|
|
|
6083
|
-
// packages/ai/src/common/output-utils.ts
|
|
6084
|
-
var init_output_utils = () => {};
|
|
6085
|
-
|
|
6086
6205
|
// packages/ai/src/bash/runtime.ts
|
|
6087
6206
|
import { exec } from "child_process";
|
|
6088
6207
|
import { promisify } from "util";
|
|
@@ -6118,11 +6237,15 @@ var init_runtime5 = __esm(() => {
|
|
|
6118
6237
|
import { createTwoFilesPatch } from "diff";
|
|
6119
6238
|
var init_unified_diff = () => {};
|
|
6120
6239
|
|
|
6240
|
+
// packages/ai/src/edit-file/match.ts
|
|
6241
|
+
var init_match = () => {};
|
|
6242
|
+
|
|
6121
6243
|
// packages/ai/src/edit-file/runtime.ts
|
|
6122
6244
|
var init_runtime6 = __esm(() => {
|
|
6123
6245
|
init_runtime5();
|
|
6124
6246
|
init_resolve_within_workspace();
|
|
6125
6247
|
init_unified_diff();
|
|
6248
|
+
init_match();
|
|
6126
6249
|
init_schema2();
|
|
6127
6250
|
});
|
|
6128
6251
|
|
|
@@ -6169,6 +6292,112 @@ var init_runtime8 = __esm(() => {
|
|
|
6169
6292
|
// packages/ai/src/git/runtime.ts
|
|
6170
6293
|
import { execFile as execFile2 } from "child_process";
|
|
6171
6294
|
import { promisify as promisify3 } from "util";
|
|
6295
|
+
function isRecord7(value) {
|
|
6296
|
+
return typeof value === "object" && value !== null;
|
|
6297
|
+
}
|
|
6298
|
+
function getStringProperty(record, key) {
|
|
6299
|
+
const value = record[key];
|
|
6300
|
+
return typeof value === "string" ? value : "";
|
|
6301
|
+
}
|
|
6302
|
+
function getExitCode(error) {
|
|
6303
|
+
const code = error.code;
|
|
6304
|
+
return typeof code === "number" ? code : null;
|
|
6305
|
+
}
|
|
6306
|
+
async function runGit(args, workspaceContext, maxBuffer = 10 * 1024 * 1024) {
|
|
6307
|
+
try {
|
|
6308
|
+
const result = await execFileAsync2("git", args, {
|
|
6309
|
+
cwd: workspaceContext.root,
|
|
6310
|
+
maxBuffer,
|
|
6311
|
+
windowsHide: true
|
|
6312
|
+
});
|
|
6313
|
+
return {
|
|
6314
|
+
ok: true,
|
|
6315
|
+
stdout: result.stdout,
|
|
6316
|
+
stderr: result.stderr,
|
|
6317
|
+
exitCode: 0
|
|
6318
|
+
};
|
|
6319
|
+
} catch (error) {
|
|
6320
|
+
const record = isRecord7(error) ? error : {};
|
|
6321
|
+
const code = record.code;
|
|
6322
|
+
const codeText = typeof code === "string" ? code : "git_failed";
|
|
6323
|
+
const message = getStringProperty(record, "message") || "Git command failed.";
|
|
6324
|
+
return {
|
|
6325
|
+
ok: false,
|
|
6326
|
+
error: {
|
|
6327
|
+
code: codeText,
|
|
6328
|
+
message,
|
|
6329
|
+
exitCode: getExitCode(record),
|
|
6330
|
+
stdout: getStringProperty(record, "stdout"),
|
|
6331
|
+
stderr: getStringProperty(record, "stderr")
|
|
6332
|
+
}
|
|
6333
|
+
};
|
|
6334
|
+
}
|
|
6335
|
+
}
|
|
6336
|
+
function parseBranchLine(line) {
|
|
6337
|
+
const branchLine = line.replace(/^##\s*/, "");
|
|
6338
|
+
const [branchSegment, trackingSegment] = branchLine.split("...");
|
|
6339
|
+
const branch = branchSegment && branchSegment !== "No commits yet on " ? branchSegment.replace(/^No commits yet on\s+/, "") : null;
|
|
6340
|
+
const trackingMatch = trackingSegment?.match(/^([^\s]+)(?:\s+\[(.*)\])?/);
|
|
6341
|
+
const upstream = trackingMatch?.[1] ?? null;
|
|
6342
|
+
const trackingDetails = trackingMatch?.[2] ?? "";
|
|
6343
|
+
const aheadMatch = trackingDetails.match(/ahead\s+(\d+)/);
|
|
6344
|
+
const behindMatch = trackingDetails.match(/behind\s+(\d+)/);
|
|
6345
|
+
return {
|
|
6346
|
+
branch,
|
|
6347
|
+
upstream,
|
|
6348
|
+
ahead: aheadMatch ? Number(aheadMatch[1]) : 0,
|
|
6349
|
+
behind: behindMatch ? Number(behindMatch[1]) : 0
|
|
6350
|
+
};
|
|
6351
|
+
}
|
|
6352
|
+
function parseStatusEntries(stdout, maxEntries) {
|
|
6353
|
+
const lines = stdout.split(/\r?\n/).filter((line) => line.length > 0);
|
|
6354
|
+
const branchInfo = lines[0]?.startsWith("##") ? parseBranchLine(lines[0]) : { branch: null, upstream: null, ahead: 0, behind: 0 };
|
|
6355
|
+
const statusLines = lines.filter((line) => !line.startsWith("##"));
|
|
6356
|
+
const entries = statusLines.slice(0, maxEntries).map((line) => {
|
|
6357
|
+
const indexStatus = line[0] === " " ? "" : line[0] ?? "";
|
|
6358
|
+
const workingTreeStatus = line[1] === " " ? "" : line[1] ?? "";
|
|
6359
|
+
const rawPath = line.slice(3);
|
|
6360
|
+
const renameParts = rawPath.split(" -> ");
|
|
6361
|
+
const originalPath = renameParts.length > 1 ? renameParts[0] : null;
|
|
6362
|
+
const filePath = renameParts.length > 1 ? renameParts[1] : rawPath;
|
|
6363
|
+
return {
|
|
6364
|
+
path: filePath,
|
|
6365
|
+
indexStatus,
|
|
6366
|
+
workingTreeStatus,
|
|
6367
|
+
originalPath
|
|
6368
|
+
};
|
|
6369
|
+
});
|
|
6370
|
+
return {
|
|
6371
|
+
...branchInfo,
|
|
6372
|
+
entries,
|
|
6373
|
+
totalEntries: statusLines.length,
|
|
6374
|
+
truncated: statusLines.length > entries.length
|
|
6375
|
+
};
|
|
6376
|
+
}
|
|
6377
|
+
async function executeGitStatus(input, workspaceContext = getDefaultWorkspaceContext()) {
|
|
6378
|
+
const parsedInput = gitStatusInputSchema.parse(input);
|
|
6379
|
+
const result = await runGit(["status", "--porcelain=v1", "-b", "--untracked-files=all", "--", "."], workspaceContext);
|
|
6380
|
+
if (!result.ok) {
|
|
6381
|
+
return gitStatusOutputSchema.parse({
|
|
6382
|
+
ok: false,
|
|
6383
|
+
cwd: workspaceContext.root,
|
|
6384
|
+
branch: null,
|
|
6385
|
+
upstream: null,
|
|
6386
|
+
ahead: 0,
|
|
6387
|
+
behind: 0,
|
|
6388
|
+
entries: [],
|
|
6389
|
+
totalEntries: 0,
|
|
6390
|
+
truncated: false,
|
|
6391
|
+
error: result.error
|
|
6392
|
+
});
|
|
6393
|
+
}
|
|
6394
|
+
return gitStatusOutputSchema.parse({
|
|
6395
|
+
ok: true,
|
|
6396
|
+
cwd: workspaceContext.root,
|
|
6397
|
+
...parseStatusEntries(result.stdout, parsedInput.maxEntries),
|
|
6398
|
+
error: null
|
|
6399
|
+
});
|
|
6400
|
+
}
|
|
6172
6401
|
var execFileAsync2;
|
|
6173
6402
|
var init_runtime9 = __esm(() => {
|
|
6174
6403
|
init_resolve_within_workspace();
|
|
@@ -6563,6 +6792,24 @@ var init_overflow_recovery = __esm(() => {
|
|
|
6563
6792
|
overflowRecoveryRounds = new Map;
|
|
6564
6793
|
});
|
|
6565
6794
|
|
|
6795
|
+
// apps/server/src/lib/learned-context-limit.ts
|
|
6796
|
+
function noteLearnedContextLimit(modelId, limitTokens) {
|
|
6797
|
+
if (!modelId || typeof limitTokens !== "number" || !Number.isFinite(limitTokens) || limitTokens <= 0) {
|
|
6798
|
+
return;
|
|
6799
|
+
}
|
|
6800
|
+
const existing = learnedLimits.get(modelId);
|
|
6801
|
+
if (existing === undefined || limitTokens < existing) {
|
|
6802
|
+
learnedLimits.set(modelId, Math.floor(limitTokens));
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
6805
|
+
function getLearnedContextLimit(modelId) {
|
|
6806
|
+
return learnedLimits.get(modelId) ?? null;
|
|
6807
|
+
}
|
|
6808
|
+
var learnedLimits;
|
|
6809
|
+
var init_learned_context_limit = __esm(() => {
|
|
6810
|
+
learnedLimits = new Map;
|
|
6811
|
+
});
|
|
6812
|
+
|
|
6566
6813
|
// apps/server/src/lib/sse-heartbeat.ts
|
|
6567
6814
|
function endsWithEventBoundary(chunk) {
|
|
6568
6815
|
const length = chunk.length;
|
|
@@ -6788,6 +7035,16 @@ function getAnthropicProviderOptions(modelId) {
|
|
|
6788
7035
|
}
|
|
6789
7036
|
};
|
|
6790
7037
|
}
|
|
7038
|
+
function getOpenRouterProviderOptions() {
|
|
7039
|
+
return {
|
|
7040
|
+
openrouter: {
|
|
7041
|
+
provider: {
|
|
7042
|
+
allow_fallbacks: true,
|
|
7043
|
+
require_parameters: true
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
};
|
|
7047
|
+
}
|
|
6791
7048
|
function resolveHeadroomRouting(config5, realBaseUrl) {
|
|
6792
7049
|
const headroom = config5.headroom;
|
|
6793
7050
|
if (!headroom?.enabled) {
|
|
@@ -6852,12 +7109,8 @@ function resolveOpenAITransportModel({
|
|
|
6852
7109
|
if (isOpenRouter) {
|
|
6853
7110
|
const referer = getEnvValue(env, "OPENROUTER_HTTP_REFERER");
|
|
6854
7111
|
const title = getEnvValue(env, "OPENROUTER_APP_TITLE", "LIGHTCODE_APP_TITLE");
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
}
|
|
6858
|
-
if (title) {
|
|
6859
|
-
headers["X-OpenRouter-Title"] = title;
|
|
6860
|
-
}
|
|
7112
|
+
headers["HTTP-Referer"] = referer ?? "https://github.com/kmugalkhod/lightcode";
|
|
7113
|
+
headers["X-Title"] = title ?? "Lightcode";
|
|
6861
7114
|
}
|
|
6862
7115
|
const provider = createOpenAICompatible({
|
|
6863
7116
|
name: config5.provider,
|
|
@@ -6874,6 +7127,7 @@ function resolveOpenAITransportModel({
|
|
|
6874
7127
|
headroomRouted,
|
|
6875
7128
|
model: withXmlToolCallSupport(provider(configuredModel)),
|
|
6876
7129
|
needsToolCallDiscipline: true,
|
|
7130
|
+
providerOptions: isOpenRouter ? getOpenRouterProviderOptions() : undefined,
|
|
6877
7131
|
contextWindow: capabilities?.contextLength ?? getModelContextWindow(config5.provider, configuredModel),
|
|
6878
7132
|
missingCredentialHints: apiKey ? [] : isOpenCodeZen ? ["Set OPENCODE_API_KEY to call OpenCode Zen models."] : isOpenRouter ? ["Set OPENROUTER_API_KEY to call OpenRouter models."] : [
|
|
6879
7133
|
"Set LIGHTCODE_OPENAI_COMPATIBLE_API_KEY or OPENAI_API_KEY if your OpenAI-compatible endpoint requires authentication."
|
|
@@ -6939,6 +7193,7 @@ var init_provider_registry = __esm(() => {
|
|
|
6939
7193
|
var exports_runtime_config = {};
|
|
6940
7194
|
__export(exports_runtime_config, {
|
|
6941
7195
|
resolvedProviderModel: () => resolvedProviderModel,
|
|
7196
|
+
reloadRuntimeConfig: () => reloadRuntimeConfig,
|
|
6942
7197
|
lightcodeConfigResult: () => lightcodeConfigResult,
|
|
6943
7198
|
initializeModelCapabilities: () => initializeModelCapabilities,
|
|
6944
7199
|
disableHeadroomRouting: () => disableHeadroomRouting,
|
|
@@ -6999,6 +7254,31 @@ function applyModelSelection({
|
|
|
6999
7254
|
});
|
|
7000
7255
|
return configStatus;
|
|
7001
7256
|
}
|
|
7257
|
+
function reloadRuntimeConfig() {
|
|
7258
|
+
const nextConfigResult = loadLightcodeConfig({
|
|
7259
|
+
cwd: process.cwd(),
|
|
7260
|
+
env: Bun.env
|
|
7261
|
+
});
|
|
7262
|
+
const nextResolved = resolveConfiguredProviderModel({
|
|
7263
|
+
config: nextConfigResult.config,
|
|
7264
|
+
env: Bun.env
|
|
7265
|
+
});
|
|
7266
|
+
const nextAgent = buildCodingAgent(nextConfigResult.config, nextResolved);
|
|
7267
|
+
lightcodeConfigResult = nextConfigResult;
|
|
7268
|
+
resolvedProviderModel = nextResolved;
|
|
7269
|
+
chatModelId = nextResolved.resolvedModelId;
|
|
7270
|
+
codingAgent = nextAgent;
|
|
7271
|
+
configStatus = createConfigStatus({
|
|
7272
|
+
config: nextConfigResult.config,
|
|
7273
|
+
loadedFiles: nextConfigResult.loadedFiles,
|
|
7274
|
+
resolvedProviderModel: nextResolved
|
|
7275
|
+
});
|
|
7276
|
+
logger5.info("runtime_config_reloaded", {
|
|
7277
|
+
provider: nextConfigResult.config.provider,
|
|
7278
|
+
model: nextConfigResult.config.model
|
|
7279
|
+
});
|
|
7280
|
+
return configStatus;
|
|
7281
|
+
}
|
|
7002
7282
|
function disableHeadroomRouting() {
|
|
7003
7283
|
if (!lightcodeConfigResult.config.headroom?.enabled) {
|
|
7004
7284
|
return;
|
|
@@ -7134,11 +7414,122 @@ var init_session_auto_title = __esm(() => {
|
|
|
7134
7414
|
logger6 = createLogger("session-auto-title");
|
|
7135
7415
|
});
|
|
7136
7416
|
|
|
7417
|
+
// apps/server/src/lib/workspace-context.ts
|
|
7418
|
+
import { readFile as readFile2, readdir } from "fs/promises";
|
|
7419
|
+
import { platform as platform3 } from "os";
|
|
7420
|
+
import path11 from "path";
|
|
7421
|
+
function describePlatform() {
|
|
7422
|
+
const map = {
|
|
7423
|
+
darwin: "macOS",
|
|
7424
|
+
win32: "Windows",
|
|
7425
|
+
linux: "Linux"
|
|
7426
|
+
};
|
|
7427
|
+
return map[platform3()] ?? platform3();
|
|
7428
|
+
}
|
|
7429
|
+
async function buildDirectoryListing(cwd) {
|
|
7430
|
+
try {
|
|
7431
|
+
const dirents = await readdir(cwd, { withFileTypes: true });
|
|
7432
|
+
const names = dirents.filter((entry) => entry.name !== ".git").map((entry) => entry.isDirectory() ? `${entry.name}/` : entry.name).sort((a, b) => a.localeCompare(b));
|
|
7433
|
+
const shown = names.slice(0, maxListedEntries);
|
|
7434
|
+
const suffix = names.length > shown.length ? `
|
|
7435
|
+
\u2026 and ${names.length - shown.length} more` : "";
|
|
7436
|
+
return shown.length > 0 ? shown.join(`
|
|
7437
|
+
`) + suffix : "(empty directory)";
|
|
7438
|
+
} catch (error) {
|
|
7439
|
+
logger7.warn("workspace_listing_failed", { cwd, error: getErrorMessage(error) });
|
|
7440
|
+
return "(unable to list directory)";
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
7443
|
+
async function buildGitSummary(cwd) {
|
|
7444
|
+
try {
|
|
7445
|
+
const context2 = createWorkspaceContext(cwd);
|
|
7446
|
+
const status = await executeGitStatus({}, context2);
|
|
7447
|
+
if (!status.ok) {
|
|
7448
|
+
return "Not a git repository.";
|
|
7449
|
+
}
|
|
7450
|
+
const header = [
|
|
7451
|
+
`branch: ${status.branch ?? "(detached)"}`,
|
|
7452
|
+
status.ahead || status.behind ? `ahead ${status.ahead}, behind ${status.behind}` : null
|
|
7453
|
+
].filter(Boolean).join(" | ");
|
|
7454
|
+
if (status.totalEntries === 0) {
|
|
7455
|
+
return `${header}
|
|
7456
|
+
working tree clean`;
|
|
7457
|
+
}
|
|
7458
|
+
const lines = status.entries.slice(0, maxListedEntries).map((entry) => `${entry.indexStatus}${entry.workingTreeStatus} ${entry.path}`.trim());
|
|
7459
|
+
const more = status.totalEntries > lines.length ? `
|
|
7460
|
+
\u2026 and ${status.totalEntries - lines.length} more` : "";
|
|
7461
|
+
return `${header}
|
|
7462
|
+
changes:
|
|
7463
|
+
${lines.join(`
|
|
7464
|
+
`)}${more}`;
|
|
7465
|
+
} catch (error) {
|
|
7466
|
+
logger7.warn("workspace_git_summary_failed", { cwd, error: getErrorMessage(error) });
|
|
7467
|
+
return "Not a git repository.";
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
async function buildProjectDocs(cwd) {
|
|
7471
|
+
const sections = [];
|
|
7472
|
+
for (const name of projectDocNames) {
|
|
7473
|
+
try {
|
|
7474
|
+
const content = (await readFile2(path11.join(cwd, name), "utf8")).trim();
|
|
7475
|
+
if (!content) {
|
|
7476
|
+
continue;
|
|
7477
|
+
}
|
|
7478
|
+
const truncated = content.length > maxDocChars ? `${content.slice(0, maxDocChars)}
|
|
7479
|
+
\u2026 (truncated)` : content;
|
|
7480
|
+
sections.push(`### ${name}
|
|
7481
|
+
${truncated}`);
|
|
7482
|
+
} catch {}
|
|
7483
|
+
}
|
|
7484
|
+
return sections.join(`
|
|
7485
|
+
|
|
7486
|
+
`);
|
|
7487
|
+
}
|
|
7488
|
+
async function buildWorkspaceContext({
|
|
7489
|
+
cwd
|
|
7490
|
+
}) {
|
|
7491
|
+
const [listing, git, docs] = await Promise.all([
|
|
7492
|
+
buildDirectoryListing(cwd),
|
|
7493
|
+
buildGitSummary(cwd),
|
|
7494
|
+
buildProjectDocs(cwd)
|
|
7495
|
+
]);
|
|
7496
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
7497
|
+
const parts = [
|
|
7498
|
+
"<environment>",
|
|
7499
|
+
`Working directory: ${cwd}`,
|
|
7500
|
+
`Platform: ${describePlatform()}`,
|
|
7501
|
+
`Date: ${today}`,
|
|
7502
|
+
"",
|
|
7503
|
+
"Git:",
|
|
7504
|
+
git,
|
|
7505
|
+
"",
|
|
7506
|
+
"Top-level entries:",
|
|
7507
|
+
listing,
|
|
7508
|
+
docs ? `
|
|
7509
|
+
Project instructions:
|
|
7510
|
+
${docs}` : "",
|
|
7511
|
+
"</environment>"
|
|
7512
|
+
];
|
|
7513
|
+
const block = parts.join(`
|
|
7514
|
+
`);
|
|
7515
|
+
return block.length > maxBlockChars ? `${block.slice(0, maxBlockChars)}
|
|
7516
|
+
\u2026 (environment truncated)
|
|
7517
|
+
</environment>` : block;
|
|
7518
|
+
}
|
|
7519
|
+
var logger7, projectDocNames, maxListedEntries = 40, maxDocChars = 1500, maxBlockChars = 8000;
|
|
7520
|
+
var init_workspace_context = __esm(() => {
|
|
7521
|
+
init_runtime17();
|
|
7522
|
+
init_src();
|
|
7523
|
+
logger7 = createLogger("workspace-context");
|
|
7524
|
+
projectDocNames = ["AGENTS.md", "CLAUDE.md", "README.md"];
|
|
7525
|
+
});
|
|
7526
|
+
|
|
7137
7527
|
// apps/server/src/lib/chat-stream.ts
|
|
7138
7528
|
import {
|
|
7139
7529
|
createAgentUIStreamResponse,
|
|
7140
7530
|
consumeStream,
|
|
7141
7531
|
generateId as generateId3,
|
|
7532
|
+
isToolUIPart as isToolUIPart2,
|
|
7142
7533
|
safeValidateUIMessages as safeValidateUIMessages2,
|
|
7143
7534
|
streamText
|
|
7144
7535
|
} from "ai";
|
|
@@ -7158,6 +7549,9 @@ function buildFastChatModelMessages(messages) {
|
|
|
7158
7549
|
content: collectMessageText(message)
|
|
7159
7550
|
})).filter((message) => message.content.length > 0).slice(-fastChatRecentMessageCount);
|
|
7160
7551
|
}
|
|
7552
|
+
function messagesHaveToolActivity(messages) {
|
|
7553
|
+
return messages.some((message) => message.parts?.some((part) => isToolUIPart2(part)));
|
|
7554
|
+
}
|
|
7161
7555
|
function shouldUseFastChatPath({
|
|
7162
7556
|
messages,
|
|
7163
7557
|
mode,
|
|
@@ -7166,6 +7560,9 @@ function shouldUseFastChatPath({
|
|
|
7166
7560
|
if (allowedTools && allowedTools.length > 0) {
|
|
7167
7561
|
return false;
|
|
7168
7562
|
}
|
|
7563
|
+
if (messagesHaveToolActivity(messages)) {
|
|
7564
|
+
return false;
|
|
7565
|
+
}
|
|
7169
7566
|
return selectCodingAgentIntentTools({
|
|
7170
7567
|
mode,
|
|
7171
7568
|
prompt: "",
|
|
@@ -7180,7 +7577,7 @@ async function countPendingChatInteractions(sessionId) {
|
|
|
7180
7577
|
});
|
|
7181
7578
|
return pendingInteractions.interactions.length;
|
|
7182
7579
|
} catch (error) {
|
|
7183
|
-
|
|
7580
|
+
logger8.warn("context_compaction_pending_interactions_unavailable", {
|
|
7184
7581
|
sessionId,
|
|
7185
7582
|
error: getErrorMessage(error)
|
|
7186
7583
|
});
|
|
@@ -7191,7 +7588,7 @@ async function loadSessionContextStateSafe(sessionId) {
|
|
|
7191
7588
|
try {
|
|
7192
7589
|
return await getSessionContextState(sessionId);
|
|
7193
7590
|
} catch (error) {
|
|
7194
|
-
|
|
7591
|
+
logger8.warn("context_state_unavailable", {
|
|
7195
7592
|
sessionId,
|
|
7196
7593
|
error: getErrorMessage(error)
|
|
7197
7594
|
});
|
|
@@ -7240,7 +7637,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7240
7637
|
staleSkip: false
|
|
7241
7638
|
});
|
|
7242
7639
|
} catch (error) {
|
|
7243
|
-
|
|
7640
|
+
logger8.error("chat_pre_stream_persist_failed", {
|
|
7244
7641
|
sessionId,
|
|
7245
7642
|
error: getErrorMessage(error)
|
|
7246
7643
|
});
|
|
@@ -7251,6 +7648,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7251
7648
|
}
|
|
7252
7649
|
const contextConfig = lightcodeConfigResult.config.context;
|
|
7253
7650
|
const cacheActive = resolvedProviderModel.provider === "anthropic";
|
|
7651
|
+
const reservedOutputTokens = resolveMaxOutputTokens(lightcodeConfigResult.config.maxOutputTokens, lightcodeConfigDefaults.maxOutputTokens, resolvedProviderModel.maxCompletionTokens);
|
|
7652
|
+
const endpointLimitTokens = getLearnedContextLimit(chatModelId);
|
|
7254
7653
|
const pendingInteractionCount = await countPendingChatInteractions(sessionId);
|
|
7255
7654
|
let contextState = await loadSessionContextStateSafe(sessionId);
|
|
7256
7655
|
let view = buildProviderView({
|
|
@@ -7258,44 +7657,56 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7258
7657
|
contextState,
|
|
7259
7658
|
config: contextConfig,
|
|
7260
7659
|
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
7660
|
+
reservedOutputTokens,
|
|
7661
|
+
endpointLimitTokens,
|
|
7261
7662
|
pendingInteractionCount,
|
|
7262
7663
|
cacheActive
|
|
7263
7664
|
});
|
|
7264
|
-
if (
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
contextState
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7665
|
+
if (contextConfig.autoCompact) {
|
|
7666
|
+
let compactionRound = 0;
|
|
7667
|
+
while (view.needsCompaction && compactionRound < MAX_COMPACTION_ROUNDS) {
|
|
7668
|
+
compactionRound += 1;
|
|
7669
|
+
try {
|
|
7670
|
+
const estimatedTokensBefore = view.estimate.tokens;
|
|
7671
|
+
const compaction = await compactSessionContext({
|
|
7672
|
+
sessionId,
|
|
7673
|
+
coveredMessages: view.coveredMessages,
|
|
7674
|
+
previousState: contextState,
|
|
7675
|
+
model: resolvedProviderModel.model,
|
|
7676
|
+
modelId: chatModelId,
|
|
7677
|
+
cwd,
|
|
7678
|
+
config: contextConfig,
|
|
7679
|
+
estimatedTokens: estimatedTokensBefore
|
|
7680
|
+
});
|
|
7681
|
+
contextState = compaction.state;
|
|
7682
|
+
view = buildProviderView({
|
|
7683
|
+
messages: validatedMessages,
|
|
7684
|
+
contextState,
|
|
7685
|
+
config: contextConfig,
|
|
7686
|
+
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
7687
|
+
reservedOutputTokens,
|
|
7688
|
+
endpointLimitTokens,
|
|
7689
|
+
pendingInteractionCount,
|
|
7690
|
+
cacheActive
|
|
7691
|
+
});
|
|
7692
|
+
logger8.info("context_auto_compacted", {
|
|
7693
|
+
sessionId,
|
|
7694
|
+
round: compactionRound,
|
|
7695
|
+
tier: compaction.state.tier,
|
|
7696
|
+
usedFallback: compaction.usedFallback,
|
|
7697
|
+
coveredMessageCount: compaction.state.coveredMessageCount,
|
|
7698
|
+
estimatedTokensBefore,
|
|
7699
|
+
estimatedTokensAfter: view.estimate.tokens,
|
|
7700
|
+
stillNeedsCompaction: view.needsCompaction
|
|
7701
|
+
});
|
|
7702
|
+
} catch (error) {
|
|
7703
|
+
logger8.error("context_compaction_failed", {
|
|
7704
|
+
sessionId,
|
|
7705
|
+
round: compactionRound,
|
|
7706
|
+
error: getErrorMessage(error)
|
|
7707
|
+
});
|
|
7708
|
+
break;
|
|
7709
|
+
}
|
|
7299
7710
|
}
|
|
7300
7711
|
}
|
|
7301
7712
|
const overflowPreserve = getOverflowPreserveRecentMessages(sessionId);
|
|
@@ -7310,6 +7721,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7310
7721
|
contextState,
|
|
7311
7722
|
config: overflowConfig,
|
|
7312
7723
|
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
7724
|
+
reservedOutputTokens,
|
|
7725
|
+
endpointLimitTokens,
|
|
7313
7726
|
pendingInteractionCount: 0,
|
|
7314
7727
|
cacheActive
|
|
7315
7728
|
});
|
|
@@ -7331,10 +7744,12 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7331
7744
|
contextState,
|
|
7332
7745
|
config: overflowConfig,
|
|
7333
7746
|
modelContextWindow: resolvedProviderModel.contextWindow,
|
|
7747
|
+
reservedOutputTokens,
|
|
7748
|
+
endpointLimitTokens,
|
|
7334
7749
|
pendingInteractionCount: 0,
|
|
7335
7750
|
cacheActive
|
|
7336
7751
|
});
|
|
7337
|
-
|
|
7752
|
+
logger8.info("context_overflow_recovery", {
|
|
7338
7753
|
sessionId,
|
|
7339
7754
|
preserveRecentMessages: overflowConfig.preserveRecentMessages,
|
|
7340
7755
|
estimatedTokens: view.estimate.tokens,
|
|
@@ -7343,13 +7758,30 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7343
7758
|
compactedMessages: view.coveredMessages.length
|
|
7344
7759
|
});
|
|
7345
7760
|
} catch (error) {
|
|
7346
|
-
|
|
7761
|
+
logger8.error("context_overflow_recovery_failed", {
|
|
7347
7762
|
sessionId,
|
|
7348
7763
|
error: getErrorMessage(error)
|
|
7349
7764
|
});
|
|
7350
7765
|
}
|
|
7351
7766
|
}
|
|
7352
|
-
const
|
|
7767
|
+
const fitted = fitMessagesToBudget(view.providerMessages, {
|
|
7768
|
+
inputBudgetTokens: view.inputBudgetTokens,
|
|
7769
|
+
preserveRecentMessages: contextConfig.preserveRecentMessages
|
|
7770
|
+
});
|
|
7771
|
+
if (fitted.fitted) {
|
|
7772
|
+
logger8.warn("context_fit_to_budget", {
|
|
7773
|
+
sessionId,
|
|
7774
|
+
inputBudgetTokens: view.inputBudgetTokens,
|
|
7775
|
+
contextWindow: view.contextWindow,
|
|
7776
|
+
estimatedTokensBefore: estimateStructuralTokens(view.providerMessages),
|
|
7777
|
+
estimatedTokensAfter: fitted.estimatedTokens,
|
|
7778
|
+
elidedToolOutputs: fitted.elidedToolOutputs,
|
|
7779
|
+
droppedMessages: fitted.droppedMessages,
|
|
7780
|
+
truncatedTextParts: fitted.truncatedTextParts,
|
|
7781
|
+
withinBudget: fitted.withinBudget
|
|
7782
|
+
});
|
|
7783
|
+
}
|
|
7784
|
+
const providerMessages = normalizeProviderMessages(fitted.messages);
|
|
7353
7785
|
let overflowNotedThisRequest = false;
|
|
7354
7786
|
const noteOverflowForSession = () => {
|
|
7355
7787
|
if (overflowNotedThisRequest) {
|
|
@@ -7379,14 +7811,14 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7379
7811
|
});
|
|
7380
7812
|
const normalizedMessages = removeTrailingEmptyAssistantMessages(fullHistory);
|
|
7381
7813
|
if (normalizedMessages.length !== fullHistory.length) {
|
|
7382
|
-
|
|
7814
|
+
logger8.warn("chat_finish_empty_assistant_skipped", { sessionId });
|
|
7383
7815
|
return;
|
|
7384
7816
|
}
|
|
7385
7817
|
const validatedFinishResult = await safeValidateUIMessages2({
|
|
7386
7818
|
messages: normalizedMessages
|
|
7387
7819
|
});
|
|
7388
7820
|
if (!validatedFinishResult.success) {
|
|
7389
|
-
|
|
7821
|
+
logger8.warn("chat_invalid_finish_payload", {
|
|
7390
7822
|
sessionId,
|
|
7391
7823
|
message: validatedFinishResult.error.message
|
|
7392
7824
|
});
|
|
@@ -7420,7 +7852,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7420
7852
|
messages: validatedFinishResult.data
|
|
7421
7853
|
});
|
|
7422
7854
|
} catch (error) {
|
|
7423
|
-
|
|
7855
|
+
logger8.error("chat_finish_persist_failed", {
|
|
7424
7856
|
sessionId,
|
|
7425
7857
|
error: getErrorMessage(error)
|
|
7426
7858
|
});
|
|
@@ -7443,22 +7875,30 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7443
7875
|
totalTokens: part.totalUsage.totalTokens
|
|
7444
7876
|
},
|
|
7445
7877
|
modelId: chatModelId,
|
|
7446
|
-
finishReason: part.finishReason
|
|
7878
|
+
finishReason: part.finishReason,
|
|
7879
|
+
context: {
|
|
7880
|
+
inputTokens: view.estimate.tokens,
|
|
7881
|
+
contextWindow: view.contextWindow,
|
|
7882
|
+
compactedMessages: contextState?.coveredMessageCount ?? 0
|
|
7883
|
+
}
|
|
7447
7884
|
};
|
|
7448
7885
|
};
|
|
7886
|
+
const environmentContext = await buildWorkspaceContext({ cwd });
|
|
7449
7887
|
try {
|
|
7450
7888
|
if (shouldUseFastChatPath({
|
|
7451
7889
|
messages: providerMessages,
|
|
7452
7890
|
mode,
|
|
7453
7891
|
allowedTools
|
|
7454
7892
|
})) {
|
|
7455
|
-
|
|
7893
|
+
logger8.info("chat_fast_path", {
|
|
7456
7894
|
sessionId,
|
|
7457
7895
|
model: chatModelId
|
|
7458
7896
|
});
|
|
7459
7897
|
const result = streamText({
|
|
7460
7898
|
model: resolvedProviderModel.model,
|
|
7461
|
-
system: fastChatSystemPrompt
|
|
7899
|
+
system: `${fastChatSystemPrompt}
|
|
7900
|
+
|
|
7901
|
+
${environmentContext}`,
|
|
7462
7902
|
messages: buildFastChatModelMessages(providerMessages),
|
|
7463
7903
|
maxOutputTokens: Math.min(lightcodeConfigResult.config.maxOutputTokens, fastChatMaxOutputTokens),
|
|
7464
7904
|
maxRetries: lightcodeConfigResult.config.maxRetries,
|
|
@@ -7488,6 +7928,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7488
7928
|
onError: (error) => {
|
|
7489
7929
|
const classified = classifyChatError(error);
|
|
7490
7930
|
if (classified.kind === "context_overflow") {
|
|
7931
|
+
noteLearnedContextLimit(chatModelId, classified.contextLimitTokens);
|
|
7491
7932
|
noteOverflowForSession();
|
|
7492
7933
|
}
|
|
7493
7934
|
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), { sessionId, statusCode: classified.statusCode });
|
|
@@ -7505,6 +7946,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7505
7946
|
const mapStreamError = (error) => {
|
|
7506
7947
|
const classified = classifyChatError(error);
|
|
7507
7948
|
if (classified.kind === "context_overflow") {
|
|
7949
|
+
noteLearnedContextLimit(chatModelId, classified.contextLimitTokens);
|
|
7508
7950
|
noteOverflowForSession();
|
|
7509
7951
|
}
|
|
7510
7952
|
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), {
|
|
@@ -7528,7 +7970,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7528
7970
|
permissionMode,
|
|
7529
7971
|
allowedTools,
|
|
7530
7972
|
permissionRules,
|
|
7531
|
-
sandbox
|
|
7973
|
+
sandbox,
|
|
7974
|
+
environmentContext
|
|
7532
7975
|
},
|
|
7533
7976
|
abortSignal: c.req.raw.signal,
|
|
7534
7977
|
generateMessageId: generateId3,
|
|
@@ -7558,6 +8001,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7558
8001
|
} catch (error) {
|
|
7559
8002
|
const classified = classifyChatError(error);
|
|
7560
8003
|
if (classified.kind === "context_overflow") {
|
|
8004
|
+
noteLearnedContextLimit(chatModelId, classified.contextLimitTokens);
|
|
7561
8005
|
noteContextOverflow(sessionId);
|
|
7562
8006
|
}
|
|
7563
8007
|
incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), {
|
|
@@ -7588,7 +8032,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7588
8032
|
}, 500);
|
|
7589
8033
|
}
|
|
7590
8034
|
}
|
|
7591
|
-
var
|
|
8035
|
+
var logger8, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, fastChatRecentMessageCount = 6, MAX_COMPACTION_ROUNDS = 3;
|
|
7592
8036
|
var init_chat_stream = __esm(() => {
|
|
7593
8037
|
init_src2();
|
|
7594
8038
|
init_src();
|
|
@@ -7597,13 +8041,15 @@ var init_chat_stream = __esm(() => {
|
|
|
7597
8041
|
init_chat_store();
|
|
7598
8042
|
init_context_compaction();
|
|
7599
8043
|
init_overflow_recovery();
|
|
8044
|
+
init_learned_context_limit();
|
|
7600
8045
|
init_sse_heartbeat();
|
|
7601
8046
|
init_context_state_store();
|
|
7602
8047
|
init_session_auto_title();
|
|
8048
|
+
init_workspace_context();
|
|
7603
8049
|
init_runtime_config();
|
|
7604
|
-
|
|
8050
|
+
logger8 = createLogger("chat-stream");
|
|
7605
8051
|
providerBillingOrQuotaMessage = "The configured model provider rejected this request due to billing or quota limits. " + "Update provider credits/quota and retry.";
|
|
7606
|
-
fastChatSystemPrompt = "You are Lightcode's
|
|
8052
|
+
fastChatSystemPrompt = "You are Lightcode's coding assistant, running inside the user's project. " + "For casual conversation, reply briefly and naturally. " + "If the user asks about or wants work on their code, do not ask them to paste it \u2014 you can read the project files directly; offer to look and proceed.";
|
|
7607
8053
|
});
|
|
7608
8054
|
|
|
7609
8055
|
// apps/server/src/routes/route-helpers.ts
|
|
@@ -7612,16 +8058,16 @@ function internalErrorResponse(c, {
|
|
|
7612
8058
|
message,
|
|
7613
8059
|
error
|
|
7614
8060
|
}) {
|
|
7615
|
-
|
|
8061
|
+
logger9.error(event, { error: getErrorMessage(error) });
|
|
7616
8062
|
return c.json({
|
|
7617
8063
|
error: message,
|
|
7618
8064
|
details: Bun.env.NODE_ENV === "production" ? undefined : getErrorMessage(error)
|
|
7619
8065
|
}, 500);
|
|
7620
8066
|
}
|
|
7621
|
-
var
|
|
8067
|
+
var logger9;
|
|
7622
8068
|
var init_route_helpers = __esm(() => {
|
|
7623
8069
|
init_src();
|
|
7624
|
-
|
|
8070
|
+
logger9 = createLogger("routes");
|
|
7625
8071
|
});
|
|
7626
8072
|
|
|
7627
8073
|
// apps/server/src/routes/chat-routes.ts
|
|
@@ -7916,7 +8362,7 @@ var init_config_routes = __esm(() => {
|
|
|
7916
8362
|
provider: lightcodeProviderSchema,
|
|
7917
8363
|
model: z35.string().min(1).max(240)
|
|
7918
8364
|
});
|
|
7919
|
-
configRoutes = new Hono2().get("/status", (c) => c.json(configStatus)).get("/models", async (c) => {
|
|
8365
|
+
configRoutes = new Hono2().get("/status", (c) => c.json(configStatus)).post("/reload", (c) => c.json({ ok: true, configStatus: reloadRuntimeConfig() })).get("/models", async (c) => {
|
|
7920
8366
|
const provider = c.req.query("provider") ?? "openrouter";
|
|
7921
8367
|
if (provider !== "openrouter") {
|
|
7922
8368
|
return c.json({ error: `Model listing is not supported for provider "${provider}".` }, 400);
|
|
@@ -7987,7 +8433,7 @@ var init_extension_runtime = __esm(() => {
|
|
|
7987
8433
|
|
|
7988
8434
|
// apps/server/src/routes/diagnostics-routes.ts
|
|
7989
8435
|
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
7990
|
-
import
|
|
8436
|
+
import path12 from "path";
|
|
7991
8437
|
import { Hono as Hono3 } from "hono";
|
|
7992
8438
|
function combineStatuses(statuses) {
|
|
7993
8439
|
if (statuses.includes("error")) {
|
|
@@ -8309,7 +8755,7 @@ async function buildLogsTailPayload(requestedLines) {
|
|
|
8309
8755
|
lines: []
|
|
8310
8756
|
};
|
|
8311
8757
|
}
|
|
8312
|
-
const filePath =
|
|
8758
|
+
const filePath = path12.join(directory, latestFile);
|
|
8313
8759
|
const content = await Bun.file(filePath).text();
|
|
8314
8760
|
const lines = content.split(`
|
|
8315
8761
|
`).filter((line) => line.length > 0);
|
|
@@ -8432,36 +8878,36 @@ async function ensureHeadroomRouting() {
|
|
|
8432
8878
|
health = await checkHeadroomHealth(headroom.proxyUrl);
|
|
8433
8879
|
} catch (error) {
|
|
8434
8880
|
health = "down";
|
|
8435
|
-
|
|
8881
|
+
logger10.warn("headroom_health_probe_failed", {
|
|
8436
8882
|
proxyUrl: headroom.proxyUrl,
|
|
8437
8883
|
error: getErrorMessage(error)
|
|
8438
8884
|
});
|
|
8439
8885
|
}
|
|
8440
8886
|
if (health === "healthy") {
|
|
8441
|
-
|
|
8887
|
+
logger10.info("headroom_routing_active", { proxyUrl: headroom.proxyUrl });
|
|
8442
8888
|
return;
|
|
8443
8889
|
}
|
|
8444
8890
|
if (headroom.failOpen) {
|
|
8445
|
-
|
|
8891
|
+
logger10.warn("headroom_unreachable_failing_open", {
|
|
8446
8892
|
proxyUrl: headroom.proxyUrl
|
|
8447
8893
|
});
|
|
8448
8894
|
disableHeadroomRouting();
|
|
8449
8895
|
return;
|
|
8450
8896
|
}
|
|
8451
|
-
|
|
8897
|
+
logger10.error("headroom_unreachable_fail_closed", {
|
|
8452
8898
|
proxyUrl: headroom.proxyUrl
|
|
8453
8899
|
});
|
|
8454
8900
|
}
|
|
8455
|
-
var
|
|
8901
|
+
var logger10, healthCheckTimeoutMs = 1500;
|
|
8456
8902
|
var init_headroom_proxy = __esm(() => {
|
|
8457
8903
|
init_src();
|
|
8458
8904
|
init_runtime_config();
|
|
8459
|
-
|
|
8905
|
+
logger10 = createLogger("headroom-proxy");
|
|
8460
8906
|
});
|
|
8461
8907
|
|
|
8462
8908
|
// apps/server/src/index.ts
|
|
8463
8909
|
init_src();
|
|
8464
|
-
var
|
|
8910
|
+
var logger11 = createLogger("server");
|
|
8465
8911
|
var httpIdleTimeoutSeconds = 30;
|
|
8466
8912
|
var shutdownForceExitMs = 5000;
|
|
8467
8913
|
function isChatStreamingRoute(request) {
|
|
@@ -8474,7 +8920,7 @@ function isChatStreamingRoute(request) {
|
|
|
8474
8920
|
async function startServer() {
|
|
8475
8921
|
const logDirectory = enableFileLogSink(Bun.env);
|
|
8476
8922
|
if (logDirectory) {
|
|
8477
|
-
|
|
8923
|
+
logger11.info("file_log_sink_enabled", { directory: logDirectory });
|
|
8478
8924
|
}
|
|
8479
8925
|
const port = Number(Bun.env.PORT ?? 4983);
|
|
8480
8926
|
const hostname = Bun.env.LIGHTCODE_HOST ?? "127.0.0.1";
|
|
@@ -8495,12 +8941,12 @@ async function startServer() {
|
|
|
8495
8941
|
process.exit(1);
|
|
8496
8942
|
}
|
|
8497
8943
|
Promise.resolve().then(() => (init_runtime_config(), exports_runtime_config)).then((runtime19) => runtime19.initializeModelCapabilities()).catch((error) => {
|
|
8498
|
-
|
|
8944
|
+
logger11.warn("model_capability_init_failed", {
|
|
8499
8945
|
error: getErrorMessage(error)
|
|
8500
8946
|
});
|
|
8501
8947
|
});
|
|
8502
8948
|
Promise.resolve().then(() => (init_headroom_proxy(), exports_headroom_proxy)).then((headroom) => headroom.ensureHeadroomRouting()).catch((error) => {
|
|
8503
|
-
|
|
8949
|
+
logger11.warn("headroom_init_failed", {
|
|
8504
8950
|
error: getErrorMessage(error)
|
|
8505
8951
|
});
|
|
8506
8952
|
});
|
|
@@ -8536,7 +8982,7 @@ async function startServer() {
|
|
|
8536
8982
|
return;
|
|
8537
8983
|
}
|
|
8538
8984
|
shuttingDown = true;
|
|
8539
|
-
|
|
8985
|
+
logger11.info("server_shutdown", { signal });
|
|
8540
8986
|
const forceExitTimer = setTimeout(() => {
|
|
8541
8987
|
server.stop(true);
|
|
8542
8988
|
process.exit(0);
|
|
@@ -8551,6 +8997,17 @@ async function startServer() {
|
|
|
8551
8997
|
};
|
|
8552
8998
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
8553
8999
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
9000
|
+
const parentPid = Number(Bun.env.LIGHTCODE_PARENT_PID);
|
|
9001
|
+
if (Number.isInteger(parentPid) && parentPid > 0) {
|
|
9002
|
+
const parentWatch = setInterval(() => {
|
|
9003
|
+
try {
|
|
9004
|
+
process.kill(parentPid, 0);
|
|
9005
|
+
} catch {
|
|
9006
|
+
shutdown("parent-exit");
|
|
9007
|
+
}
|
|
9008
|
+
}, 2000);
|
|
9009
|
+
parentWatch.unref();
|
|
9010
|
+
}
|
|
8554
9011
|
console.log(`Server listening on http://${hostname}:${port}`);
|
|
8555
9012
|
}
|
|
8556
9013
|
if (import.meta.main) {
|