@dreb/coding-agent 2.47.0 → 2.48.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.
@@ -29,6 +29,7 @@ import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
29
29
  import { checkScriptContent, extractScriptPaths, isForbiddenCommand } from "./forbidden-commands.js";
30
30
  import { getGitRepoState, getGitStatusMetadata } from "./git-repo-state.js";
31
31
  import { findGitRoot } from "./git-root.js";
32
+ import { deriveK3ContextTierModel, isK3256kTier, K3_1M_CONTEXT_WINDOW, K3_256K_CONTEXT_WINDOW, K3_UPGRADE_CUTOFF_TOKENS, shouldUpgradeK3Tier, } from "./k3-context-tier.js";
32
33
  import { log } from "./logger.js";
33
34
  import { computeNestedContextBlock } from "./nested-context.js";
34
35
  import { PerformanceTracker } from "./performance-tracker.js";
@@ -1697,7 +1698,7 @@ export class AgentSession {
1697
1698
  }
1698
1699
  const previousModel = this.model;
1699
1700
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1700
- this.agent.setModel(model);
1701
+ this.agent.setModel(this._applyContextTier(model));
1701
1702
  this._refreshThinkingDisplay(model);
1702
1703
  this.sessionManager.appendModelChange(model.provider, model.id);
1703
1704
  this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
@@ -1760,7 +1761,7 @@ export class AgentSession {
1760
1761
  const next = scopedModels[nextIndex];
1761
1762
  const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1762
1763
  // Apply model
1763
- this.agent.setModel(next.model);
1764
+ this.agent.setModel(this._applyContextTier(next.model));
1764
1765
  this._refreshThinkingDisplay(next.model);
1765
1766
  this.sessionManager.appendModelChange(next.model.provider, next.model.id);
1766
1767
  this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
@@ -1790,7 +1791,7 @@ export class AgentSession {
1790
1791
  throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
1791
1792
  }
1792
1793
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1793
- this.agent.setModel(nextModel);
1794
+ this.agent.setModel(this._applyContextTier(nextModel));
1794
1795
  this._refreshThinkingDisplay(nextModel);
1795
1796
  this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
1796
1797
  this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
@@ -1979,6 +1980,11 @@ export class AgentSession {
1979
1980
  const newEntries = this.sessionManager.getEntries();
1980
1981
  const sessionContext = this.sessionManager.buildSessionContext();
1981
1982
  this.agent.replaceMessages(sessionContext.messages);
1983
+ // Re-derive the K3 context tier: the compacted context is small again,
1984
+ // so the session returns to the cheaper 256k wire tier.
1985
+ if (this.model) {
1986
+ this.agent.setModel(this._applyContextTier(this.model));
1987
+ }
1982
1988
  // Get the saved compaction entry for the extension event
1983
1989
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1984
1990
  if (this._extensionRunner && savedCompactionEntry) {
@@ -2026,12 +2032,10 @@ export class AgentSession {
2026
2032
  */
2027
2033
  async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
2028
2034
  const settings = this.settingsManager.getCompactionSettings();
2029
- if (!settings.enabled)
2030
- return;
2031
2035
  // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
2032
2036
  if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
2033
2037
  return;
2034
- const contextWindow = this.model?.contextWindow ?? 0;
2038
+ let contextWindow = this.model?.contextWindow ?? 0;
2035
2039
  // Skip overflow check if the message came from a different model.
2036
2040
  // This handles the case where user switched from a smaller-context model (e.g. opus)
2037
2041
  // to a larger-context model (e.g. codex) - the overflow error from the old model
@@ -2047,6 +2051,23 @@ export class AgentSession {
2047
2051
  }
2048
2052
  // Case 1: Overflow - LLM returned context overflow error
2049
2053
  if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
2054
+ // K3 auto context tier: an overflow in the 256k tier upgrades to the
2055
+ // 1M tier instead of compacting — the Kimi backend grows the prompt
2056
+ // cache seamlessly. This runs even when compaction is disabled since
2057
+ // no context reduction is involved.
2058
+ if (this._tryUpgradeK3ContextTier()) {
2059
+ // Remove the error message from agent state (it IS saved to session
2060
+ // for history, but we don't want it in context for the retry)
2061
+ this._removeLastAssistantMessage();
2062
+ setTimeout(() => {
2063
+ this.agent.continue().catch((err) => {
2064
+ this.warnInSession(`Agent failed to continue after context window upgrade: ${err instanceof Error ? err.message : String(err)}`);
2065
+ });
2066
+ }, 100);
2067
+ return;
2068
+ }
2069
+ if (!settings.enabled)
2070
+ return;
2050
2071
  if (this._overflowRecoveryAttempted) {
2051
2072
  this._emit({
2052
2073
  type: "auto_compaction_end",
@@ -2060,10 +2081,7 @@ export class AgentSession {
2060
2081
  this._overflowRecoveryAttempted = true;
2061
2082
  // Remove the error message from agent state (it IS saved to session for history,
2062
2083
  // but we don't want it in context for the retry)
2063
- const messages = this.agent.state.messages;
2064
- if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2065
- this.agent.replaceMessages(messages.slice(0, -1));
2066
- }
2084
+ this._removeLastAssistantMessage();
2067
2085
  await this._runAutoCompaction("overflow", true);
2068
2086
  return;
2069
2087
  }
@@ -2090,10 +2108,65 @@ export class AgentSession {
2090
2108
  else {
2091
2109
  contextTokens = calculateContextTokens(assistantMessage.usage);
2092
2110
  }
2111
+ // K3 auto context tier: reaching the 256k cutoff upgrades to the 1M tier
2112
+ // instead of compacting. This is model-capability management, not context
2113
+ // reduction, so it applies even when compaction is disabled. The cutoff is
2114
+ // fixed at the default compaction threshold of the 256k window; a
2115
+ // user-lowered compaction threshold takes precedence and effectively
2116
+ // disables the upgrade.
2117
+ if (shouldUpgradeK3Tier(this.model, contextTokens)) {
2118
+ // A user-lowered compaction threshold takes precedence over the
2119
+ // upgrade: if the user's compact point for the 256k window sits below
2120
+ // the default cutoff and is already exceeded, compact instead.
2121
+ const userCompactPoint = K3_256K_CONTEXT_WINDOW - settings.reserveTokens;
2122
+ const userThresholdPreempts = settings.enabled && userCompactPoint < K3_UPGRADE_CUTOFF_TOKENS && contextTokens > userCompactPoint;
2123
+ if (!userThresholdPreempts) {
2124
+ this._tryUpgradeK3ContextTier();
2125
+ contextWindow = this.model?.contextWindow ?? contextWindow;
2126
+ }
2127
+ }
2128
+ if (!settings.enabled)
2129
+ return;
2093
2130
  if (shouldCompact(contextTokens, contextWindow, settings)) {
2094
2131
  await this._runAutoCompaction("threshold", false);
2095
2132
  }
2096
2133
  }
2134
+ /**
2135
+ * Apply the K3 auto context tier to a model being set on the agent. The
2136
+ * user-facing `k3` model runs on the cheaper `k3-256k` wire model ID until
2137
+ * the session context grows past the 256k cutoff; no-op for other models.
2138
+ * See k3-context-tier.ts.
2139
+ */
2140
+ _applyContextTier(model) {
2141
+ return deriveK3ContextTierModel(model, estimateContextTokens(this.agent.state.messages).tokens);
2142
+ }
2143
+ /** Remove the last message from agent state when it is an assistant message. */
2144
+ _removeLastAssistantMessage() {
2145
+ const messages = this.agent.state.messages;
2146
+ if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2147
+ this.agent.replaceMessages(messages.slice(0, -1));
2148
+ }
2149
+ }
2150
+ /**
2151
+ * Upgrade the K3 auto context tier from 256k to 1M. The Kimi backend
2152
+ * upgrades the prompt cache seamlessly, so no context is lost or
2153
+ * compacted. Returns true when the upgrade was applied.
2154
+ */
2155
+ _tryUpgradeK3ContextTier() {
2156
+ const model = this.model;
2157
+ if (!model || !isK3256kTier(model))
2158
+ return false;
2159
+ const upgraded = deriveK3ContextTierModel(model, K3_UPGRADE_CUTOFF_TOKENS + 1);
2160
+ this.agent.setModel(upgraded);
2161
+ this._emit({
2162
+ type: "context_window_upgrade",
2163
+ provider: upgraded.provider,
2164
+ modelId: upgraded.id,
2165
+ fromContextWindow: K3_256K_CONTEXT_WINDOW,
2166
+ toContextWindow: K3_1M_CONTEXT_WINDOW,
2167
+ });
2168
+ return true;
2169
+ }
2097
2170
  /**
2098
2171
  * Internal: Run auto-compaction with events.
2099
2172
  */
@@ -2163,6 +2236,11 @@ export class AgentSession {
2163
2236
  const newEntries = this.sessionManager.getEntries();
2164
2237
  const sessionContext = this.sessionManager.buildSessionContext();
2165
2238
  this.agent.replaceMessages(sessionContext.messages);
2239
+ // Re-derive the K3 context tier: the compacted context is small again,
2240
+ // so the session returns to the cheaper 256k wire tier.
2241
+ if (this.model) {
2242
+ this.agent.setModel(this._applyContextTier(this.model));
2243
+ }
2166
2244
  // Get the saved compaction entry for the extension event
2167
2245
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2168
2246
  if (this._extensionRunner && savedCompactionEntry) {
@@ -2305,7 +2383,7 @@ export class AgentSession {
2305
2383
  if (!refreshedModel || refreshedModel === currentModel) {
2306
2384
  return;
2307
2385
  }
2308
- this.agent.setModel(refreshedModel);
2386
+ this.agent.setModel(this._applyContextTier(refreshedModel));
2309
2387
  this._refreshThinkingDisplay(refreshedModel);
2310
2388
  }
2311
2389
  _bindExtensionCore(runner) {
@@ -2875,7 +2953,7 @@ export class AgentSession {
2875
2953
  const availableModels = await this._modelRegistry.getAvailable();
2876
2954
  const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
2877
2955
  if (match) {
2878
- this.agent.setModel(match);
2956
+ this.agent.setModel(this._applyContextTier(match));
2879
2957
  this._refreshThinkingDisplay(match);
2880
2958
  await this._emitModelSelect(match, previousModel, "restore");
2881
2959
  }