@kmugalkhod/lightcode 0.18.5 → 0.19.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/server.js CHANGED
@@ -635,7 +635,7 @@ var init_web_auth = __esm(() => {
635
635
  });
636
636
 
637
637
  // packages/ai/src/constants.ts
638
- 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, TODO_CONTENT_MAX_CHARS = 200, TODO_ACTIVE_FORM_MAX_CHARS = 80, PROGRESS_NOTE_PRUNE_MIN_CHARS = 200;
638
+ var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, DEFAULT_TOOL_TEXT_OUTPUT_CHARS = 6000, READ_FILE_OUTPUT_CHARS = 24000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24, TODO_CONTENT_MAX_CHARS = 200, TODO_ACTIVE_FORM_MAX_CHARS = 80, PROGRESS_NOTE_PRUNE_MIN_CHARS = 200;
639
639
 
640
640
  // packages/ai/src/coding-agent-modes.ts
641
641
  import { z as z2 } from "zod";
@@ -2002,14 +2002,15 @@ var init_schema8 = __esm(() => {
2002
2002
 
2003
2003
  // packages/ai/src/read-file/schema.ts
2004
2004
  import { z as z19 } from "zod";
2005
- var readFileDescription = "Read file content from the working directory. Each call returns about 6,000 characters (roughly 150 lines); longer content is truncated head+tail. Small files: read them whole. Long files: find the spot with grep first, then read a startLine/endLine range of about 100-150 lines around it.", readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
2005
+ var readFileDescription, readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
2006
2006
  var init_schema9 = __esm(() => {
2007
2007
  init_base_schemas();
2008
+ readFileDescription = "Read file content from the working directory. Returns whole lines, up to about 24,000 characters (roughly 600 lines) per call, so read a file in one call instead of in slices. " + "If the file is longer, the result stops at endLine and nextStartLine says where to continue. " + "To inspect one spot in a large file, grep for it first, then read a generous startLine/endLine range around it.";
2008
2009
  readFileInputSchema = z19.object({
2009
2010
  path: boundedPathSchema,
2010
2011
  startLine: positiveLineSchema.optional(),
2011
2012
  endLine: positiveLineSchema.optional(),
2012
- maxChars: integerRangeSchema(1, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional()
2013
+ maxChars: integerRangeSchema(1, READ_FILE_OUTPUT_CHARS, "maxChars").optional()
2013
2014
  }).superRefine((value, ctx) => {
2014
2015
  if (value.startLine !== undefined && value.endLine !== undefined && value.endLine < value.startLine) {
2015
2016
  ctx.addIssue({
@@ -2030,7 +2031,8 @@ var init_schema9 = __esm(() => {
2030
2031
  endLine: z19.number().int().min(1),
2031
2032
  content: z19.string(),
2032
2033
  totalLines: z19.number().int().nonnegative(),
2033
- truncated: z19.boolean()
2034
+ truncated: z19.boolean(),
2035
+ nextStartLine: z19.number().int().min(1).optional()
2034
2036
  });
2035
2037
  });
2036
2038
 
@@ -2350,7 +2352,7 @@ var init_tool_registry = __esm(() => {
2350
2352
  permissionMode: "read-only",
2351
2353
  execution: "server",
2352
2354
  availability: "git-workspace",
2353
- activation: "specialized",
2355
+ activation: "core",
2354
2356
  outputPolicy: "artifact-if-large"
2355
2357
  },
2356
2358
  git_diff: {
@@ -2363,7 +2365,7 @@ var init_tool_registry = __esm(() => {
2363
2365
  permissionMode: "read-only",
2364
2366
  execution: "server",
2365
2367
  availability: "git-workspace",
2366
- activation: "specialized",
2368
+ activation: "core",
2367
2369
  outputPolicy: "artifact-if-large"
2368
2370
  },
2369
2371
  git_log: {
@@ -2376,7 +2378,7 @@ var init_tool_registry = __esm(() => {
2376
2378
  permissionMode: "read-only",
2377
2379
  execution: "server",
2378
2380
  availability: "git-workspace",
2379
- activation: "specialized",
2381
+ activation: "core",
2380
2382
  outputPolicy: "artifact-if-large"
2381
2383
  },
2382
2384
  git_show: {
@@ -2389,7 +2391,7 @@ var init_tool_registry = __esm(() => {
2389
2391
  permissionMode: "read-only",
2390
2392
  execution: "server",
2391
2393
  availability: "git-workspace",
2392
- activation: "specialized",
2394
+ activation: "core",
2393
2395
  outputPolicy: "artifact-if-large"
2394
2396
  },
2395
2397
  tool_search: {
@@ -2790,6 +2792,7 @@ var init_agent_tools = __esm(() => {
2790
2792
  contextWindow: z27.number().int().positive().optional(),
2791
2793
  preserveRecentTokens: z27.number().int().nonnegative().optional(),
2792
2794
  originalInputTokens: z27.number().int().nonnegative().optional(),
2795
+ promptCacheActive: z27.boolean().optional(),
2793
2796
  assemblyAbortSignal: z27.custom().optional(),
2794
2797
  turnKey: z27.string().min(1).max(200).optional()
2795
2798
  });
@@ -3068,10 +3071,10 @@ ${environmentContext.trim()}` : "");
3068
3071
  var cwdPromptPlaceholder = "{cwd}", defaultCodingAgentSystemPrompt, toolCallingDisciplineAddendum;
3069
3072
  var init_coding_agent_prompt = __esm(() => {
3070
3073
  init_coding_agent_modes();
3071
- 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. " + "Gather context efficiently \u2014 every tool round trip costs the user seconds. Locate before you read: use grep or glob_search to find the relevant files and line numbers, then read those regions in generous ranges (about 100-150 lines) instead of many small slices or re-reading the same file. When several reads or searches do not depend on each other, request them together in one step. Skip generated output (dist/, build/, minified bundles, node_modules) unless the task is about it. Stop exploring once you have enough evidence to act or answer. " + "Check the available skill descriptions for every task. When a skill is explicitly named or clearly matches the task, load it with the skill tool before acting and follow its instructions within the user's scope and tool permissions. Skill files may be installed outside the workspace; use the skill tool to read their relative supporting resources. Never claim to have used a skill you have not loaded. " + `Inspect context before risky operations. Progress notes: before a major tool action you may emit ONE short note (one sentence, ~15 words) saying what you are about to do. After a tool returns, mention a finding only if it changes your plan, and state it as fact grounded in what the tool actually returned \u2014 cite the file/line/symbol. NEVER claim you found, changed, or fixed something before a tool result confirms it. Skip notes for routine reads, and do not restate the todo list in prose.
3074
+ 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. " + "Gather context efficiently \u2014 every tool round trip costs the user seconds. The environment section maps the repository's files: use it to go straight to the right paths instead of listing directories. Locate before you read: use grep or glob_search to find the relevant files and line numbers, then read whole files (read_file returns up to about 600 lines per call) or, in larger files, a range of a few hundred lines around the spot \u2014 never many small slices, and never re-read content you already have. When several reads or searches do not depend on each other, request them together in one step. Skip generated output (dist/, build/, minified bundles, node_modules) unless the task is about it. Stop exploring once you have enough evidence to act or answer. " + "Check the available skill descriptions for every task. When a skill is explicitly named or clearly matches the task, load it with the skill tool before acting and follow its instructions within the user's scope and tool permissions. Skill files may be installed outside the workspace; use the skill tool to read their relative supporting resources. Never claim to have used a skill you have not loaded. " + `Inspect context before risky operations. Progress notes: before a major tool action you may emit ONE short note (one sentence, ~15 words) saying what you are about to do. After a tool returns, mention a finding only if it changes your plan, and state it as fact grounded in what the tool actually returned \u2014 cite the file/line/symbol. NEVER claim you found, changed, or fixed something before a tool result confirms it. Skip notes for routine reads, and do not restate the todo list in prose.
3072
3075
 
3073
3076
  ` + `Task list discipline \u2014 use the todo_write tool to keep a visible, evolving checklist:
3074
- ` + `- Create the list FIRST. For any task with roughly three or more distinct steps, or any multi-file or multi-stage work, call todo_write as your very first action \u2014 before reading or editing \u2014 with every step listed and all of them set to pending.
3077
+ ` + `- Create the list in your first step. For any task with roughly three or more distinct steps, or any multi-file or multi-stage work, call todo_write in your first response with every step listed and all of them set to pending \u2014 request it together with your first reads or searches, not as a separate step, and always before any edit.
3075
3078
  ` + `- Keep exactly ONE item in_progress at a time. Mark an item in_progress immediately before you start it; never have two items in_progress at once.
3076
3079
  ` + `- Mark an item completed only after a tool result confirms it is actually done (test passed, file written, build clean) \u2014 not on intent. Do this the MOMENT it is confirmed; do not batch completions or wait until the end of the turn.
3077
3080
  ` + `- Completions are monotonic: once a task is completed, do not silently move it back to pending or in_progress. Reopen it only if it genuinely regressed.
@@ -3481,20 +3484,26 @@ function collectTextFromUnknown(value) {
3481
3484
  }
3482
3485
  return "";
3483
3486
  }
3484
- function getLastUserText(messages, prompt) {
3487
+ function getUserTexts(messages, prompt) {
3488
+ const texts = [];
3485
3489
  if (Array.isArray(messages)) {
3486
- for (let index = messages.length - 1;index >= 0; index -= 1) {
3487
- const message = messages[index];
3490
+ for (const message of messages) {
3488
3491
  if (!isRecord4(message) || message.role !== "user") {
3489
3492
  continue;
3490
3493
  }
3491
3494
  const text = collectTextFromUnknown(message).trim();
3492
3495
  if (text) {
3493
- return text;
3496
+ texts.push(text);
3494
3497
  }
3495
3498
  }
3496
3499
  }
3497
- return collectTextFromUnknown(prompt).trim();
3500
+ if (texts.length === 0) {
3501
+ const promptText = collectTextFromUnknown(prompt).trim();
3502
+ if (promptText) {
3503
+ texts.push(promptText);
3504
+ }
3505
+ }
3506
+ return texts;
3498
3507
  }
3499
3508
  function escapeRegExp(value) {
3500
3509
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3520,23 +3529,20 @@ function selectCodingAgentIntentTools({
3520
3529
  messages,
3521
3530
  availableSkillNames = []
3522
3531
  }) {
3523
- const userText = getLastUserText(messages, prompt);
3524
- const normalizedText = userText.toLowerCase();
3525
- if (!normalizedText || casualPromptPattern.test(normalizedText)) {
3532
+ const userTexts = getUserTexts(messages, prompt);
3533
+ if (userTexts.every((text) => casualPromptPattern.test(text))) {
3526
3534
  return [];
3527
3535
  }
3536
+ const latestText = userTexts.at(-1) ?? "";
3528
3537
  const modeTools = getRegistryToolsForMode(mode);
3529
3538
  const selected = new Set(modeTools.filter((toolName) => codingToolRegistry[toolName].activation === "core"));
3530
- if (gitIntentPattern.test(userText)) {
3531
- addTools(selected, gitToolNames);
3532
- }
3533
- if (webIntentPattern.test(userText)) {
3539
+ if (webIntentPattern.test(latestText)) {
3534
3540
  addTools(selected, webToolNames);
3535
3541
  }
3536
- if (mcpIntentPattern.test(userText)) {
3542
+ if (userTexts.some((text) => mcpIntentPattern.test(text))) {
3537
3543
  addTools(selected, mcpToolNames);
3538
3544
  }
3539
- if (skillIntentPattern.test(userText) || requestsAvailableSkill(userText, availableSkillNames)) {
3545
+ if (userTexts.some((text) => skillIntentPattern.test(text) || requestsAvailableSkill(text, availableSkillNames))) {
3540
3546
  selected.add("skill");
3541
3547
  }
3542
3548
  return modeTools.filter((toolName) => selected.has(toolName));
@@ -3616,21 +3622,14 @@ function limitProviderActiveTools(tools, maxTools = maxProviderActiveTools) {
3616
3622
  const toolSet = new Set(tools);
3617
3623
  return providerToolPriority.filter((toolName) => toolSet.has(toolName)).slice(0, maxTools);
3618
3624
  }
3619
- var casualPromptPattern, gitIntentPattern, webIntentPattern, mcpIntentPattern, skillIntentPattern, gitToolNames, webToolNames, mcpToolNames, maxProviderActiveTools = 24, providerToolPriority;
3625
+ var casualPromptPattern, webIntentPattern, mcpIntentPattern, skillIntentPattern, webToolNames, mcpToolNames, maxProviderActiveTools = 24, providerToolPriority;
3620
3626
  var init_intent_tool_selection = __esm(() => {
3621
3627
  init_coding_agent_modes();
3622
3628
  init_tool_registry();
3623
3629
  casualPromptPattern = /^(hi|hello|hey|how are you|how r you|what'?s up|whats up|sup|thanks|thank you|ok|okay|yes|no|nice|cool|great)[\s?!.,]*$/i;
3624
- gitIntentPattern = /(?:\bgit\b|\bcommits?\b|\bstaged?\b|\bunstaged\b|\buntracked\b|\bworking tree\b|\brepositor(?:y|ies)\s+(?:status|diff|history|changes))/i;
3625
3630
  webIntentPattern = /(?:\bhttps?:\/\/|\bwww\.|\bweb(?:site|page)?\b|\binternet\b|\bonline\b|\burl\b|\bbrowser\b|\bsearch\s+(?:the\s+)?web\b|\bbrowse\s+(?:the\s+web|online|for)\b|\blook\s+up\b|\b(?:latest|current|recent|today'?s)\s+(?:news|price|weather|docs?|documentation|release|version)\b)/i;
3626
3631
  mcpIntentPattern = /(?:\bmcp\b|\bmodel context protocol\b)/i;
3627
3632
  skillIntentPattern = /\bskills?\b/i;
3628
- gitToolNames = [
3629
- "git_status",
3630
- "git_diff",
3631
- "git_log",
3632
- "git_show"
3633
- ];
3634
3633
  webToolNames = [
3635
3634
  "web_fetch",
3636
3635
  "web_search"
@@ -5160,19 +5159,44 @@ async function executeReadFile(input, workspaceContext = getDefaultWorkspaceCont
5160
5159
  const fileContent = await fs4.readFile(resolvedPath, "utf8");
5161
5160
  const lines = fileContent.split(/\r?\n/);
5162
5161
  const { start, end } = clampLineRange(lines.length, parsedInput.startLine, parsedInput.endLine);
5163
- const selectedContent = lines.slice(start - 1, end).join(`
5164
- `);
5165
- const maxChars = parsedInput.maxChars ?? DEFAULT_TOOL_TEXT_OUTPUT_CHARS;
5166
- const truncated = truncateText(selectedContent, maxChars);
5162
+ const maxChars = parsedInput.maxChars ?? READ_FILE_OUTPUT_CHARS;
5163
+ const taken = takeWholeLines(lines.slice(start - 1, end), maxChars);
5164
+ const lastLine = start + taken.lineCount - 1;
5167
5165
  return readFileOutputSchema.parse({
5168
5166
  path: relativePath,
5169
5167
  startLine: start,
5170
- endLine: end,
5171
- content: truncated.text,
5168
+ endLine: lastLine,
5169
+ content: taken.text,
5172
5170
  totalLines: lines.length,
5173
- truncated: truncated.truncated
5171
+ truncated: taken.truncated,
5172
+ ...lastLine < end ? { nextStartLine: lastLine + 1 } : {}
5174
5173
  });
5175
5174
  }
5175
+ function takeWholeLines(lines, maxChars) {
5176
+ let used = 0;
5177
+ let lineCount = 0;
5178
+ for (const line of lines) {
5179
+ const cost = line.length + (lineCount > 0 ? 1 : 0);
5180
+ if (used + cost > maxChars) {
5181
+ break;
5182
+ }
5183
+ used += cost;
5184
+ lineCount += 1;
5185
+ }
5186
+ if (lineCount === 0 && lines.length > 0) {
5187
+ return {
5188
+ text: truncateText(lines[0] ?? "", maxChars).text,
5189
+ lineCount: 1,
5190
+ truncated: true
5191
+ };
5192
+ }
5193
+ return {
5194
+ text: lines.slice(0, lineCount).join(`
5195
+ `),
5196
+ lineCount,
5197
+ truncated: lineCount < lines.length
5198
+ };
5199
+ }
5176
5200
  var init_runtime8 = __esm(() => {
5177
5201
  init_resolve_within_workspace();
5178
5202
  init_output_utils();
@@ -6444,10 +6468,12 @@ function buildProviderView(options) {
6444
6468
  }
6445
6469
  const tokenBoundedCutoffIndex = computeTokenBoundedCompactionCutoffIndex(messages, preserveRecentTokens, afterAnchorIndex);
6446
6470
  const userIndicesAfterAnchor = messages.map((message, index) => ({ message, index })).filter(({ message, index }) => index >= afterAnchorIndex && message.role === "user").map(({ index }) => index);
6447
- const rollingTurnCutoffIndex = userIndicesAfterAnchor.length > config.uncachedRollingCompactionUserTurns ? userIndicesAfterAnchor[userIndicesAfterAnchor.length - config.uncachedRollingCompactionUserTurns] ?? afterAnchorIndex : afterAnchorIndex;
6471
+ const rollingKeepUserTurns = Math.max(1, Math.ceil(config.uncachedRollingCompactionUserTurns / 2));
6472
+ const rollingTurnCutoffIndex = userIndicesAfterAnchor.length > rollingKeepUserTurns ? userIndicesAfterAnchor[userIndicesAfterAnchor.length - rollingKeepUserTurns] ?? afterAnchorIndex : afterAnchorIndex;
6448
6473
  const overCompactThreshold = triggerTokens(estimate.tokens) > config.compactAtFraction * inputBudgetTokens;
6474
+ const rollingActive = (options.rollingCompaction ?? options.cacheActive === false) && config.aggressivePruneWhenUncached;
6449
6475
  const userTurnsAfterAnchor = messages.slice(afterAnchorIndex).filter((message) => message.role === "user").length;
6450
- const rollingThresholdReached = uncachedActive && userTurnsAfterAnchor > config.uncachedRollingCompactionUserTurns;
6476
+ const rollingThresholdReached = rollingActive && userTurnsAfterAnchor > (options.continueRolling ? rollingKeepUserTurns : config.uncachedRollingCompactionUserTurns);
6451
6477
  const compactionCutoffIndex = rollingThresholdReached ? Math.max(tokenBoundedCutoffIndex, rollingTurnCutoffIndex) : tokenBoundedCutoffIndex;
6452
6478
  const coveredMessages = capCoverageToTokenBudget(messages.slice(afterAnchorIndex, compactionCutoffIndex), config.maxCoverageTokensPerCompaction);
6453
6479
  let compactionBlockedReason = null;
@@ -6901,6 +6927,14 @@ import path12 from "path";
6901
6927
  import {
6902
6928
  toolModelMessageSchema
6903
6929
  } from "ai";
6930
+ function resolveToolOutputArtifactThresholds({
6931
+ promptCacheActive
6932
+ }) {
6933
+ return {
6934
+ thresholdCharacters: promptCacheActive ? DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS : DEFAULT_TOOL_OUTPUT_ARTIFACT_THRESHOLD_CHARACTERS,
6935
+ currentTurnThresholdCharacters: DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS
6936
+ };
6937
+ }
6904
6938
  function isRecord10(value) {
6905
6939
  return typeof value === "object" && value !== null;
6906
6940
  }
@@ -7138,7 +7172,7 @@ async function artifactizeLargeToolOutputs(messages, options = {}) {
7138
7172
  savedCharacters
7139
7173
  };
7140
7174
  }
7141
- var TOOL_OUTPUT_ARTIFACT_HANDLE_PREFIX = "lightcode-artifact://tool-output/sha256/", DEFAULT_TOOL_OUTPUT_ARTIFACT_THRESHOLD_CHARACTERS, DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS = 16000, DEFAULT_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS = 2400, MIN_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS = 512, SHA256_PATTERN, ARTIFACT_PREVIEW_MARKER = "[Lightcode tool-output artifact]";
7175
+ var TOOL_OUTPUT_ARTIFACT_HANDLE_PREFIX = "lightcode-artifact://tool-output/sha256/", DEFAULT_TOOL_OUTPUT_ARTIFACT_THRESHOLD_CHARACTERS, DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS = 32000, DEFAULT_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS = 2400, MIN_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS = 512, SHA256_PATTERN, ARTIFACT_PREVIEW_MARKER = "[Lightcode tool-output artifact]";
7142
7176
  var init_tool_output_artifacts = __esm(() => {
7143
7177
  init_src();
7144
7178
  DEFAULT_TOOL_OUTPUT_ARTIFACT_THRESHOLD_CHARACTERS = DEFAULT_TOOL_TEXT_OUTPUT_CHARS;
@@ -8625,7 +8659,9 @@ function createCodingAgent({
8625
8659
  });
8626
8660
  const artifactized = await artifactizeLargeToolOutputs(messages, {
8627
8661
  signal: context.assemblyAbortSignal,
8628
- currentTurnThresholdCharacters: DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS
8662
+ ...resolveToolOutputArtifactThresholds({
8663
+ promptCacheActive: context.promptCacheActive ?? false
8664
+ })
8629
8665
  });
8630
8666
  const assembled = assembler.assembleModelMessages(artifactized.messages, {
8631
8667
  preserveRecentTokens: context.preserveRecentTokens
@@ -8691,6 +8727,7 @@ function createCodingAgent({
8691
8727
  reservedOutputTokens: options.maxOutputTokens,
8692
8728
  preserveRecentTokens: options.preserveRecentTokens,
8693
8729
  originalInputTokens: options.originalInputTokens,
8730
+ promptCacheActive: options.promptCacheActive,
8694
8731
  assemblyAbortSignal: options.assemblyAbortSignal
8695
8732
  },
8696
8733
  instructions
@@ -13350,7 +13387,8 @@ function summarizeRawModel(raw3) {
13350
13387
  const pricingRaw = Reflect.get(raw3, "pricing");
13351
13388
  const pricing = typeof pricingRaw === "object" && pricingRaw !== null ? {
13352
13389
  prompt: typeof Reflect.get(pricingRaw, "prompt") === "string" ? Reflect.get(pricingRaw, "prompt") : null,
13353
- completion: typeof Reflect.get(pricingRaw, "completion") === "string" ? Reflect.get(pricingRaw, "completion") : null
13390
+ completion: typeof Reflect.get(pricingRaw, "completion") === "string" ? Reflect.get(pricingRaw, "completion") : null,
13391
+ inputCacheRead: typeof Reflect.get(pricingRaw, "input_cache_read") === "string" ? Reflect.get(pricingRaw, "input_cache_read") : null
13354
13392
  } : null;
13355
13393
  return {
13356
13394
  id,
@@ -13380,12 +13418,12 @@ async function listOpenRouterModels({
13380
13418
  forceRefresh = false
13381
13419
  } = {}) {
13382
13420
  const cache = readCache();
13383
- if (!forceRefresh && cache && Date.now() - cache.fetchedAt < cacheTtlMs) {
13421
+ if (!forceRefresh && cache && cache.version === modelsCacheVersion && Date.now() - cache.fetchedAt < cacheTtlMs) {
13384
13422
  return { models: cache.models, fromCache: true };
13385
13423
  }
13386
13424
  try {
13387
13425
  const models = await fetchModelsFromApi();
13388
- writeCache({ fetchedAt: Date.now(), models });
13426
+ writeCache({ version: modelsCacheVersion, fetchedAt: Date.now(), models });
13389
13427
  return { models, fromCache: false };
13390
13428
  } catch (error) {
13391
13429
  logger5.warn("openrouter_models_fetch_failed", {
@@ -13423,12 +13461,14 @@ function toCapabilityPricing(pricing) {
13423
13461
  if (!Number.isFinite(promptPerToken) || !Number.isFinite(completionPerToken)) {
13424
13462
  return null;
13425
13463
  }
13464
+ const cacheReadPerToken = pricing.inputCacheRead ? Number.parseFloat(pricing.inputCacheRead) : Number.NaN;
13426
13465
  return {
13427
13466
  inputPerMTok: promptPerToken * 1e6,
13428
- outputPerMTok: completionPerToken * 1e6
13467
+ outputPerMTok: completionPerToken * 1e6,
13468
+ cachedInputPerMTok: Number.isFinite(cacheReadPerToken) && cacheReadPerToken > 0 ? cacheReadPerToken * 1e6 : null
13429
13469
  };
13430
13470
  }
13431
- var logger5, openRouterModelsUrl = "https://openrouter.ai/api/v1/models", cacheTtlMs, fetchTimeoutMs = 15000, openRouterModelSummarySchema, modelsCacheSchema;
13471
+ var logger5, openRouterModelsUrl = "https://openrouter.ai/api/v1/models", cacheTtlMs, fetchTimeoutMs = 15000, modelsCacheVersion = 2, openRouterModelSummarySchema, modelsCacheSchema;
13432
13472
  var init_openrouter_models = __esm(() => {
13433
13473
  init_src();
13434
13474
  init_network_fetch();
@@ -13443,10 +13483,12 @@ var init_openrouter_models = __esm(() => {
13443
13483
  supportsReasoning: z40.boolean(),
13444
13484
  pricing: z40.object({
13445
13485
  prompt: z40.string().nullable(),
13446
- completion: z40.string().nullable()
13486
+ completion: z40.string().nullable(),
13487
+ inputCacheRead: z40.string().nullable().default(null)
13447
13488
  }).nullable()
13448
13489
  });
13449
13490
  modelsCacheSchema = z40.object({
13491
+ version: z40.number().int().optional(),
13450
13492
  fetchedAt: z40.number(),
13451
13493
  models: z40.array(openRouterModelSummarySchema)
13452
13494
  });
@@ -13730,6 +13772,7 @@ function resolveAnthropicModel({
13730
13772
  contextWindow: getModelContextWindow("anthropic", modelId),
13731
13773
  missingCredentialHints,
13732
13774
  supportsPromptCaching: true,
13775
+ rollingCompaction: false,
13733
13776
  pricing: getAnthropicModelPricing(modelId),
13734
13777
  webSearchCapability,
13735
13778
  providerTools: webSearchCapability.execution === "provider" ? {
@@ -13745,7 +13788,7 @@ function resolveCapabilityPricing(capabilities, cachedInputFactor) {
13745
13788
  return {
13746
13789
  inputPerMTok: pricing.inputPerMTok,
13747
13790
  outputPerMTok: pricing.outputPerMTok,
13748
- cachedInputPerMTok: cachedInputFactor === null ? null : pricing.inputPerMTok * cachedInputFactor
13791
+ cachedInputPerMTok: pricing.cachedInputPerMTok ?? (cachedInputFactor === null ? null : pricing.inputPerMTok * cachedInputFactor)
13749
13792
  };
13750
13793
  }
13751
13794
  function resolveOpenAITransportModel({
@@ -13794,6 +13837,8 @@ function resolveOpenAITransportModel({
13794
13837
  env,
13795
13838
  storedCredentials
13796
13839
  });
13840
+ const openRouterAnthropicFamily = isOpenRouter && isOpenRouterAnthropicFamilyModel(configuredModel);
13841
+ const openRouterCatalogCaching = isOpenRouter && (capabilities?.pricing?.cachedInputPerMTok ?? 0) > 0;
13797
13842
  return {
13798
13843
  provider: config6.provider,
13799
13844
  configuredModel,
@@ -13807,8 +13852,9 @@ function resolveOpenAITransportModel({
13807
13852
  contextWindow: capabilities?.contextLength ?? getModelContextWindow(config6.provider, configuredModel),
13808
13853
  missingCredentialHints: apiKey ? [] : isOpenCodeZen ? ["Set OPENCODE_API_KEY to call OpenCode Zen models."] : isOpenRouter ? ["Set OPENROUTER_API_KEY to call OpenRouter models."] : [],
13809
13854
  maxCompletionTokens: capabilities?.maxCompletionTokens ?? null,
13810
- supportsPromptCaching: isOpenRouter && isOpenRouterAnthropicFamilyModel(configuredModel),
13811
- pricing: resolveCapabilityPricing(capabilities, isOpenRouter && isOpenRouterAnthropicFamilyModel(configuredModel) ? 0.1 : null),
13855
+ supportsPromptCaching: openRouterAnthropicFamily || openRouterCatalogCaching,
13856
+ rollingCompaction: !openRouterAnthropicFamily,
13857
+ pricing: resolveCapabilityPricing(capabilities, openRouterAnthropicFamily ? 0.1 : null),
13812
13858
  webSearchCapability,
13813
13859
  providerTools: openRouterProvider && webSearchCapability.execution === "provider" ? {
13814
13860
  web_search: createOpenRouterWebSearchTool(openRouterProvider, webSearchCapability)
@@ -14066,6 +14112,7 @@ var init_subagent_task_store = __esm(() => {
14066
14112
  });
14067
14113
 
14068
14114
  // apps/server/src/lib/workspace-context.ts
14115
+ import { execFile as execFile3 } from "child_process";
14069
14116
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
14070
14117
  import {
14071
14118
  chmod as chmod2,
@@ -14080,6 +14127,7 @@ import {
14080
14127
  } from "fs/promises";
14081
14128
  import { platform as platform3 } from "os";
14082
14129
  import path21 from "path";
14130
+ import { promisify as promisify4 } from "util";
14083
14131
  import { z as z41 } from "zod";
14084
14132
  function isContainedPath(root, candidate) {
14085
14133
  const relative = path21.relative(root, candidate);
@@ -14088,12 +14136,16 @@ function isContainedPath(root, candidate) {
14088
14136
  function escapeAttribute(value) {
14089
14137
  return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
14090
14138
  }
14091
- function instructionEpochStatePath({
14139
+ function sessionStatePath({
14140
+ directory,
14092
14141
  sessionId,
14093
14142
  dataDir
14094
14143
  }) {
14095
14144
  const sessionKey = createHash5("sha256").update(sessionId).digest("hex");
14096
- return path21.join(dataDir ?? getLightcodeDataDir(), "instruction-epochs", `${sessionKey}.json`);
14145
+ return path21.join(dataDir ?? getLightcodeDataDir(), directory, `${sessionKey}.json`);
14146
+ }
14147
+ function instructionEpochStatePath(options) {
14148
+ return sessionStatePath({ directory: "instruction-epochs", ...options });
14097
14149
  }
14098
14150
  async function loadInstructionEpochs(statePath) {
14099
14151
  try {
@@ -14114,6 +14166,9 @@ async function loadInstructionEpochs(statePath) {
14114
14166
  return [];
14115
14167
  }
14116
14168
  async function persistInstructionEpochs(statePath, epochs) {
14169
+ await writePrivateJsonFile(statePath, { epochs });
14170
+ }
14171
+ async function writePrivateJsonFile(statePath, value) {
14117
14172
  const directory = path21.dirname(statePath);
14118
14173
  await mkdir3(directory, { recursive: true, mode: 448 });
14119
14174
  await chmod2(directory, 448).catch(() => {
@@ -14121,7 +14176,7 @@ async function persistInstructionEpochs(statePath, epochs) {
14121
14176
  });
14122
14177
  const temporaryPath = `${statePath}.${process.pid}.${randomUUID3()}.tmp`;
14123
14178
  try {
14124
- await writeFile2(temporaryPath, `${JSON.stringify({ epochs }, null, 2)}
14179
+ await writeFile2(temporaryPath, `${JSON.stringify(value, null, 2)}
14125
14180
  `, { encoding: "utf8", flag: "wx", mode: 384 });
14126
14181
  await rename2(temporaryPath, statePath);
14127
14182
  await chmod2(statePath, 384).catch(() => {
@@ -14370,6 +14425,138 @@ async function buildDirectoryListing(cwd) {
14370
14425
  return "(unable to list directory)";
14371
14426
  }
14372
14427
  }
14428
+ async function listRepositoryFiles(cwd) {
14429
+ try {
14430
+ const { stdout } = await execFileAsync3("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
14431
+ cwd,
14432
+ encoding: "utf8",
14433
+ maxBuffer: 64 * 1024 * 1024,
14434
+ timeout: repositoryFileListTimeoutMs,
14435
+ windowsHide: true
14436
+ });
14437
+ return [...new Set(stdout.split("\x00").filter(Boolean))];
14438
+ } catch {
14439
+ return null;
14440
+ }
14441
+ }
14442
+ function createRepositoryTreeNode() {
14443
+ return { directories: new Map, files: [], fileCount: 0 };
14444
+ }
14445
+ function compareNames(left, right) {
14446
+ return left < right ? -1 : left > right ? 1 : 0;
14447
+ }
14448
+ function visibleSubdirectories(node) {
14449
+ const all = [...node.directories.entries()].sort(([left], [right]) => compareNames(left, right)).map(([name, child]) => ({ name, node: child }));
14450
+ return {
14451
+ shown: all.slice(0, maxRepositoryMapChildren),
14452
+ hiddenCount: Math.max(0, all.length - maxRepositoryMapChildren)
14453
+ };
14454
+ }
14455
+ function subdirectoryLines(node, depth) {
14456
+ const indent = " ".repeat(depth);
14457
+ const { shown, hiddenCount } = visibleSubdirectories(node);
14458
+ const lines = shown.map(({ name, node: child }) => `${indent}${name}/ (${child.fileCount})`);
14459
+ if (hiddenCount > 0) {
14460
+ lines.push(`${indent}\u2026 ${hiddenCount} more folders`);
14461
+ }
14462
+ return lines;
14463
+ }
14464
+ function fileNameLines(node, depth) {
14465
+ const indent = " ".repeat(depth);
14466
+ const files = [...node.files].sort(compareNames);
14467
+ const lines = files.slice(0, maxRepositoryMapChildren).map((name) => `${indent}${name}`);
14468
+ if (files.length > lines.length) {
14469
+ lines.push(`${indent}\u2026 ${files.length - lines.length} more files`);
14470
+ }
14471
+ return lines;
14472
+ }
14473
+ function filePlaceholderLines(node, depth) {
14474
+ return node.files.length > 0 ? [`${" ".repeat(depth)}\u2026 ${node.files.length} files`] : [];
14475
+ }
14476
+ function linesCost(lines) {
14477
+ return lines.reduce((sum, line) => sum + line.length + 1, 0);
14478
+ }
14479
+ function formatRepositoryMap(paths, maxChars = maxRepositoryMapChars) {
14480
+ const root = createRepositoryTreeNode();
14481
+ for (const filePath of paths) {
14482
+ const segments = filePath.split("/").filter(Boolean);
14483
+ const fileName = segments.pop();
14484
+ if (!fileName) {
14485
+ continue;
14486
+ }
14487
+ let current = root;
14488
+ current.fileCount += 1;
14489
+ for (const segment of segments) {
14490
+ let child = current.directories.get(segment);
14491
+ if (!child) {
14492
+ child = createRepositoryTreeNode();
14493
+ current.directories.set(segment, child);
14494
+ }
14495
+ child.fileCount += 1;
14496
+ current = child;
14497
+ }
14498
+ current.files.push(fileName);
14499
+ }
14500
+ if (root.fileCount === 0) {
14501
+ return "(no files)";
14502
+ }
14503
+ const order = [
14504
+ { node: root, depth: 0 }
14505
+ ];
14506
+ const expanded = new Set;
14507
+ let used = 0;
14508
+ for (let index = 0;index < order.length; index += 1) {
14509
+ const { node, depth } = order[index];
14510
+ const cost = linesCost(subdirectoryLines(node, depth)) + linesCost(filePlaceholderLines(node, depth));
14511
+ if (node !== root && used + cost > maxChars) {
14512
+ continue;
14513
+ }
14514
+ used += cost;
14515
+ expanded.add(node);
14516
+ for (const { name, node: child } of visibleSubdirectories(node).shown) {
14517
+ if (!name.startsWith(".")) {
14518
+ order.push({ node: child, depth: depth + 1 });
14519
+ }
14520
+ }
14521
+ }
14522
+ const fileCandidates = order.filter(({ node }) => expanded.has(node) && node.files.length > 0).sort((left, right) => left.node === root ? -1 : right.node === root ? 1 : left.node.files.length - right.node.files.length);
14523
+ const filesShown = new Set;
14524
+ for (const { node, depth } of fileCandidates) {
14525
+ const delta = linesCost(fileNameLines(node, depth)) - linesCost(filePlaceholderLines(node, depth));
14526
+ if (used + delta > maxChars) {
14527
+ continue;
14528
+ }
14529
+ used += delta;
14530
+ filesShown.add(node);
14531
+ }
14532
+ const lines = [];
14533
+ const render = (node, depth) => {
14534
+ const { shown } = visibleSubdirectories(node);
14535
+ subdirectoryLines(node, depth).forEach((line, index) => {
14536
+ lines.push(line);
14537
+ const child = shown[index]?.node;
14538
+ if (child && expanded.has(child)) {
14539
+ render(child, depth + 1);
14540
+ }
14541
+ });
14542
+ lines.push(...filesShown.has(node) ? fileNameLines(node, depth) : filePlaceholderLines(node, depth));
14543
+ };
14544
+ render(root, 0);
14545
+ return lines.join(`
14546
+ `);
14547
+ }
14548
+ async function buildRepositoryOverview(cwd) {
14549
+ const files = await listRepositoryFiles(cwd);
14550
+ if (!files) {
14551
+ return ["Top-level entries:", await buildDirectoryListing(cwd)].join(`
14552
+ `);
14553
+ }
14554
+ return [
14555
+ `Repository files (${files.length}; dir/ (N) = N files inside, "\u2026" = more not shown; use glob_search or list_files for the rest):`,
14556
+ formatRepositoryMap(files)
14557
+ ].join(`
14558
+ `);
14559
+ }
14373
14560
  function buildGitSummary(cwd) {
14374
14561
  const cached = gitSummaryCache.get(cwd);
14375
14562
  if (cached && cached.expiresAt > Date.now()) {
@@ -14449,13 +14636,98 @@ async function buildWorkspaceInstructionBaseline({
14449
14636
  `)
14450
14637
  };
14451
14638
  }
14639
+ function memoizeEnvironmentSnapshot(statePath, snapshot) {
14640
+ environmentSnapshotMemo.delete(statePath);
14641
+ environmentSnapshotMemo.set(statePath, snapshot);
14642
+ if (environmentSnapshotMemo.size > maxMemoizedEnvironmentSnapshots) {
14643
+ const oldest = environmentSnapshotMemo.keys().next().value;
14644
+ if (oldest !== undefined) {
14645
+ environmentSnapshotMemo.delete(oldest);
14646
+ }
14647
+ }
14648
+ return snapshot;
14649
+ }
14650
+ async function captureEnvironmentSnapshot(cwd) {
14651
+ const [overview, git] = await Promise.all([
14652
+ buildRepositoryOverview(cwd),
14653
+ buildGitSummary(cwd)
14654
+ ]);
14655
+ const capturedAt = new Date().toISOString();
14656
+ return {
14657
+ cwd,
14658
+ capturedAt,
14659
+ body: [
14660
+ `Working directory: ${cwd}`,
14661
+ `Platform: ${describePlatform()}`,
14662
+ `Date: ${capturedAt.slice(0, 10)} (session start)`,
14663
+ "",
14664
+ "Git (snapshot from session start; call git_status for the current state):",
14665
+ git,
14666
+ "",
14667
+ overview
14668
+ ].join(`
14669
+ `)
14670
+ };
14671
+ }
14672
+ async function loadEnvironmentSnapshot(statePath, cwd) {
14673
+ const memoized = environmentSnapshotMemo.get(statePath);
14674
+ if (memoized?.cwd === cwd) {
14675
+ return memoized;
14676
+ }
14677
+ try {
14678
+ const parsed = environmentSnapshotSchema.safeParse(JSON.parse(await readFile4(statePath, "utf8")));
14679
+ if (parsed.success && parsed.data.cwd === cwd) {
14680
+ return memoizeEnvironmentSnapshot(statePath, parsed.data);
14681
+ }
14682
+ if (!parsed.success) {
14683
+ logger7.warn("workspace_environment_snapshot_invalid", { statePath });
14684
+ }
14685
+ } catch (error) {
14686
+ const code = isRecord3(error) ? Reflect.get(error, "code") : undefined;
14687
+ if (code !== "ENOENT") {
14688
+ logger7.warn("workspace_environment_snapshot_load_failed", {
14689
+ statePath,
14690
+ error: getErrorMessage(error)
14691
+ });
14692
+ }
14693
+ }
14694
+ return null;
14695
+ }
14696
+ async function resolveEnvironmentSnapshot({
14697
+ cwd,
14698
+ sessionId,
14699
+ dataDir
14700
+ }) {
14701
+ if (!sessionId) {
14702
+ return captureEnvironmentSnapshot(cwd);
14703
+ }
14704
+ const statePath = sessionStatePath({
14705
+ directory: "session-environment",
14706
+ sessionId,
14707
+ dataDir
14708
+ });
14709
+ const existing = await loadEnvironmentSnapshot(statePath, cwd);
14710
+ if (existing) {
14711
+ return existing;
14712
+ }
14713
+ const snapshot = memoizeEnvironmentSnapshot(statePath, await captureEnvironmentSnapshot(cwd));
14714
+ try {
14715
+ await writePrivateJsonFile(statePath, snapshot);
14716
+ } catch (error) {
14717
+ logger7.warn("workspace_environment_snapshot_persist_failed", {
14718
+ statePath,
14719
+ error: getErrorMessage(error)
14720
+ });
14721
+ }
14722
+ return snapshot;
14723
+ }
14452
14724
  async function buildWorkspaceContext({
14453
14725
  cwd,
14454
14726
  sessionId,
14455
14727
  relatedPaths,
14456
14728
  dataDir
14457
14729
  }) {
14458
- const [baseline, nestedInstructions, listing, git] = await Promise.all([
14730
+ const [baseline, nestedInstructions, snapshot] = await Promise.all([
14459
14731
  buildWorkspaceInstructionBaseline({ cwd }),
14460
14732
  buildNestedWorkspaceInstructionBlock({
14461
14733
  cwd,
@@ -14463,26 +14735,16 @@ async function buildWorkspaceContext({
14463
14735
  relatedPaths,
14464
14736
  dataDir
14465
14737
  }),
14466
- buildDirectoryListing(cwd),
14467
- buildGitSummary(cwd)
14738
+ resolveEnvironmentSnapshot({ cwd, sessionId, dataDir })
14468
14739
  ]);
14469
14740
  const skills = buildAvailableSkills(cwd);
14470
- const today = new Date().toISOString().slice(0, 10);
14471
14741
  const parts = [
14472
14742
  baseline.block,
14473
14743
  nestedInstructions ? `
14474
14744
  ${nestedInstructions}` : "",
14475
14745
  "",
14476
14746
  "<environment>",
14477
- `Working directory: ${cwd}`,
14478
- `Platform: ${describePlatform()}`,
14479
- `Date: ${today}`,
14480
- "",
14481
- "Git:",
14482
- git,
14483
- "",
14484
- "Top-level entries:",
14485
- listing,
14747
+ snapshot.body,
14486
14748
  skills ? `
14487
14749
  ${skills}` : "",
14488
14750
  "</environment>"
@@ -14493,48 +14755,12 @@ ${skills}` : "",
14493
14755
  \u2026 (environment truncated)
14494
14756
  </environment>` : block;
14495
14757
  }
14496
- async function buildWorkspaceContextDelta({
14497
- cwd,
14498
- sessionId,
14499
- relatedPaths,
14500
- dataDir
14501
- }) {
14502
- const [baseline, nestedInstructions, git] = await Promise.all([
14503
- buildWorkspaceInstructionBaseline({ cwd }),
14504
- buildNestedWorkspaceInstructionBlock({
14505
- cwd,
14506
- sessionId,
14507
- relatedPaths,
14508
- dataDir
14509
- }),
14510
- buildGitSummary(cwd)
14511
- ]);
14512
- const skills = buildAvailableSkills(cwd);
14513
- const today = new Date().toISOString().slice(0, 10);
14514
- return [
14515
- baseline.block,
14516
- nestedInstructions ? `
14517
- ${nestedInstructions}` : "",
14518
- "",
14519
- "<environment>",
14520
- `Working directory: ${cwd}`,
14521
- `Platform: ${describePlatform()}`,
14522
- `Date: ${today}`,
14523
- "(Top-level listing is available through list_files; current git state follows.)",
14524
- "",
14525
- "Git:",
14526
- git,
14527
- skills ? `
14528
- ${skills}` : "",
14529
- "</environment>"
14530
- ].join(`
14531
- `);
14532
- }
14533
- var logger7, projectInstructionNames, maxListedEntries = 40, maxInstructionChars = 6000, maxNestedInstructionChars = 6000, maxNestedInstructionFileChars = 1600, maxBlockChars = 18000, instructionEpochSchema, instructionEpochStateSchema, gitSummaryCacheTtlMs = 5000, gitSummaryCache;
14758
+ var execFileAsync3, logger7, projectInstructionNames, maxListedEntries = 40, maxInstructionChars = 6000, maxNestedInstructionChars = 6000, maxNestedInstructionFileChars = 1600, maxRepositoryMapChars = 6000, maxRepositoryMapChildren = 32, maxBlockChars = 24000, repositoryFileListTimeoutMs = 5000, instructionEpochSchema, instructionEpochStateSchema, gitSummaryCacheTtlMs = 5000, gitSummaryCache, environmentSnapshotSchema, maxMemoizedEnvironmentSnapshots = 256, environmentSnapshotMemo;
14534
14759
  var init_workspace_context = __esm(() => {
14535
14760
  init_runtime17();
14536
14761
  init_src2();
14537
14762
  init_src();
14763
+ execFileAsync3 = promisify4(execFile3);
14538
14764
  logger7 = createLogger("workspace-context");
14539
14765
  projectInstructionNames = ["AGENTS.md", "CLAUDE.md"];
14540
14766
  instructionEpochSchema = z41.object({
@@ -14548,6 +14774,12 @@ var init_workspace_context = __esm(() => {
14548
14774
  epochs: z41.array(instructionEpochSchema)
14549
14775
  });
14550
14776
  gitSummaryCache = new Map;
14777
+ environmentSnapshotSchema = z41.object({
14778
+ cwd: z41.string().min(1),
14779
+ capturedAt: z41.string().min(1),
14780
+ body: z41.string()
14781
+ });
14782
+ environmentSnapshotMemo = new Map;
14551
14783
  });
14552
14784
 
14553
14785
  // apps/server/src/lib/subagent-runner.ts
@@ -15138,6 +15370,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
15138
15370
  });
15139
15371
  const contextConfig = lightcodeConfigResult.config.context;
15140
15372
  const cacheActive = resolvedProviderModel.supportsPromptCaching;
15373
+ const rollingCompaction = resolvedProviderModel.rollingCompaction ?? !cacheActive;
15141
15374
  const endpointLimitTokens = getLearnedContextLimit(chatModelId);
15142
15375
  const effectiveContextWindow = resolveContextWindowTokens({
15143
15376
  config: contextConfig,
@@ -15145,9 +15378,12 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
15145
15378
  endpointLimitTokens
15146
15379
  });
15147
15380
  const reservedOutputTokens = Math.min(resolveMaxOutputTokens(lightcodeConfigResult.config.maxOutputTokens, lightcodeConfigDefaults.maxOutputTokens, resolvedProviderModel.maxCompletionTokens), Math.max(1024, Math.floor(effectiveContextWindow / 2)));
15148
- const isFirstAssistantTurn = !validatedMessages.some((message) => message.role === "assistant");
15149
15381
  const relatedPaths = collectRelatedWorkspacePaths(validatedMessages);
15150
- const environmentContext = isFirstAssistantTurn ? await buildWorkspaceContext({ cwd, sessionId, relatedPaths }) : await buildWorkspaceContextDelta({ cwd, sessionId, relatedPaths });
15382
+ const environmentContext = await buildWorkspaceContext({
15383
+ cwd,
15384
+ sessionId,
15385
+ relatedPaths
15386
+ });
15151
15387
  const availableSkillNames = listSkills({ cwd }).map((skill) => skill.name);
15152
15388
  const useFastChatPath = shouldUseFastChatPath({
15153
15389
  messages: providerInputMessages,
@@ -15217,6 +15453,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15217
15453
  endpointLimitTokens,
15218
15454
  pendingInteractionCount,
15219
15455
  cacheActive,
15456
+ rollingCompaction,
15220
15457
  fixedInputTokens: turnAssembler.fixedInputTokens
15221
15458
  });
15222
15459
  if (contextConfig.autoCompact) {
@@ -15247,6 +15484,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15247
15484
  endpointLimitTokens,
15248
15485
  pendingInteractionCount,
15249
15486
  cacheActive,
15487
+ rollingCompaction,
15250
15488
  fixedInputTokens: turnAssembler.fixedInputTokens
15251
15489
  });
15252
15490
  logger11.info("context_auto_compacted", {
@@ -15289,6 +15527,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15289
15527
  endpointLimitTokens,
15290
15528
  pendingInteractionCount: 0,
15291
15529
  cacheActive,
15530
+ rollingCompaction,
15292
15531
  fixedInputTokens: turnAssembler.fixedInputTokens
15293
15532
  });
15294
15533
  if (overflowView.coveredMessages.length > 0) {
@@ -15315,6 +15554,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15315
15554
  endpointLimitTokens,
15316
15555
  pendingInteractionCount: 0,
15317
15556
  cacheActive,
15557
+ rollingCompaction,
15318
15558
  fixedInputTokens: turnAssembler.fixedInputTokens
15319
15559
  });
15320
15560
  logger11.info("context_overflow_recovery", {
@@ -15368,7 +15608,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15368
15608
  noteContextOverflow(sessionId);
15369
15609
  };
15370
15610
  const maybeRunRollingCompaction = async (finishedMessages) => {
15371
- if (cacheActive || !contextConfig.autoCompact) {
15611
+ if (!rollingCompaction || !contextConfig.autoCompact) {
15372
15612
  return;
15373
15613
  }
15374
15614
  if (rollingCompactionInFlight.has(sessionId)) {
@@ -15376,38 +15616,45 @@ ${environmentContext}` : agentCallSettings.instructions;
15376
15616
  }
15377
15617
  rollingCompactionInFlight.add(sessionId);
15378
15618
  try {
15379
- const latestState = await loadSessionContextStateSafe(sessionId);
15380
- const rollingView = buildProviderView({
15381
- messages: finishedMessages,
15382
- contextState: latestState,
15383
- config: contextConfig,
15384
- modelContextWindow: resolvedProviderModel.contextWindow,
15385
- reservedOutputTokens: budgetReservedOutputTokens,
15386
- endpointLimitTokens,
15387
- pendingInteractionCount: await countPendingChatInteractions(sessionId),
15388
- cacheActive,
15389
- fixedInputTokens: turnAssembler.fixedInputTokens
15390
- });
15391
- if (!rollingView.needsRollingCompaction) {
15392
- return;
15619
+ let latestState = await loadSessionContextStateSafe(sessionId);
15620
+ const pendingInteractionCount2 = await countPendingChatInteractions(sessionId);
15621
+ for (let round = 1;round <= MAX_ROLLING_COMPACTION_ROUNDS; round += 1) {
15622
+ const rollingView = buildProviderView({
15623
+ messages: finishedMessages,
15624
+ contextState: latestState,
15625
+ config: contextConfig,
15626
+ modelContextWindow: resolvedProviderModel.contextWindow,
15627
+ reservedOutputTokens: budgetReservedOutputTokens,
15628
+ endpointLimitTokens,
15629
+ pendingInteractionCount: pendingInteractionCount2,
15630
+ cacheActive,
15631
+ rollingCompaction,
15632
+ continueRolling: round > 1,
15633
+ fixedInputTokens: turnAssembler.fixedInputTokens
15634
+ });
15635
+ if (!rollingView.needsRollingCompaction) {
15636
+ return;
15637
+ }
15638
+ const compaction = await compactSessionContext({
15639
+ sessionId,
15640
+ coveredMessages: rollingView.coveredMessages,
15641
+ previousState: latestState,
15642
+ model: resolvedProviderModel.model,
15643
+ modelId: chatModelId,
15644
+ cwd,
15645
+ config: contextConfig,
15646
+ estimatedTokens: rollingView.estimate.tokens,
15647
+ contextWindow: rollingView.contextWindow
15648
+ });
15649
+ latestState = compaction.state;
15650
+ logger11.info("context_rolling_compaction", {
15651
+ sessionId,
15652
+ round,
15653
+ tier: compaction.state.tier,
15654
+ usedFallback: compaction.usedFallback,
15655
+ coveredMessageCount: compaction.state.coveredMessageCount
15656
+ });
15393
15657
  }
15394
- const compaction = await compactSessionContext({
15395
- sessionId,
15396
- coveredMessages: rollingView.coveredMessages,
15397
- previousState: latestState,
15398
- model: resolvedProviderModel.model,
15399
- modelId: chatModelId,
15400
- cwd,
15401
- config: contextConfig,
15402
- estimatedTokens: rollingView.estimate.tokens,
15403
- contextWindow: rollingView.contextWindow
15404
- });
15405
- logger11.info("context_rolling_compaction", {
15406
- sessionId,
15407
- tier: compaction.state.tier,
15408
- usedFallback: compaction.usedFallback,
15409
- coveredMessageCount: compaction.state.coveredMessageCount
15410
- });
15411
15658
  } catch (error) {
15412
15659
  logger11.error("context_rolling_compaction_failed", {
15413
15660
  sessionId,
@@ -15624,6 +15871,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15624
15871
  contextWindow: view.contextWindow,
15625
15872
  preserveRecentTokens: view.preserveRecentTokens,
15626
15873
  originalInputTokens: estimateStructuralTokens(providerInputMessages) + turnAssembler.fixedInputTokens,
15874
+ promptCacheActive: cacheActive,
15627
15875
  assemblyAbortSignal: requestAbortSignal,
15628
15876
  turnKey: [...validatedMessages].reverse().find((message) => message.role === "user")?.id
15629
15877
  },
@@ -15689,7 +15937,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15689
15937
  }, 500);
15690
15938
  }
15691
15939
  }
15692
- var logger11, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, MAX_COMPACTION_ROUNDS = 1, rollingCompactionInFlight;
15940
+ var logger11, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, MAX_COMPACTION_ROUNDS = 1, MAX_ROLLING_COMPACTION_ROUNDS = 4, rollingCompactionInFlight;
15693
15941
  var init_chat_stream = __esm(() => {
15694
15942
  init_src2();
15695
15943
  init_src();
@@ -15751,11 +15999,7 @@ async function assembleStoredSessionProviderTurn({
15751
15999
  messages,
15752
16000
  cwd: session.cwd
15753
16001
  });
15754
- const environmentContext = messages.some((message) => message.role === "assistant") ? await buildWorkspaceContextDelta({
15755
- cwd: session.cwd,
15756
- sessionId: session.id,
15757
- relatedPaths: collectRelatedWorkspacePaths(messages)
15758
- }) : await buildWorkspaceContext({
16002
+ const environmentContext = await buildWorkspaceContext({
15759
16003
  cwd: session.cwd,
15760
16004
  sessionId: session.id,
15761
16005
  relatedPaths: collectRelatedWorkspacePaths(messages)
@@ -15808,6 +16052,7 @@ async function assembleStoredSessionProviderTurn({
15808
16052
  endpointLimitTokens,
15809
16053
  reservedOutputTokens,
15810
16054
  cacheActive: resolvedProviderModel.supportsPromptCaching,
16055
+ rollingCompaction: resolvedProviderModel.rollingCompaction ?? !resolvedProviderModel.supportsPromptCaching,
15811
16056
  fixedInputTokens: baseAssembler.fixedInputTokens
15812
16057
  });
15813
16058
  const assembler = new ProviderTurnAssembler({
@@ -15820,7 +16065,10 @@ async function assembleStoredSessionProviderTurn({
15820
16065
  });
15821
16066
  const modelMessages = await convertToModelMessages(normalizeProviderMessages(view.providerMessages), { tools });
15822
16067
  const artifactized = await artifactizeLargeToolOutputs(modelMessages, {
15823
- signal: abortSignal
16068
+ signal: abortSignal,
16069
+ ...resolveToolOutputArtifactThresholds({
16070
+ promptCacheActive: resolvedProviderModel.supportsPromptCaching
16071
+ })
15824
16072
  });
15825
16073
  const assembled = assembler.assembleModelMessages(artifactized.messages, { preserveRecentTokens: view.preserveRecentTokens });
15826
16074
  return { view, assembled };