@kmugalkhod/lightcode 0.5.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.
Files changed (3) hide show
  1. package/cli.js +1544 -973
  2. package/package.json +1 -1
  3. package/server.js +388 -249
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 = "Edit a file via literal search and replace. Use this for focused, small edits.", editFileInputSchema, editFileProviderInputSchema, editFileOutputSchema;
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),
@@ -2225,126 +2226,7 @@ function selectCodingAgentIntentTools({
2225
2226
  if (!normalizedText || casualPromptPattern.test(normalizedText)) {
2226
2227
  return [];
2227
2228
  }
2228
- const selectedTools = [];
2229
- const hasContinuationIntent = includesAny(normalizedText, [
2230
- "continue",
2231
- "proceed",
2232
- "go on",
2233
- "go ahead",
2234
- "keep going",
2235
- "carry on",
2236
- "resume",
2237
- "finish",
2238
- "next step"
2239
- ]);
2240
- const hasCodeIntent = includesAny(normalizedText, [
2241
- "file",
2242
- "folder",
2243
- "code",
2244
- "repo",
2245
- "project",
2246
- "implement",
2247
- "fix",
2248
- "add",
2249
- "build",
2250
- "refactor",
2251
- "change",
2252
- "create",
2253
- "update",
2254
- "edit",
2255
- "delete",
2256
- "run",
2257
- "test",
2258
- "typecheck",
2259
- "debug",
2260
- "error",
2261
- "issue",
2262
- "problem",
2263
- "fails",
2264
- "failed",
2265
- "failing",
2266
- "bug",
2267
- "crash",
2268
- "slow",
2269
- "slowness",
2270
- "not working",
2271
- "epic",
2272
- "ticket"
2273
- ]);
2274
- const hasReadIntent = includesAny(normalizedText, [
2275
- "read",
2276
- "list",
2277
- "find",
2278
- "search",
2279
- "grep",
2280
- "glob",
2281
- "inspect",
2282
- "analyse",
2283
- "analyze",
2284
- "check"
2285
- ]);
2286
- const workspaceReferents = [
2287
- "this",
2288
- "these",
2289
- "that",
2290
- "here",
2291
- "code",
2292
- "codebase",
2293
- "repo",
2294
- "repository",
2295
- "project",
2296
- "file",
2297
- "files",
2298
- "directory",
2299
- "folder",
2300
- "app",
2301
- "application",
2302
- "module",
2303
- "my"
2304
- ];
2305
- const strongReviewIntent = includesAny(normalizedText, [
2306
- "review",
2307
- "audit",
2308
- "walk through",
2309
- "walkthrough",
2310
- "code review"
2311
- ]);
2312
- const softReviewIntent = includesAny(normalizedText, [
2313
- "explain",
2314
- "understand",
2315
- "summarize",
2316
- "summarise",
2317
- "overview",
2318
- "describe",
2319
- "explore",
2320
- "look at"
2321
- ]) && includesAny(normalizedText, workspaceReferents);
2322
- const hasReviewIntent = strongReviewIntent || softReviewIntent;
2323
- const hasWriteIntent = mode === "build" && (hasContinuationIntent || includesAny(normalizedText, [
2324
- "write",
2325
- "edit",
2326
- "change",
2327
- "create",
2328
- "implement",
2329
- "fix",
2330
- "update",
2331
- "delete",
2332
- "remove",
2333
- "rename"
2334
- ]));
2335
- const hasShellIntent = mode === "build" && (hasContinuationIntent || includesAny(normalizedText, [
2336
- "run",
2337
- "command",
2338
- "terminal",
2339
- "shell",
2340
- "bash",
2341
- "powershell",
2342
- "bun",
2343
- "tests",
2344
- "run test",
2345
- "run tests",
2346
- "typecheck"
2347
- ]));
2229
+ const situational = [];
2348
2230
  const hasGitIntent = includesAny(normalizedText, [
2349
2231
  "git",
2350
2232
  "diff",
@@ -2355,95 +2237,64 @@ function selectCodingAgentIntentTools({
2355
2237
  "revision",
2356
2238
  "changes"
2357
2239
  ]);
2358
- const hasTodoIntent = includesAny(normalizedText, [
2359
- "todo",
2360
- "tasks",
2361
- "checklist",
2362
- "steps",
2363
- "epic",
2364
- "ticket"
2365
- ]);
2366
- const hasWebIntent = includesAny(normalizedText, [
2367
- "http://",
2368
- "https://",
2369
- "url",
2370
- "fetch",
2371
- "website",
2372
- "web",
2373
- "online",
2374
- "search internet",
2375
- "latest"
2376
- ]);
2377
- const explicitToolSearch = includesAny(normalizedText, [
2378
- "tool_search",
2379
- "tool search",
2380
- "find tool",
2381
- "available tool"
2382
- ]);
2383
- const hasSkillIntent = includesAny(normalizedText, [
2384
- "skill",
2385
- "skills",
2386
- "load skill"
2387
- ]);
2388
- const hasMcpIntent = includesAny(normalizedText, [
2389
- "mcp",
2390
- "resource",
2391
- "resources",
2392
- "mcp tool",
2393
- "mcp server"
2394
- ]);
2395
- if (hasCodeIntent || hasContinuationIntent || hasReviewIntent || hasReadIntent && !hasGitIntent && !hasWebIntent && !explicitToolSearch) {
2396
- for (const toolName of baseReadTools) {
2397
- pushUniqueTool(selectedTools, toolName);
2398
- }
2399
- }
2400
2240
  if (hasGitIntent) {
2401
2241
  if (includesAny(normalizedText, ["status", "state", "dirty", "working tree"]) || !includesAny(normalizedText, ["diff", "log", "show", "history", "commit"])) {
2402
- pushUniqueTool(selectedTools, "git_status");
2242
+ pushUniqueTool(situational, "git_status");
2403
2243
  }
2404
2244
  if (includesAny(normalizedText, ["diff", "changes", "patch"])) {
2405
- pushUniqueTool(selectedTools, "git_diff");
2245
+ pushUniqueTool(situational, "git_diff");
2406
2246
  }
2407
2247
  if (includesAny(normalizedText, ["log", "history", "commits"])) {
2408
- pushUniqueTool(selectedTools, "git_log");
2248
+ pushUniqueTool(situational, "git_log");
2409
2249
  }
2410
2250
  if (includesAny(normalizedText, ["show", "revision", "commit"])) {
2411
- pushUniqueTool(selectedTools, "git_show");
2251
+ pushUniqueTool(situational, "git_show");
2412
2252
  }
2413
2253
  }
2414
- if (hasTodoIntent && mode === "build") {
2415
- pushUniqueTool(selectedTools, "todo_write");
2254
+ if (mode === "build" && includesAny(normalizedText, ["todo", "tasks", "checklist", "steps", "epic", "ticket"])) {
2255
+ pushUniqueTool(situational, "todo_write");
2416
2256
  }
2417
- if (explicitToolSearch) {
2418
- pushUniqueTool(selectedTools, "tool_search");
2257
+ if (includesAny(normalizedText, ["tool_search", "tool search", "find tool", "available tool"])) {
2258
+ pushUniqueTool(situational, "tool_search");
2419
2259
  }
2420
- if (hasSkillIntent) {
2421
- pushUniqueTool(selectedTools, "skill");
2260
+ if (includesAny(normalizedText, ["skill", "skills", "load skill"])) {
2261
+ pushUniqueTool(situational, "skill");
2422
2262
  }
2423
- if (hasMcpIntent) {
2424
- pushUniqueTool(selectedTools, "list_mcp_resources");
2263
+ if (includesAny(normalizedText, ["mcp", "resource", "resources", "mcp tool", "mcp server"])) {
2264
+ pushUniqueTool(situational, "list_mcp_resources");
2425
2265
  if (includesAny(normalizedText, ["read", "resource", "resources"])) {
2426
- pushUniqueTool(selectedTools, "read_mcp_resource");
2266
+ pushUniqueTool(situational, "read_mcp_resource");
2427
2267
  }
2428
2268
  if (mode === "build" && includesAny(normalizedText, ["call", "execute", "run"])) {
2429
- pushUniqueTool(selectedTools, "call_mcp_tool");
2269
+ pushUniqueTool(situational, "call_mcp_tool");
2430
2270
  }
2431
2271
  }
2432
- if (hasWriteIntent) {
2433
- pushUniqueTool(selectedTools, "write_file");
2434
- pushUniqueTool(selectedTools, "edit_file");
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");
2435
2285
  }
2436
- if (hasShellIntent) {
2437
- pushUniqueTool(selectedTools, "bash");
2286
+ if (mode === "plan" && includesAny(normalizedText, ["question", "ask", "clarify"])) {
2287
+ pushUniqueTool(situational, "request_user_input");
2438
2288
  }
2439
- if (hasWebIntent) {
2440
- pushUniqueTool(selectedTools, "web_fetch");
2441
- pushUniqueTool(selectedTools, "web_search");
2289
+ const ordered = [...baseReadTools, ...situational];
2290
+ if (mode === "build") {
2291
+ ordered.push("write_file", "edit_file", "bash");
2442
2292
  }
2443
- if (mode === "plan" && includesAny(normalizedText, ["question", "ask", "clarify"])) {
2444
- pushUniqueTool(selectedTools, "request_user_input");
2293
+ const selected = [];
2294
+ for (const toolName of ordered) {
2295
+ pushUniqueTool(selected, toolName);
2445
2296
  }
2446
- return selectedTools;
2297
+ return selected.slice(0, maxProviderActiveTools);
2447
2298
  }
2448
2299
  function limitProviderActiveTools(tools, maxTools = maxProviderActiveTools) {
2449
2300
  if (tools.length <= maxTools) {
@@ -2714,17 +2565,33 @@ function normalizeContextOptimizerConfig(config) {
2714
2565
  }
2715
2566
  function resolveContextWindowTokens({
2716
2567
  config,
2717
- 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
2718
2581
  }) {
2719
- return config.contextWindowOverride ?? modelContextWindow ?? DEFAULT_CONTEXT_WINDOW_TOKENS;
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));
2720
2586
  }
2721
- 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;
2722
2588
  var init_config3 = __esm(() => {
2723
2589
  contextOptimizerConfigSchema = z26.object({
2724
2590
  autoCompact: z26.boolean().optional(),
2725
2591
  compactAtFraction: z26.number().min(0.3).max(0.95).optional(),
2726
2592
  pruneAtFraction: z26.number().min(0.2).max(0.9).optional(),
2727
2593
  preserveRecentMessages: z26.number().int().min(2).max(100).optional(),
2594
+ maxCoverageTokensPerCompaction: z26.number().int().min(2000).max(200000).optional(),
2728
2595
  contextWindowOverride: z26.number().int().min(8000).max(1e7).optional(),
2729
2596
  summaryMaxChars: z26.number().int().min(200).max(50000).optional(),
2730
2597
  aggressivePruneWhenUncached: z26.boolean().optional(),
@@ -2738,6 +2605,7 @@ var init_config3 = __esm(() => {
2738
2605
  compactAtFraction: z26.number().min(0.3).max(0.95),
2739
2606
  pruneAtFraction: z26.number().min(0.2).max(0.9),
2740
2607
  preserveRecentMessages: z26.number().int().min(2).max(100),
2608
+ maxCoverageTokensPerCompaction: z26.number().int().min(2000).max(200000),
2741
2609
  contextWindowOverride: z26.number().int().min(8000).max(1e7).nullable(),
2742
2610
  summaryMaxChars: z26.number().int().min(200).max(50000),
2743
2611
  aggressivePruneWhenUncached: z26.boolean(),
@@ -2750,6 +2618,7 @@ var init_config3 = __esm(() => {
2750
2618
  compactAtFraction: 0.7,
2751
2619
  pruneAtFraction: 0.6,
2752
2620
  preserveRecentMessages: 6,
2621
+ maxCoverageTokensPerCompaction: 12000,
2753
2622
  contextWindowOverride: null,
2754
2623
  summaryMaxChars: 4000,
2755
2624
  aggressivePruneWhenUncached: true,
@@ -2829,10 +2698,44 @@ var init_estimate = __esm(() => {
2829
2698
  totalTokens: z27.number().nonnegative().optional()
2830
2699
  }),
2831
2700
  modelId: z27.string().optional(),
2832
- 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()
2833
2707
  }).loose();
2834
2708
  });
2835
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
+
2836
2739
  // packages/ai/src/context/heuristic-summary.ts
2837
2740
  function truncateInline(text, maxChars) {
2838
2741
  const normalized = text.replace(/\s+/g, " ").trim();
@@ -3182,6 +3085,127 @@ var init_tier1_prune = __esm(() => {
3182
3085
  init_message_parts();
3183
3086
  });
3184
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
+
3185
3209
  // packages/ai/src/context/build-provider-view.ts
3186
3210
  function computeCompactionCutoffIndex(messages, preserveRecentMessages, afterAnchorIndex) {
3187
3211
  const candidate = messages.length - preserveRecentMessages;
@@ -3195,11 +3219,32 @@ function computeCompactionCutoffIndex(messages, preserveRecentMessages, afterAnc
3195
3219
  }
3196
3220
  return afterAnchorIndex;
3197
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
+ }
3198
3238
  function buildProviderView(options) {
3199
3239
  const { messages, contextState, config } = options;
3200
3240
  const contextWindow = resolveContextWindowTokens({
3201
3241
  config,
3202
- modelContextWindow: options.modelContextWindow
3242
+ modelContextWindow: options.modelContextWindow,
3243
+ endpointLimitTokens: options.endpointLimitTokens
3244
+ });
3245
+ const inputBudgetTokens = resolveInputBudgetTokens({
3246
+ contextWindow,
3247
+ reservedOutputTokens: options.reservedOutputTokens
3203
3248
  });
3204
3249
  let anchorResolved = false;
3205
3250
  let afterAnchorIndex = 0;
@@ -3223,7 +3268,8 @@ function buildProviderView(options) {
3223
3268
  let tier1 = null;
3224
3269
  const uncachedActive = options.cacheActive === false && config.aggressivePruneWhenUncached;
3225
3270
  const pruneFraction = uncachedActive ? config.uncachedPruneAtFraction : config.pruneAtFraction;
3226
- if (estimate.tokens > pruneFraction * contextWindow) {
3271
+ const triggerTokens = (currentEstimateTokens) => Math.max(currentEstimateTokens, estimateStructuralTokens(providerMessages), typeof options.measuredInputTokens === "number" ? options.measuredInputTokens : 0);
3272
+ if (triggerTokens(estimate.tokens) > pruneFraction * inputBudgetTokens) {
3227
3273
  tier1 = pruneToolOutputs(providerMessages, {
3228
3274
  preserveRecentMessages: config.preserveRecentMessages,
3229
3275
  minOutputChars: uncachedActive ? config.uncachedPruneMinOutputChars : undefined,
@@ -3238,15 +3284,15 @@ function buildProviderView(options) {
3238
3284
  }
3239
3285
  }
3240
3286
  const compactionCutoffIndex = computeCompactionCutoffIndex(messages, config.preserveRecentMessages, afterAnchorIndex);
3241
- const coveredMessages = messages.slice(afterAnchorIndex, compactionCutoffIndex);
3242
- const overCompactThreshold = estimate.tokens > config.compactAtFraction * contextWindow;
3287
+ const coveredMessages = capCoverageToTokenBudget(messages.slice(afterAnchorIndex, compactionCutoffIndex), config.maxCoverageTokensPerCompaction);
3288
+ const overCompactThreshold = triggerTokens(estimate.tokens) > config.compactAtFraction * inputBudgetTokens;
3243
3289
  let compactionBlockedReason = null;
3244
3290
  if (overCompactThreshold) {
3245
3291
  if (coveredMessages.length === 0) {
3246
3292
  compactionBlockedReason = "not_enough_messages";
3247
3293
  } else if ((options.pendingInteractionCount ?? 0) > 0) {
3248
3294
  compactionBlockedReason = "pending_interactions";
3249
- } else if (hasUnresolvedToolWork(messages)) {
3295
+ } else if (hasUnresolvedToolWork(coveredMessages)) {
3250
3296
  compactionBlockedReason = "unresolved_tool_work";
3251
3297
  }
3252
3298
  }
@@ -3254,6 +3300,7 @@ function buildProviderView(options) {
3254
3300
  providerMessages,
3255
3301
  estimate,
3256
3302
  contextWindow,
3303
+ inputBudgetTokens,
3257
3304
  needsCompaction: overCompactThreshold && compactionBlockedReason === null,
3258
3305
  compactionBlockedReason,
3259
3306
  anchorResolved,
@@ -3265,6 +3312,7 @@ function buildProviderView(options) {
3265
3312
  var init_build_provider_view = __esm(() => {
3266
3313
  init_config3();
3267
3314
  init_estimate();
3315
+ init_fit_to_budget();
3268
3316
  init_heuristic_summary();
3269
3317
  init_message_parts();
3270
3318
  init_tier1_prune();
@@ -3310,6 +3358,7 @@ var init_context = __esm(() => {
3310
3358
  init_config3();
3311
3359
  init_context_state();
3312
3360
  init_estimate();
3361
+ init_fit_to_budget();
3313
3362
  init_heuristic_summary();
3314
3363
  init_message_parts();
3315
3364
  init_normalize_provider_messages();
@@ -3583,7 +3632,7 @@ var init_lightcode_config = __esm(() => {
3583
3632
  context: defaultContextOptimizerConfig,
3584
3633
  maxOutputTokens: 32768,
3585
3634
  maxSteps: 30,
3586
- maxRetries: 5,
3635
+ maxRetries: 3,
3587
3636
  autoContinue: defaultAutoContinueConfig,
3588
3637
  headroom: defaultHeadroomConfig
3589
3638
  };
@@ -4097,6 +4146,10 @@ async function extractToolCalls(text, knownToolNames) {
4097
4146
  while ((match = DEEPSEEK_WRAPPER_RE.exec(text)) !== null) {
4098
4147
  segments.push({ start: match.index, end: match.index + match[0].length, call: null });
4099
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
+ }
4100
4153
  const covered = (idx) => segments.some((s) => idx >= s.start && idx < s.end);
4101
4154
  const orphanIdx = findOrphanMarker(text, covered);
4102
4155
  if (orphanIdx !== -1) {
@@ -4184,7 +4237,7 @@ async function parseBareTrailingJson(text, knownToolNames) {
4184
4237
  return null;
4185
4238
  return { start: idx, payload };
4186
4239
  }
4187
- 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;
4188
4241
  var init_xml_tool_call_parser = __esm(() => {
4189
4242
  init_tool_call_json_repair();
4190
4243
  TOOL_CALL_OPEN_MARKERS = [
@@ -4211,6 +4264,7 @@ var init_xml_tool_call_parser = __esm(() => {
4211
4264
  deepseek: /<\uFF5Ctool\u2581call\u2581begin\uFF5C>\s*(?:function)?\s*(?:<\uFF5Ctool\u2581sep\uFF5C>)?\s*([\w.-]+)\s*\n?([\s\S]*?)<\uFF5Ctool\u2581call\u2581end\uFF5C>/g
4212
4265
  };
4213
4266
  DEEPSEEK_WRAPPER_RE = /<\uFF5Ctool\u2581calls\u2581(?:begin|end)\uFF5C>/g;
4267
+ FUNCTION_CALLS_WRAPPER_RE = /<\/?function_calls\s*>/gi;
4214
4268
  PARAMETER_TAG_RE = /<parameter\s+name=["']?([\w.-]+)["']?\s*>([\s\S]*?)(?:<\/parameter>|(?=<parameter\s)|$)/gi;
4215
4269
  PARAMETER_EQ_RE = /<parameter=([\w.-]+)>([\s\S]*?)(?=<parameter=|<\/function>|$)/gi;
4216
4270
  });
@@ -5099,6 +5153,14 @@ function isProviderBillingOrQuotaError(error) {
5099
5153
  function isContextOverflowMessage(message) {
5100
5154
  return contextOverflowPattern.test(message);
5101
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
+ }
5102
5164
  function isAbortError(error) {
5103
5165
  return error instanceof Error && error.name === "AbortError" || normalizeErrorMessage(getErrorMessage(error)).includes("aborted");
5104
5166
  }
@@ -5139,7 +5201,8 @@ function classifyChatError(error) {
5139
5201
  kind,
5140
5202
  statusCode: error.statusCode,
5141
5203
  retryable,
5142
- message: message2
5204
+ message: message2,
5205
+ contextLimitTokens: kind === "context_overflow" ? extractContextLimitTokens(message2) : undefined
5143
5206
  };
5144
5207
  }
5145
5208
  return {
@@ -5153,7 +5216,12 @@ function classifyChatError(error) {
5153
5216
  return { kind: "aborted", retryable: false, message };
5154
5217
  }
5155
5218
  if (isContextOverflowMessage(message)) {
5156
- return { kind: "context_overflow", retryable: true, message };
5219
+ return {
5220
+ kind: "context_overflow",
5221
+ retryable: true,
5222
+ message,
5223
+ contextLimitTokens: extractContextLimitTokens(message)
5224
+ };
5157
5225
  }
5158
5226
  if (isProviderBillingOrQuotaError(error)) {
5159
5227
  return { kind: "billing", retryable: false, message };
@@ -5219,7 +5287,7 @@ function logChatDisconnectEvent({
5219
5287
  message: getErrorMessage(error)
5220
5288
  });
5221
5289
  }
5222
- var chatFailureCounters, logger2, retryableStatusCodes, contextOverflowPattern;
5290
+ var chatFailureCounters, logger2, retryableStatusCodes, contextOverflowPattern, contextLimitPattern;
5223
5291
  var init_chat_observability = __esm(() => {
5224
5292
  init_src();
5225
5293
  chatFailureCounters = {
@@ -5238,6 +5306,7 @@ var init_chat_observability = __esm(() => {
5238
5306
  logger2 = createLogger("chat");
5239
5307
  retryableStatusCodes = new Set([408, 409, 429, 500, 502, 503, 504]);
5240
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;
5241
5310
  });
5242
5311
 
5243
5312
  // packages/db/generated/prisma/internal/class.ts
@@ -6133,9 +6202,6 @@ var init_resolve_within_workspace = __esm(() => {
6133
6202
  WORKSPACE = defaultWorkspaceContext.root;
6134
6203
  });
6135
6204
 
6136
- // packages/ai/src/common/output-utils.ts
6137
- var init_output_utils = () => {};
6138
-
6139
6205
  // packages/ai/src/bash/runtime.ts
6140
6206
  import { exec } from "child_process";
6141
6207
  import { promisify } from "util";
@@ -6171,11 +6237,15 @@ var init_runtime5 = __esm(() => {
6171
6237
  import { createTwoFilesPatch } from "diff";
6172
6238
  var init_unified_diff = () => {};
6173
6239
 
6240
+ // packages/ai/src/edit-file/match.ts
6241
+ var init_match = () => {};
6242
+
6174
6243
  // packages/ai/src/edit-file/runtime.ts
6175
6244
  var init_runtime6 = __esm(() => {
6176
6245
  init_runtime5();
6177
6246
  init_resolve_within_workspace();
6178
6247
  init_unified_diff();
6248
+ init_match();
6179
6249
  init_schema2();
6180
6250
  });
6181
6251
 
@@ -6722,6 +6792,24 @@ var init_overflow_recovery = __esm(() => {
6722
6792
  overflowRecoveryRounds = new Map;
6723
6793
  });
6724
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
+
6725
6813
  // apps/server/src/lib/sse-heartbeat.ts
6726
6814
  function endsWithEventBoundary(chunk) {
6727
6815
  const length = chunk.length;
@@ -6947,6 +7035,16 @@ function getAnthropicProviderOptions(modelId) {
6947
7035
  }
6948
7036
  };
6949
7037
  }
7038
+ function getOpenRouterProviderOptions() {
7039
+ return {
7040
+ openrouter: {
7041
+ provider: {
7042
+ allow_fallbacks: true,
7043
+ require_parameters: true
7044
+ }
7045
+ }
7046
+ };
7047
+ }
6950
7048
  function resolveHeadroomRouting(config5, realBaseUrl) {
6951
7049
  const headroom = config5.headroom;
6952
7050
  if (!headroom?.enabled) {
@@ -7011,12 +7109,8 @@ function resolveOpenAITransportModel({
7011
7109
  if (isOpenRouter) {
7012
7110
  const referer = getEnvValue(env, "OPENROUTER_HTTP_REFERER");
7013
7111
  const title = getEnvValue(env, "OPENROUTER_APP_TITLE", "LIGHTCODE_APP_TITLE");
7014
- if (referer) {
7015
- headers["HTTP-Referer"] = referer;
7016
- }
7017
- if (title) {
7018
- headers["X-OpenRouter-Title"] = title;
7019
- }
7112
+ headers["HTTP-Referer"] = referer ?? "https://github.com/kmugalkhod/lightcode";
7113
+ headers["X-Title"] = title ?? "Lightcode";
7020
7114
  }
7021
7115
  const provider = createOpenAICompatible({
7022
7116
  name: config5.provider,
@@ -7033,6 +7127,7 @@ function resolveOpenAITransportModel({
7033
7127
  headroomRouted,
7034
7128
  model: withXmlToolCallSupport(provider(configuredModel)),
7035
7129
  needsToolCallDiscipline: true,
7130
+ providerOptions: isOpenRouter ? getOpenRouterProviderOptions() : undefined,
7036
7131
  contextWindow: capabilities?.contextLength ?? getModelContextWindow(config5.provider, configuredModel),
7037
7132
  missingCredentialHints: apiKey ? [] : isOpenCodeZen ? ["Set OPENCODE_API_KEY to call OpenCode Zen models."] : isOpenRouter ? ["Set OPENROUTER_API_KEY to call OpenRouter models."] : [
7038
7133
  "Set LIGHTCODE_OPENAI_COMPATIBLE_API_KEY or OPENAI_API_KEY if your OpenAI-compatible endpoint requires authentication."
@@ -7553,6 +7648,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7553
7648
  }
7554
7649
  const contextConfig = lightcodeConfigResult.config.context;
7555
7650
  const cacheActive = resolvedProviderModel.provider === "anthropic";
7651
+ const reservedOutputTokens = resolveMaxOutputTokens(lightcodeConfigResult.config.maxOutputTokens, lightcodeConfigDefaults.maxOutputTokens, resolvedProviderModel.maxCompletionTokens);
7652
+ const endpointLimitTokens = getLearnedContextLimit(chatModelId);
7556
7653
  const pendingInteractionCount = await countPendingChatInteractions(sessionId);
7557
7654
  let contextState = await loadSessionContextStateSafe(sessionId);
7558
7655
  let view = buildProviderView({
@@ -7560,44 +7657,56 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7560
7657
  contextState,
7561
7658
  config: contextConfig,
7562
7659
  modelContextWindow: resolvedProviderModel.contextWindow,
7660
+ reservedOutputTokens,
7661
+ endpointLimitTokens,
7563
7662
  pendingInteractionCount,
7564
7663
  cacheActive
7565
7664
  });
7566
- if (view.needsCompaction && contextConfig.autoCompact) {
7567
- try {
7568
- const estimatedTokensBefore = view.estimate.tokens;
7569
- const compaction = await compactSessionContext({
7570
- sessionId,
7571
- coveredMessages: view.coveredMessages,
7572
- previousState: contextState,
7573
- model: resolvedProviderModel.model,
7574
- modelId: chatModelId,
7575
- cwd,
7576
- config: contextConfig,
7577
- estimatedTokens: estimatedTokensBefore
7578
- });
7579
- contextState = compaction.state;
7580
- view = buildProviderView({
7581
- messages: validatedMessages,
7582
- contextState,
7583
- config: contextConfig,
7584
- modelContextWindow: resolvedProviderModel.contextWindow,
7585
- pendingInteractionCount,
7586
- cacheActive
7587
- });
7588
- logger8.info("context_auto_compacted", {
7589
- sessionId,
7590
- tier: compaction.state.tier,
7591
- usedFallback: compaction.usedFallback,
7592
- coveredMessageCount: compaction.state.coveredMessageCount,
7593
- estimatedTokensBefore,
7594
- estimatedTokensAfter: view.estimate.tokens
7595
- });
7596
- } catch (error) {
7597
- logger8.error("context_compaction_failed", {
7598
- sessionId,
7599
- error: getErrorMessage(error)
7600
- });
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
+ }
7601
7710
  }
7602
7711
  }
7603
7712
  const overflowPreserve = getOverflowPreserveRecentMessages(sessionId);
@@ -7612,6 +7721,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7612
7721
  contextState,
7613
7722
  config: overflowConfig,
7614
7723
  modelContextWindow: resolvedProviderModel.contextWindow,
7724
+ reservedOutputTokens,
7725
+ endpointLimitTokens,
7615
7726
  pendingInteractionCount: 0,
7616
7727
  cacheActive
7617
7728
  });
@@ -7633,6 +7744,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7633
7744
  contextState,
7634
7745
  config: overflowConfig,
7635
7746
  modelContextWindow: resolvedProviderModel.contextWindow,
7747
+ reservedOutputTokens,
7748
+ endpointLimitTokens,
7636
7749
  pendingInteractionCount: 0,
7637
7750
  cacheActive
7638
7751
  });
@@ -7651,7 +7764,24 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7651
7764
  });
7652
7765
  }
7653
7766
  }
7654
- const providerMessages = normalizeProviderMessages(view.providerMessages);
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);
7655
7785
  let overflowNotedThisRequest = false;
7656
7786
  const noteOverflowForSession = () => {
7657
7787
  if (overflowNotedThisRequest) {
@@ -7745,7 +7875,12 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7745
7875
  totalTokens: part.totalUsage.totalTokens
7746
7876
  },
7747
7877
  modelId: chatModelId,
7748
- finishReason: part.finishReason
7878
+ finishReason: part.finishReason,
7879
+ context: {
7880
+ inputTokens: view.estimate.tokens,
7881
+ contextWindow: view.contextWindow,
7882
+ compactedMessages: contextState?.coveredMessageCount ?? 0
7883
+ }
7749
7884
  };
7750
7885
  };
7751
7886
  const environmentContext = await buildWorkspaceContext({ cwd });
@@ -7793,6 +7928,7 @@ ${environmentContext}`,
7793
7928
  onError: (error) => {
7794
7929
  const classified = classifyChatError(error);
7795
7930
  if (classified.kind === "context_overflow") {
7931
+ noteLearnedContextLimit(chatModelId, classified.contextLimitTokens);
7796
7932
  noteOverflowForSession();
7797
7933
  }
7798
7934
  incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), { sessionId, statusCode: classified.statusCode });
@@ -7810,6 +7946,7 @@ ${environmentContext}`,
7810
7946
  const mapStreamError = (error) => {
7811
7947
  const classified = classifyChatError(error);
7812
7948
  if (classified.kind === "context_overflow") {
7949
+ noteLearnedContextLimit(chatModelId, classified.contextLimitTokens);
7813
7950
  noteOverflowForSession();
7814
7951
  }
7815
7952
  incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), {
@@ -7864,6 +8001,7 @@ ${environmentContext}`,
7864
8001
  } catch (error) {
7865
8002
  const classified = classifyChatError(error);
7866
8003
  if (classified.kind === "context_overflow") {
8004
+ noteLearnedContextLimit(chatModelId, classified.contextLimitTokens);
7867
8005
  noteContextOverflow(sessionId);
7868
8006
  }
7869
8007
  incrementChatFailureCounter(chatFailureClassForErrorKind(classified.kind), {
@@ -7894,7 +8032,7 @@ ${environmentContext}`,
7894
8032
  }, 500);
7895
8033
  }
7896
8034
  }
7897
- var logger8, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, fastChatRecentMessageCount = 6;
8035
+ var logger8, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, fastChatRecentMessageCount = 6, MAX_COMPACTION_ROUNDS = 3;
7898
8036
  var init_chat_stream = __esm(() => {
7899
8037
  init_src2();
7900
8038
  init_src();
@@ -7903,6 +8041,7 @@ var init_chat_stream = __esm(() => {
7903
8041
  init_chat_store();
7904
8042
  init_context_compaction();
7905
8043
  init_overflow_recovery();
8044
+ init_learned_context_limit();
7906
8045
  init_sse_heartbeat();
7907
8046
  init_context_state_store();
7908
8047
  init_session_auto_title();