@kmugalkhod/lightcode 0.2.0 → 0.4.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 +261 -80
  2. package/package.json +1 -1
  3. package/server.js +276 -40
package/server.js CHANGED
@@ -147,7 +147,7 @@ var init_src = __esm(() => {
147
147
  });
148
148
 
149
149
  // packages/ai/src/constants.ts
150
- var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24;
150
+ var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, DEFAULT_TOOL_TEXT_OUTPUT_CHARS = 6000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24;
151
151
 
152
152
  // packages/ai/src/coding-agent-modes.ts
153
153
  import { z } from "zod";
@@ -724,13 +724,13 @@ var init_base_schemas = __esm(() => {
724
724
 
725
725
  // packages/ai/src/bash/schema.ts
726
726
  import { z as z5 } from "zod";
727
- var bashDescription = "Run a shell command from workspace root with a timeout. Commands are classified before execution and may require approval.", bashInputSchema, bashProviderInputSchema, bashOutputSchema;
727
+ var bashDescription = "Run a shell command from workspace root with a timeout. Bound noisy output at the source (head/tail/grep/wc, or write to a file then read a range) rather than dumping everything \u2014 output is truncated head+tail and wastes tokens. Commands are classified before execution and may require approval.", bashInputSchema, bashProviderInputSchema, bashOutputSchema;
728
728
  var init_schema = __esm(() => {
729
729
  init_base_schemas();
730
730
  bashInputSchema = z5.object({
731
731
  command: z5.string().min(1).max(4000),
732
732
  timeoutMs: integerRangeSchema(1000, 120000, "timeoutMs").optional().default(30000),
733
- maxOutputChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxOutputChars").optional().default(MAX_TOOL_TEXT_OUTPUT_CHARS)
733
+ maxOutputChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxOutputChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
734
734
  });
735
735
  bashProviderInputSchema = z5.object({
736
736
  command: z5.string().min(1).max(4000)
@@ -840,7 +840,7 @@ var init_schema5 = __esm(() => {
840
840
  path: boundedPathSchema.optional(),
841
841
  staged: z9.boolean().optional().default(false),
842
842
  statOnly: z9.boolean().optional().default(false),
843
- maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(MAX_TOOL_TEXT_OUTPUT_CHARS)
843
+ maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
844
844
  });
845
845
  gitDiffProviderInputSchema = z9.object({
846
846
  path: boundedPathSchema.optional(),
@@ -855,7 +855,7 @@ var init_schema5 = __esm(() => {
855
855
  gitShowInputSchema = z9.object({
856
856
  revision: z9.string().min(1).max(240),
857
857
  path: boundedPathSchema.optional(),
858
- maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(MAX_TOOL_TEXT_OUTPUT_CHARS)
858
+ maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
859
859
  });
860
860
  gitShowProviderInputSchema = z9.object({
861
861
  revision: z9.string().min(1).max(240),
@@ -952,7 +952,7 @@ var init_schema6 = __esm(() => {
952
952
 
953
953
  // packages/ai/src/read-file/schema.ts
954
954
  import { z as z11 } from "zod";
955
- var readFileDescription = "Read file content from the working directory with optional line range limits.", readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
955
+ var readFileDescription = "Read file content from the working directory. Prefer a startLine/endLine range to read only the region you need instead of the whole file \u2014 large files are truncated (head+tail) and waste tokens. Raise maxChars only when you truly need more.", readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
956
956
  var init_schema7 = __esm(() => {
957
957
  init_base_schemas();
958
958
  readFileInputSchema = z11.object({
@@ -1206,12 +1206,12 @@ var init_schema13 = __esm(() => {
1206
1206
 
1207
1207
  // packages/ai/src/web-fetch/schema.ts
1208
1208
  import { z as z19 } from "zod";
1209
- var webFetchDescription = "Fetch a URL and return normalized, bounded text with source metadata. Requires network permission.", webFetchInputSchema, webFetchProviderInputSchema, webFetchErrorSchema, webFetchOutputSchema;
1209
+ var webFetchDescription = "Fetch a URL and return normalized, bounded text with source metadata. Keep maxChars modest and request more only if the answer wasn't in the returned text. Requires network permission.", webFetchInputSchema, webFetchProviderInputSchema, webFetchErrorSchema, webFetchOutputSchema;
1210
1210
  var init_schema14 = __esm(() => {
1211
1211
  init_base_schemas();
1212
1212
  webFetchInputSchema = z19.object({
1213
1213
  url: z19.string().url().max(4096),
1214
- maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(MAX_TOOL_TEXT_OUTPUT_CHARS),
1214
+ maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS),
1215
1215
  timeoutMs: integerRangeSchema(1000, 30000, "timeoutMs").optional().default(1e4)
1216
1216
  });
1217
1217
  webFetchProviderInputSchema = z19.object({
@@ -1899,11 +1899,11 @@ ${toolCallingDisciplineAddendum}` : "");
1899
1899
  var cwdPromptPlaceholder = "{cwd}", defaultCodingAgentSystemPrompt, toolCallingDisciplineAddendum;
1900
1900
  var init_coding_agent_prompt = __esm(() => {
1901
1901
  init_coding_agent_modes();
1902
- defaultCodingAgentSystemPrompt = "You are a basic coding agent. Use tools for filesystem and codebase tasks instead of guessing. " + "Respect the user's intent, explain changes clearly, and prefer incremental, auditable actions. " + "You can only interact with files under this working directory: {cwd}. " + "Use glob_search for path discovery, grep for text search, structured git tools for repository inspection, and bash only when dedicated tools are not enough. " + "Use todo_write to keep multi-step implementation work visible and current. " + "For risky or uncertain operations, inspect context first and be explicit about assumptions. " + "While working, emit brief progress notes in natural language before major tool actions and after important findings. " + `Keep them short, human, and concrete. Vary wording naturally and avoid repetitive templates or rigid labels.
1902
+ defaultCodingAgentSystemPrompt = "You are a coding agent. Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under this working directory: {cwd}. " + "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
1903
 
1904
- ` + "You are an agent \u2014 keep working until the user's request is fully resolved before ending your turn. " + "Never stop with a partial answer. If you say you will do something, do it in the same turn by calling a tool. " + "Use todo_write to track multi-step work and do not finish while any todo is pending or in_progress. " + `Only end your turn when the task is complete or you are blocked on input that only the user can provide.
1904
+ ` + `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
1905
 
1906
- ` + "Long-running processes: never run dev servers, watchers, or other non-exiting commands as plain foreground bash \u2014 " + "the bash tool enforces a timeout and the command will be killed. Start them detached with output redirected to a " + 'log file (e.g. on Windows: start /B cmd /c "npm run dev > dev.log 2>&1"; on Unix: nohup npm run dev > dev.log 2>&1 &), ' + "wait briefly, then verify by reading the log file or with a bounded probe like curl --max-time 5. " + "Dev servers do not always get their default port \u2014 read the actual port from the startup log instead of assuming it. " + "Never kill processes or free ports you did not open yourself; other tools (including this agent's own backend) may be using them.";
1906
+ ` + "Long-running processes (dev servers, watchers): never run them as plain foreground bash \u2014 the bash tool enforces a timeout and will kill them. Start them detached with output redirected to a log " + '(Windows: start /B cmd /c "npm run dev > dev.log 2>&1"; Unix: nohup npm run dev > dev.log 2>&1 &), then read the log to confirm startup and the actual port (do not assume the default). ' + "Never kill processes or free ports you did not open yourself.";
1907
1907
  toolCallingDisciplineAddendum = "Invoke tools only through the function-calling interface. " + "Never write tool calls as XML or plain text such as <tool_call>, <function_call>, <invoke>, or bare JSON in your message. " + "Tool arguments must be valid JSON matching the tool's schema. " + "Call one tool at a time and wait for its result before deciding the next step.";
1908
1908
  });
1909
1909
 
@@ -2674,6 +2674,10 @@ var init_config3 = __esm(() => {
2674
2674
  preserveRecentMessages: z26.number().int().min(2).max(100).optional(),
2675
2675
  contextWindowOverride: z26.number().int().min(8000).max(1e7).optional(),
2676
2676
  summaryMaxChars: z26.number().int().min(200).max(50000).optional(),
2677
+ aggressivePruneWhenUncached: z26.boolean().optional(),
2678
+ uncachedPruneAtFraction: z26.number().min(0.2).max(0.9).optional(),
2679
+ uncachedPruneMinOutputChars: z26.number().int().min(200).max(12000).optional(),
2680
+ uncachedQuantizeUserTurns: z26.number().int().min(1).max(20).optional(),
2677
2681
  maxInputTokens: z26.number().int().min(1).max(1e7).optional()
2678
2682
  }).strict();
2679
2683
  resolvedContextOptimizerConfigSchema = z26.object({
@@ -2682,15 +2686,23 @@ var init_config3 = __esm(() => {
2682
2686
  pruneAtFraction: z26.number().min(0.2).max(0.9),
2683
2687
  preserveRecentMessages: z26.number().int().min(2).max(100),
2684
2688
  contextWindowOverride: z26.number().int().min(8000).max(1e7).nullable(),
2685
- summaryMaxChars: z26.number().int().min(200).max(50000)
2689
+ summaryMaxChars: z26.number().int().min(200).max(50000),
2690
+ aggressivePruneWhenUncached: z26.boolean(),
2691
+ uncachedPruneAtFraction: z26.number().min(0.2).max(0.9),
2692
+ uncachedPruneMinOutputChars: z26.number().int().min(200).max(12000),
2693
+ uncachedQuantizeUserTurns: z26.number().int().min(1).max(20)
2686
2694
  });
2687
2695
  defaultContextOptimizerConfig = {
2688
2696
  autoCompact: true,
2689
- compactAtFraction: 0.8,
2697
+ compactAtFraction: 0.7,
2690
2698
  pruneAtFraction: 0.6,
2691
2699
  preserveRecentMessages: 6,
2692
2700
  contextWindowOverride: null,
2693
- summaryMaxChars: 4000
2701
+ summaryMaxChars: 4000,
2702
+ aggressivePruneWhenUncached: true,
2703
+ uncachedPruneAtFraction: 0.45,
2704
+ uncachedPruneMinOutputChars: 600,
2705
+ uncachedQuantizeUserTurns: 1
2694
2706
  };
2695
2707
  });
2696
2708
 
@@ -3156,9 +3168,13 @@ function buildProviderView(options) {
3156
3168
  }
3157
3169
  let estimate = estimateContextTokens(providerMessages);
3158
3170
  let tier1 = null;
3159
- if (estimate.tokens > config.pruneAtFraction * contextWindow) {
3171
+ const uncachedActive = options.cacheActive === false && config.aggressivePruneWhenUncached;
3172
+ const pruneFraction = uncachedActive ? config.uncachedPruneAtFraction : config.pruneAtFraction;
3173
+ if (estimate.tokens > pruneFraction * contextWindow) {
3160
3174
  tier1 = pruneToolOutputs(providerMessages, {
3161
- preserveRecentMessages: config.preserveRecentMessages
3175
+ preserveRecentMessages: config.preserveRecentMessages,
3176
+ minOutputChars: uncachedActive ? config.uncachedPruneMinOutputChars : undefined,
3177
+ quantizeUserTurns: uncachedActive ? config.uncachedQuantizeUserTurns : undefined
3162
3178
  });
3163
3179
  if (tier1.savedChars > 0) {
3164
3180
  providerMessages = tier1.messages;
@@ -3201,6 +3217,40 @@ var init_build_provider_view = __esm(() => {
3201
3217
  init_tier1_prune();
3202
3218
  });
3203
3219
 
3220
+ // packages/ai/src/context/normalize-provider-messages.ts
3221
+ import { isToolUIPart } from "ai";
3222
+ function resolveDanglingToolParts(messages, errorText = interruptedToolErrorText) {
3223
+ return messages.map((message) => {
3224
+ if (message.role !== "assistant") {
3225
+ return message;
3226
+ }
3227
+ let changed = false;
3228
+ const parts = message.parts.map((part) => {
3229
+ if (!isToolUIPart(part) || terminalToolStates.has(part.state)) {
3230
+ return part;
3231
+ }
3232
+ changed = true;
3233
+ return {
3234
+ ...part,
3235
+ state: "output-error",
3236
+ errorText
3237
+ };
3238
+ });
3239
+ return changed ? { ...message, parts } : message;
3240
+ });
3241
+ }
3242
+ function normalizeProviderMessages(messages) {
3243
+ return resolveDanglingToolParts(messages);
3244
+ }
3245
+ var terminalToolStates, interruptedToolErrorText = "Tool call was interrupted by a connection retry. Re-issue the tool call if it is still needed.";
3246
+ var init_normalize_provider_messages = __esm(() => {
3247
+ terminalToolStates = new Set([
3248
+ "output-available",
3249
+ "output-error",
3250
+ "output-denied"
3251
+ ]);
3252
+ });
3253
+
3204
3254
  // packages/ai/src/context/index.ts
3205
3255
  var init_context = __esm(() => {
3206
3256
  init_build_provider_view();
@@ -3209,6 +3259,7 @@ var init_context = __esm(() => {
3209
3259
  init_estimate();
3210
3260
  init_heuristic_summary();
3211
3261
  init_message_parts();
3262
+ init_normalize_provider_messages();
3212
3263
  init_tier1_prune();
3213
3264
  });
3214
3265
 
@@ -3304,9 +3355,19 @@ function readEnvConfig(env) {
3304
3355
  contextWindowOverride: parseNumberEnv(env.LIGHTCODE_CONTEXT_WINDOW, "LIGHTCODE_CONTEXT_WINDOW"),
3305
3356
  maxInputTokens: parseNumberEnv(env.LIGHTCODE_CONTEXT_MAX_INPUT_TOKENS, "LIGHTCODE_CONTEXT_MAX_INPUT_TOKENS"),
3306
3357
  preserveRecentMessages: parseNumberEnv(env.LIGHTCODE_CONTEXT_PRESERVE_RECENT_MESSAGES, "LIGHTCODE_CONTEXT_PRESERVE_RECENT_MESSAGES"),
3307
- summaryMaxChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS, "LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS")
3358
+ summaryMaxChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS, "LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS"),
3359
+ aggressivePruneWhenUncached: parseBooleanEnv(env.LIGHTCODE_CONTEXT_AGGRESSIVE_PRUNE_UNCACHED, "LIGHTCODE_CONTEXT_AGGRESSIVE_PRUNE_UNCACHED"),
3360
+ uncachedPruneAtFraction: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_PRUNE_AT_FRACTION, "LIGHTCODE_CONTEXT_UNCACHED_PRUNE_AT_FRACTION"),
3361
+ uncachedPruneMinOutputChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_PRUNE_MIN_OUTPUT_CHARS, "LIGHTCODE_CONTEXT_UNCACHED_PRUNE_MIN_OUTPUT_CHARS"),
3362
+ uncachedQuantizeUserTurns: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_QUANTIZE_USER_TURNS, "LIGHTCODE_CONTEXT_UNCACHED_QUANTIZE_USER_TURNS")
3308
3363
  };
3309
3364
  const compactContextConfig = Object.fromEntries(Object.entries(contextConfig).filter(([, value]) => value !== undefined));
3365
+ const headroomConfig = {
3366
+ enabled: parseBooleanEnv(env.LIGHTCODE_HEADROOM_ENABLED, "LIGHTCODE_HEADROOM_ENABLED"),
3367
+ proxyUrl: env.LIGHTCODE_HEADROOM_PROXY_URL?.trim() || undefined,
3368
+ failOpen: parseBooleanEnv(env.LIGHTCODE_HEADROOM_FAIL_OPEN, "LIGHTCODE_HEADROOM_FAIL_OPEN")
3369
+ };
3370
+ const compactHeadroomConfig = Object.fromEntries(Object.entries(headroomConfig).filter(([, value]) => value !== undefined));
3310
3371
  const rawConfig = {
3311
3372
  provider: env.LIGHTCODE_PROVIDER,
3312
3373
  model: env.LIGHTCODE_CHAT_MODEL,
@@ -3316,7 +3377,8 @@ function readEnvConfig(env) {
3316
3377
  maxOutputTokens: parseNumberEnv(env.LIGHTCODE_MAX_OUTPUT_TOKENS, "LIGHTCODE_MAX_OUTPUT_TOKENS"),
3317
3378
  maxSteps: parseNumberEnv(env.LIGHTCODE_MAX_STEPS, "LIGHTCODE_MAX_STEPS"),
3318
3379
  maxRetries: parseNumberEnv(env.LIGHTCODE_MAX_RETRIES, "LIGHTCODE_MAX_RETRIES"),
3319
- context: Object.keys(compactContextConfig).length > 0 ? compactContextConfig : undefined
3380
+ context: Object.keys(compactContextConfig).length > 0 ? compactContextConfig : undefined,
3381
+ headroom: Object.keys(compactHeadroomConfig).length > 0 ? compactHeadroomConfig : undefined
3320
3382
  };
3321
3383
  const compactConfig = Object.fromEntries(Object.entries(rawConfig).filter(([, value]) => value !== undefined));
3322
3384
  const parsedConfig = lightcodeConfigSchema.safeParse(compactConfig);
@@ -3340,6 +3402,10 @@ function mergeConfig(...configs) {
3340
3402
  ...mergedConfig.autoContinue,
3341
3403
  ...config2.autoContinue
3342
3404
  } : mergedConfig.autoContinue,
3405
+ headroom: config2.headroom ? {
3406
+ ...mergedConfig.headroom,
3407
+ ...config2.headroom
3408
+ } : mergedConfig.headroom,
3343
3409
  allowedTools: config2.allowedTools ?? mergedConfig.allowedTools
3344
3410
  }), {});
3345
3411
  }
@@ -3380,6 +3446,10 @@ function loadLightcodeConfig({
3380
3446
  autoContinue: {
3381
3447
  ...defaultAutoContinueConfig,
3382
3448
  ...mergedConfig.autoContinue
3449
+ },
3450
+ headroom: {
3451
+ ...defaultHeadroomConfig,
3452
+ ...mergedConfig.headroom
3383
3453
  }
3384
3454
  };
3385
3455
  return lightcodeConfigLoadResultSchema.parse({
@@ -3387,7 +3457,7 @@ function loadLightcodeConfig({
3387
3457
  loadedFiles: [userConfig.file, projectConfig.file]
3388
3458
  });
3389
3459
  }
3390
- var lightcodeProviderSchema, autoContinueConfigSchema, resolvedAutoContinueConfigSchema, defaultAutoContinueConfig, lightcodeConfigSchema, lightcodeConfigDefaults, loadedConfigFileSchema, lightcodeResolvedConfigSchema, lightcodeConfigLoadResultSchema, lightcodeConfigStatusSchema, LightcodeConfigError;
3460
+ var lightcodeProviderSchema, autoContinueConfigSchema, resolvedAutoContinueConfigSchema, defaultAutoContinueConfig, headroomConfigSchema, resolvedHeadroomConfigSchema, defaultHeadroomConfig, lightcodeConfigSchema, lightcodeConfigDefaults, loadedConfigFileSchema, lightcodeResolvedConfigSchema, lightcodeConfigLoadResultSchema, lightcodeConfigStatusSchema, LightcodeConfigError;
3391
3461
  var init_lightcode_config = __esm(() => {
3392
3462
  init_coding_agent_modes();
3393
3463
  init_coding_agent_modes();
@@ -3419,6 +3489,24 @@ var init_lightcode_config = __esm(() => {
3419
3489
  maxErrorRetries: 5,
3420
3490
  stallTimeoutSeconds: 180
3421
3491
  };
3492
+ headroomConfigSchema = z28.object({
3493
+ enabled: z28.boolean().optional(),
3494
+ proxyUrl: z28.string().min(1).max(4096).optional(),
3495
+ providers: z28.array(lightcodeProviderSchema).optional(),
3496
+ failOpen: z28.boolean().optional()
3497
+ }).strict();
3498
+ resolvedHeadroomConfigSchema = z28.object({
3499
+ enabled: z28.boolean(),
3500
+ proxyUrl: z28.string().min(1).max(4096),
3501
+ providers: z28.array(lightcodeProviderSchema).nullable(),
3502
+ failOpen: z28.boolean()
3503
+ });
3504
+ defaultHeadroomConfig = {
3505
+ enabled: false,
3506
+ proxyUrl: "http://127.0.0.1:8787",
3507
+ providers: null,
3508
+ failOpen: true
3509
+ };
3422
3510
  lightcodeConfigSchema = z28.object({
3423
3511
  provider: lightcodeProviderSchema.optional(),
3424
3512
  model: z28.string().min(1).max(240).optional(),
@@ -3430,10 +3518,11 @@ var init_lightcode_config = __esm(() => {
3430
3518
  sandbox: sandboxConfigSchema.optional(),
3431
3519
  mcp: mcpConfigSchema.optional(),
3432
3520
  context: contextOptimizerConfigSchema.optional(),
3433
- maxOutputTokens: z28.number().int().min(1).max(200000).optional(),
3521
+ maxOutputTokens: z28.number().int().min(1).max(1e6).optional(),
3434
3522
  maxSteps: z28.number().int().min(1).max(100).optional(),
3435
3523
  maxRetries: z28.number().int().min(0).max(10).optional(),
3436
- autoContinue: autoContinueConfigSchema.optional()
3524
+ autoContinue: autoContinueConfigSchema.optional(),
3525
+ headroom: headroomConfigSchema.optional()
3437
3526
  }).strict();
3438
3527
  lightcodeConfigDefaults = {
3439
3528
  provider: "anthropic",
@@ -3442,7 +3531,8 @@ var init_lightcode_config = __esm(() => {
3442
3531
  maxOutputTokens: 32768,
3443
3532
  maxSteps: 30,
3444
3533
  maxRetries: 5,
3445
- autoContinue: defaultAutoContinueConfig
3534
+ autoContinue: defaultAutoContinueConfig,
3535
+ headroom: defaultHeadroomConfig
3446
3536
  };
3447
3537
  loadedConfigFileSchema = z28.object({
3448
3538
  scope: z28.enum(["user", "project"]),
@@ -3454,10 +3544,11 @@ var init_lightcode_config = __esm(() => {
3454
3544
  provider: lightcodeProviderSchema,
3455
3545
  defaultMode: codingAgentModeSchema,
3456
3546
  context: resolvedContextOptimizerConfigSchema,
3457
- maxOutputTokens: z28.number().int().min(1).max(200000),
3547
+ maxOutputTokens: z28.number().int().min(1).max(1e6),
3458
3548
  maxSteps: z28.number().int().min(1).max(100),
3459
3549
  maxRetries: z28.number().int().min(0).max(10),
3460
- autoContinue: resolvedAutoContinueConfigSchema
3550
+ autoContinue: resolvedAutoContinueConfigSchema,
3551
+ headroom: resolvedHeadroomConfigSchema.optional()
3461
3552
  });
3462
3553
  lightcodeConfigLoadResultSchema = z28.object({
3463
3554
  config: lightcodeResolvedConfigSchema,
@@ -3471,10 +3562,15 @@ var init_lightcode_config = __esm(() => {
3471
3562
  baseUrl: z28.string().nullable(),
3472
3563
  defaultMode: codingAgentModeSchema,
3473
3564
  permissionMode: permissionModeSchema.nullable(),
3474
- maxOutputTokens: z28.number().int().min(1).max(200000),
3565
+ maxOutputTokens: z28.number().int().min(1).max(1e6),
3475
3566
  maxSteps: z28.number().int().min(1).max(100),
3476
3567
  autoContinue: resolvedAutoContinueConfigSchema,
3477
3568
  contextWindow: z28.number().int().positive(),
3569
+ headroom: z28.object({
3570
+ enabled: z28.boolean(),
3571
+ proxyUrl: z28.string().nullable(),
3572
+ routed: z28.boolean()
3573
+ }),
3478
3574
  missingCredentialHints: z28.array(z28.string())
3479
3575
  });
3480
3576
  LightcodeConfigError = class LightcodeConfigError extends Error {
@@ -3714,6 +3810,21 @@ var init_runtime = __esm(() => {
3714
3810
  init_schema13();
3715
3811
  });
3716
3812
 
3813
+ // packages/ai/src/providers/clamp-output-tokens.ts
3814
+ function clampMaxOutputTokens(configured, modelMaxCompletionTokens) {
3815
+ if (typeof modelMaxCompletionTokens === "number" && Number.isFinite(modelMaxCompletionTokens) && modelMaxCompletionTokens > 0) {
3816
+ return Math.min(configured, modelMaxCompletionTokens);
3817
+ }
3818
+ return configured;
3819
+ }
3820
+ function resolveMaxOutputTokens(configured, defaultValue, modelMaxCompletionTokens) {
3821
+ const hasModelCap = typeof modelMaxCompletionTokens === "number" && Number.isFinite(modelMaxCompletionTokens) && modelMaxCompletionTokens > 0;
3822
+ if (configured === defaultValue) {
3823
+ return hasModelCap ? modelMaxCompletionTokens : configured;
3824
+ }
3825
+ return clampMaxOutputTokens(configured, modelMaxCompletionTokens);
3826
+ }
3827
+
3717
3828
  // packages/ai/src/providers/model-metadata.ts
3718
3829
  function getModelContextWindow(provider, modelId) {
3719
3830
  if (provider === "anthropic") {
@@ -6551,6 +6662,9 @@ function summarizeRawModel(raw2) {
6551
6662
  }
6552
6663
  const name = Reflect.get(raw2, "name");
6553
6664
  const contextLength = Reflect.get(raw2, "context_length");
6665
+ const topProvider = Reflect.get(raw2, "top_provider");
6666
+ const topProviderMaxCompletion = typeof topProvider === "object" && topProvider !== null ? Reflect.get(topProvider, "max_completion_tokens") : undefined;
6667
+ const rawMaxCompletion = typeof topProviderMaxCompletion === "number" ? topProviderMaxCompletion : Reflect.get(raw2, "max_completion_tokens");
6554
6668
  const supportedParameters = Reflect.get(raw2, "supported_parameters");
6555
6669
  const parameterList = Array.isArray(supportedParameters) ? supportedParameters.filter((entry) => typeof entry === "string") : [];
6556
6670
  const pricingRaw = Reflect.get(raw2, "pricing");
@@ -6562,6 +6676,7 @@ function summarizeRawModel(raw2) {
6562
6676
  id,
6563
6677
  name: typeof name === "string" && name.length > 0 ? name : id,
6564
6678
  contextLength: typeof contextLength === "number" && contextLength > 0 ? Math.round(contextLength) : null,
6679
+ maxCompletionTokens: typeof rawMaxCompletion === "number" && rawMaxCompletion > 0 ? Math.round(rawMaxCompletion) : null,
6565
6680
  supportsTools: parameterList.includes("tools"),
6566
6681
  supportsReasoning: parameterList.includes("reasoning"),
6567
6682
  pricing
@@ -6611,7 +6726,8 @@ async function getOpenRouterModelCapabilities(modelId) {
6611
6726
  }
6612
6727
  return {
6613
6728
  supportsNativeTools: model.supportsTools,
6614
- contextLength: model.contextLength ?? undefined
6729
+ contextLength: model.contextLength ?? undefined,
6730
+ maxCompletionTokens: model.maxCompletionTokens
6615
6731
  };
6616
6732
  } catch {
6617
6733
  return;
@@ -6626,6 +6742,7 @@ var init_openrouter_models = __esm(() => {
6626
6742
  id: z34.string(),
6627
6743
  name: z34.string(),
6628
6744
  contextLength: z34.number().int().positive().nullable(),
6745
+ maxCompletionTokens: z34.number().int().positive().nullable().default(null),
6629
6746
  supportsTools: z34.boolean(),
6630
6747
  supportsReasoning: z34.boolean(),
6631
6748
  pricing: z34.object({
@@ -6671,6 +6788,16 @@ function getAnthropicProviderOptions(modelId) {
6671
6788
  }
6672
6789
  };
6673
6790
  }
6791
+ function resolveHeadroomRouting(config5, realBaseUrl) {
6792
+ const headroom = config5.headroom;
6793
+ if (!headroom?.enabled) {
6794
+ return { effectiveBaseUrl: realBaseUrl, headroomRouted: false };
6795
+ }
6796
+ if (headroom.providers && !headroom.providers.includes(config5.provider)) {
6797
+ return { effectiveBaseUrl: realBaseUrl, headroomRouted: false };
6798
+ }
6799
+ return { effectiveBaseUrl: headroom.proxyUrl, headroomRouted: true };
6800
+ }
6674
6801
  function resolveAnthropicModel({
6675
6802
  config: config5,
6676
6803
  env,
@@ -6681,19 +6808,22 @@ function resolveAnthropicModel({
6681
6808
  const apiKey = getEnvValue(env, "ANTHROPIC_API_KEY") ?? storedCredentials.anthropicApiKey;
6682
6809
  const authToken = getEnvValue(env, "ANTHROPIC_AUTH_TOKEN");
6683
6810
  const baseUrl = config5.baseUrl ?? getEnvValue(env, "ANTHROPIC_BASE_URL") ?? null;
6811
+ const { effectiveBaseUrl, headroomRouted } = resolveHeadroomRouting(config5, baseUrl);
6684
6812
  const missingCredentialHints = apiKey || authToken ? [] : [
6685
6813
  "Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN to call Anthropic models."
6686
6814
  ];
6687
6815
  const provider = createAnthropic({
6688
6816
  apiKey,
6689
6817
  authToken,
6690
- baseURL: baseUrl ?? undefined
6818
+ baseURL: effectiveBaseUrl ?? undefined
6691
6819
  });
6692
6820
  return {
6693
6821
  provider: "anthropic",
6694
6822
  configuredModel,
6695
6823
  resolvedModelId: modelId,
6696
6824
  baseUrl,
6825
+ effectiveBaseUrl,
6826
+ headroomRouted,
6697
6827
  model: provider(modelId),
6698
6828
  providerOptions: getAnthropicProviderOptions(modelId),
6699
6829
  contextWindow: getModelContextWindow("anthropic", modelId),
@@ -6716,6 +6846,7 @@ function resolveOpenAITransportModel({
6716
6846
  if (!baseUrl) {
6717
6847
  throw new Error('OpenAI-compatible provider requires "baseUrl" in config or LIGHTCODE_OPENAI_COMPATIBLE_BASE_URL.');
6718
6848
  }
6849
+ const { effectiveBaseUrl, headroomRouted } = resolveHeadroomRouting(config5, baseUrl);
6719
6850
  const apiKey = isOpenCodeZen ? getEnvValue(env, "OPENCODE_API_KEY", "LIGHTCODE_OPENAI_COMPATIBLE_API_KEY", "OPENAI_API_KEY") ?? storedCredentials.opencodeApiKey : isOpenRouter ? getEnvValue(env, "OPENROUTER_API_KEY", "LIGHTCODE_OPENAI_COMPATIBLE_API_KEY", "OPENAI_API_KEY") ?? storedCredentials.openrouterApiKey : getEnvValue(env, "LIGHTCODE_OPENAI_COMPATIBLE_API_KEY", "OPENAI_API_KEY") ?? storedCredentials.openaiCompatibleApiKey;
6720
6851
  const headers = {};
6721
6852
  if (isOpenRouter) {
@@ -6730,7 +6861,7 @@ function resolveOpenAITransportModel({
6730
6861
  }
6731
6862
  const provider = createOpenAICompatible({
6732
6863
  name: config5.provider,
6733
- baseURL: baseUrl,
6864
+ baseURL: effectiveBaseUrl ?? baseUrl,
6734
6865
  apiKey,
6735
6866
  headers
6736
6867
  });
@@ -6739,12 +6870,15 @@ function resolveOpenAITransportModel({
6739
6870
  configuredModel,
6740
6871
  resolvedModelId: configuredModel,
6741
6872
  baseUrl,
6873
+ effectiveBaseUrl,
6874
+ headroomRouted,
6742
6875
  model: withXmlToolCallSupport(provider(configuredModel)),
6743
6876
  needsToolCallDiscipline: true,
6744
6877
  contextWindow: capabilities?.contextLength ?? getModelContextWindow(config5.provider, configuredModel),
6745
6878
  missingCredentialHints: apiKey ? [] : isOpenCodeZen ? ["Set OPENCODE_API_KEY to call OpenCode Zen models."] : isOpenRouter ? ["Set OPENROUTER_API_KEY to call OpenRouter models."] : [
6746
6879
  "Set LIGHTCODE_OPENAI_COMPATIBLE_API_KEY or OPENAI_API_KEY if your OpenAI-compatible endpoint requires authentication."
6747
- ]
6880
+ ],
6881
+ maxCompletionTokens: capabilities?.maxCompletionTokens ?? null
6748
6882
  };
6749
6883
  }
6750
6884
  function resolveConfiguredProviderModel({
@@ -6776,13 +6910,18 @@ function createConfigStatus({
6776
6910
  baseUrl: resolvedProviderModel.baseUrl,
6777
6911
  defaultMode: config5.defaultMode,
6778
6912
  permissionMode: config5.permissionMode ?? null,
6779
- maxOutputTokens: config5.maxOutputTokens,
6913
+ maxOutputTokens: resolveMaxOutputTokens(config5.maxOutputTokens, lightcodeConfigDefaults.maxOutputTokens, resolvedProviderModel.maxCompletionTokens),
6780
6914
  maxSteps: config5.maxSteps,
6781
6915
  autoContinue: config5.autoContinue,
6782
6916
  contextWindow: resolveContextWindowTokens({
6783
6917
  config: config5.context,
6784
6918
  modelContextWindow: resolvedProviderModel.contextWindow
6785
6919
  }),
6920
+ headroom: {
6921
+ enabled: config5.headroom?.enabled ?? false,
6922
+ proxyUrl: config5.headroom?.proxyUrl ?? null,
6923
+ routed: resolvedProviderModel.headroomRouted
6924
+ },
6786
6925
  missingCredentialHints: resolvedProviderModel.missingCredentialHints
6787
6926
  });
6788
6927
  }
@@ -6802,6 +6941,7 @@ __export(exports_runtime_config, {
6802
6941
  resolvedProviderModel: () => resolvedProviderModel,
6803
6942
  lightcodeConfigResult: () => lightcodeConfigResult,
6804
6943
  initializeModelCapabilities: () => initializeModelCapabilities,
6944
+ disableHeadroomRouting: () => disableHeadroomRouting,
6805
6945
  configStatus: () => configStatus,
6806
6946
  codingAgent: () => codingAgent,
6807
6947
  chatModelId: () => chatModelId,
@@ -6814,7 +6954,7 @@ function buildCodingAgent(config5, model) {
6814
6954
  providerOptions: model.providerOptions,
6815
6955
  maxSteps: config5.maxSteps,
6816
6956
  maxRetries: config5.maxRetries,
6817
- maxOutputTokens: config5.maxOutputTokens,
6957
+ maxOutputTokens: resolveMaxOutputTokens(config5.maxOutputTokens, lightcodeConfigDefaults.maxOutputTokens, model.maxCompletionTokens),
6818
6958
  includeToolDiscipline: model.needsToolCallDiscipline ?? false
6819
6959
  });
6820
6960
  }
@@ -6859,6 +6999,39 @@ function applyModelSelection({
6859
6999
  });
6860
7000
  return configStatus;
6861
7001
  }
7002
+ function disableHeadroomRouting() {
7003
+ if (!lightcodeConfigResult.config.headroom?.enabled) {
7004
+ return;
7005
+ }
7006
+ const nextConfig = {
7007
+ ...lightcodeConfigResult.config,
7008
+ headroom: {
7009
+ ...lightcodeConfigResult.config.headroom ?? lightcodeConfigDefaults.headroom,
7010
+ enabled: false
7011
+ }
7012
+ };
7013
+ const nextResolved = resolveConfiguredProviderModel({
7014
+ config: nextConfig,
7015
+ env: Bun.env
7016
+ });
7017
+ const nextAgent = buildCodingAgent(nextConfig, nextResolved);
7018
+ lightcodeConfigResult = {
7019
+ ...lightcodeConfigResult,
7020
+ config: nextConfig
7021
+ };
7022
+ resolvedProviderModel = nextResolved;
7023
+ chatModelId = nextResolved.resolvedModelId;
7024
+ codingAgent = nextAgent;
7025
+ configStatus = createConfigStatus({
7026
+ config: nextConfig,
7027
+ loadedFiles: lightcodeConfigResult.loadedFiles,
7028
+ resolvedProviderModel: nextResolved
7029
+ });
7030
+ logger5.info("headroom_routing_disabled", {
7031
+ provider: nextResolved.provider,
7032
+ model: nextResolved.resolvedModelId
7033
+ });
7034
+ }
6862
7035
  async function initializeModelCapabilities() {
6863
7036
  if (lightcodeConfigResult.config.provider !== "openrouter") {
6864
7037
  return;
@@ -7077,6 +7250,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7077
7250
  }, 500);
7078
7251
  }
7079
7252
  const contextConfig = lightcodeConfigResult.config.context;
7253
+ const cacheActive = resolvedProviderModel.provider === "anthropic";
7080
7254
  const pendingInteractionCount = await countPendingChatInteractions(sessionId);
7081
7255
  let contextState = await loadSessionContextStateSafe(sessionId);
7082
7256
  let view = buildProviderView({
@@ -7084,7 +7258,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7084
7258
  contextState,
7085
7259
  config: contextConfig,
7086
7260
  modelContextWindow: resolvedProviderModel.contextWindow,
7087
- pendingInteractionCount
7261
+ pendingInteractionCount,
7262
+ cacheActive
7088
7263
  });
7089
7264
  if (view.needsCompaction && contextConfig.autoCompact) {
7090
7265
  try {
@@ -7105,7 +7280,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7105
7280
  contextState,
7106
7281
  config: contextConfig,
7107
7282
  modelContextWindow: resolvedProviderModel.contextWindow,
7108
- pendingInteractionCount
7283
+ pendingInteractionCount,
7284
+ cacheActive
7109
7285
  });
7110
7286
  logger7.info("context_auto_compacted", {
7111
7287
  sessionId,
@@ -7134,7 +7310,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7134
7310
  contextState,
7135
7311
  config: overflowConfig,
7136
7312
  modelContextWindow: resolvedProviderModel.contextWindow,
7137
- pendingInteractionCount: 0
7313
+ pendingInteractionCount: 0,
7314
+ cacheActive
7138
7315
  });
7139
7316
  if (overflowView.coveredMessages.length > 0) {
7140
7317
  const compaction = await compactSessionContext({
@@ -7154,7 +7331,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7154
7331
  contextState,
7155
7332
  config: overflowConfig,
7156
7333
  modelContextWindow: resolvedProviderModel.contextWindow,
7157
- pendingInteractionCount: 0
7334
+ pendingInteractionCount: 0,
7335
+ cacheActive
7158
7336
  });
7159
7337
  logger7.info("context_overflow_recovery", {
7160
7338
  sessionId,
@@ -7171,7 +7349,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7171
7349
  });
7172
7350
  }
7173
7351
  }
7174
- const providerMessages = view.providerMessages;
7352
+ const providerMessages = normalizeProviderMessages(view.providerMessages);
7175
7353
  let overflowNotedThisRequest = false;
7176
7354
  const noteOverflowForSession = () => {
7177
7355
  if (overflowNotedThisRequest) {
@@ -8228,9 +8406,62 @@ var init_app = __esm(() => {
8228
8406
  })).route("/", rootRoutes).route("/config", configRoutes).route("/diagnostics", diagnosticsRoutes).route("/extensions", extensionRoutes).route("/sessions", sessionRoutes);
8229
8407
  });
8230
8408
 
8409
+ // apps/server/src/lib/headroom-proxy.ts
8410
+ var exports_headroom_proxy = {};
8411
+ __export(exports_headroom_proxy, {
8412
+ ensureHeadroomRouting: () => ensureHeadroomRouting,
8413
+ checkHeadroomHealth: () => checkHeadroomHealth
8414
+ });
8415
+ async function checkHeadroomHealth(proxyUrl) {
8416
+ try {
8417
+ await fetch(new URL("/health", proxyUrl), {
8418
+ signal: AbortSignal.timeout(healthCheckTimeoutMs)
8419
+ });
8420
+ return "healthy";
8421
+ } catch {
8422
+ return "down";
8423
+ }
8424
+ }
8425
+ async function ensureHeadroomRouting() {
8426
+ const headroom = lightcodeConfigResult.config.headroom;
8427
+ if (!headroom?.enabled) {
8428
+ return;
8429
+ }
8430
+ let health;
8431
+ try {
8432
+ health = await checkHeadroomHealth(headroom.proxyUrl);
8433
+ } catch (error) {
8434
+ health = "down";
8435
+ logger9.warn("headroom_health_probe_failed", {
8436
+ proxyUrl: headroom.proxyUrl,
8437
+ error: getErrorMessage(error)
8438
+ });
8439
+ }
8440
+ if (health === "healthy") {
8441
+ logger9.info("headroom_routing_active", { proxyUrl: headroom.proxyUrl });
8442
+ return;
8443
+ }
8444
+ if (headroom.failOpen) {
8445
+ logger9.warn("headroom_unreachable_failing_open", {
8446
+ proxyUrl: headroom.proxyUrl
8447
+ });
8448
+ disableHeadroomRouting();
8449
+ return;
8450
+ }
8451
+ logger9.error("headroom_unreachable_fail_closed", {
8452
+ proxyUrl: headroom.proxyUrl
8453
+ });
8454
+ }
8455
+ var logger9, healthCheckTimeoutMs = 1500;
8456
+ var init_headroom_proxy = __esm(() => {
8457
+ init_src();
8458
+ init_runtime_config();
8459
+ logger9 = createLogger("headroom-proxy");
8460
+ });
8461
+
8231
8462
  // apps/server/src/index.ts
8232
8463
  init_src();
8233
- var logger9 = createLogger("server");
8464
+ var logger10 = createLogger("server");
8234
8465
  var httpIdleTimeoutSeconds = 30;
8235
8466
  var shutdownForceExitMs = 5000;
8236
8467
  function isChatStreamingRoute(request) {
@@ -8243,7 +8474,7 @@ function isChatStreamingRoute(request) {
8243
8474
  async function startServer() {
8244
8475
  const logDirectory = enableFileLogSink(Bun.env);
8245
8476
  if (logDirectory) {
8246
- logger9.info("file_log_sink_enabled", { directory: logDirectory });
8477
+ logger10.info("file_log_sink_enabled", { directory: logDirectory });
8247
8478
  }
8248
8479
  const port = Number(Bun.env.PORT ?? 4983);
8249
8480
  const hostname = Bun.env.LIGHTCODE_HOST ?? "127.0.0.1";
@@ -8264,7 +8495,12 @@ async function startServer() {
8264
8495
  process.exit(1);
8265
8496
  }
8266
8497
  Promise.resolve().then(() => (init_runtime_config(), exports_runtime_config)).then((runtime19) => runtime19.initializeModelCapabilities()).catch((error) => {
8267
- logger9.warn("model_capability_init_failed", {
8498
+ logger10.warn("model_capability_init_failed", {
8499
+ error: getErrorMessage(error)
8500
+ });
8501
+ });
8502
+ Promise.resolve().then(() => (init_headroom_proxy(), exports_headroom_proxy)).then((headroom) => headroom.ensureHeadroomRouting()).catch((error) => {
8503
+ logger10.warn("headroom_init_failed", {
8268
8504
  error: getErrorMessage(error)
8269
8505
  });
8270
8506
  });
@@ -8300,7 +8536,7 @@ async function startServer() {
8300
8536
  return;
8301
8537
  }
8302
8538
  shuttingDown = true;
8303
- logger9.info("server_shutdown", { signal });
8539
+ logger10.info("server_shutdown", { signal });
8304
8540
  const forceExitTimer = setTimeout(() => {
8305
8541
  server.stop(true);
8306
8542
  process.exit(0);