@dreb/coding-agent 2.46.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.
- package/README.md +4 -2
- package/dist/core/agent-session.d.ts +22 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +109 -20
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +7 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +10 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +35 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/k3-context-tier.d.ts +67 -0
- package/dist/core/k3-context-tier.d.ts.map +1 -0
- package/dist/core/k3-context-tier.js +76 -0
- package/dist/core/k3-context-tier.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +6 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +38 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/tools/ask-user.d.ts +45 -0
- package/dist/core/tools/ask-user.d.ts.map +1 -0
- package/dist/core/tools/ask-user.js +210 -0
- package/dist/core/tools/ask-user.js.map +1 -0
- package/dist/core/tools/index.d.ts +11 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +8 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/modes/interactive/components/ask-user.d.ts +59 -0
- package/dist/modes/interactive/components/ask-user.d.ts.map +1 -0
- package/dist/modes/interactive/components/ask-user.js +240 -0
- package/dist/modes/interactive/components/ask-user.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +12 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +99 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +16 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +112 -59
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +24 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/dashboard.md +1 -1
- package/docs/extensions.md +33 -3
- package/docs/providers.md +1 -0
- package/docs/rpc.md +46 -6
- package/docs/session.md +1 -1
- package/package.json +1 -1
|
@@ -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";
|
|
@@ -883,7 +884,12 @@ export class AgentSession {
|
|
|
883
884
|
}
|
|
884
885
|
/** Emit extension events based on agent events */
|
|
885
886
|
async _emitExtensionEvent(event) {
|
|
886
|
-
|
|
887
|
+
// The runner is created unconditionally (so built-in tools like ask_user
|
|
888
|
+
// always have a UI context). When no extensions are loaded there are no
|
|
889
|
+
// handlers to invoke — return synchronously to avoid inserting an extra
|
|
890
|
+
// await tick per event, which would otherwise delay the final agent_end
|
|
891
|
+
// emission past when prompt() resolves.
|
|
892
|
+
if (!this._extensionRunner || !this._extensionRunner.hasExtensions)
|
|
887
893
|
return;
|
|
888
894
|
if (event.type === "agent_start") {
|
|
889
895
|
this._turnIndex = 0;
|
|
@@ -1692,7 +1698,7 @@ export class AgentSession {
|
|
|
1692
1698
|
}
|
|
1693
1699
|
const previousModel = this.model;
|
|
1694
1700
|
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1695
|
-
this.agent.setModel(model);
|
|
1701
|
+
this.agent.setModel(this._applyContextTier(model));
|
|
1696
1702
|
this._refreshThinkingDisplay(model);
|
|
1697
1703
|
this.sessionManager.appendModelChange(model.provider, model.id);
|
|
1698
1704
|
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
|
@@ -1755,7 +1761,7 @@ export class AgentSession {
|
|
|
1755
1761
|
const next = scopedModels[nextIndex];
|
|
1756
1762
|
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
|
|
1757
1763
|
// Apply model
|
|
1758
|
-
this.agent.setModel(next.model);
|
|
1764
|
+
this.agent.setModel(this._applyContextTier(next.model));
|
|
1759
1765
|
this._refreshThinkingDisplay(next.model);
|
|
1760
1766
|
this.sessionManager.appendModelChange(next.model.provider, next.model.id);
|
|
1761
1767
|
this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
|
|
@@ -1785,7 +1791,7 @@ export class AgentSession {
|
|
|
1785
1791
|
throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
|
|
1786
1792
|
}
|
|
1787
1793
|
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1788
|
-
this.agent.setModel(nextModel);
|
|
1794
|
+
this.agent.setModel(this._applyContextTier(nextModel));
|
|
1789
1795
|
this._refreshThinkingDisplay(nextModel);
|
|
1790
1796
|
this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
|
|
1791
1797
|
this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
|
|
@@ -1974,6 +1980,11 @@ export class AgentSession {
|
|
|
1974
1980
|
const newEntries = this.sessionManager.getEntries();
|
|
1975
1981
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1976
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
|
+
}
|
|
1977
1988
|
// Get the saved compaction entry for the extension event
|
|
1978
1989
|
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
1979
1990
|
if (this._extensionRunner && savedCompactionEntry) {
|
|
@@ -2021,12 +2032,10 @@ export class AgentSession {
|
|
|
2021
2032
|
*/
|
|
2022
2033
|
async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
|
|
2023
2034
|
const settings = this.settingsManager.getCompactionSettings();
|
|
2024
|
-
if (!settings.enabled)
|
|
2025
|
-
return;
|
|
2026
2035
|
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
2027
2036
|
if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
|
|
2028
2037
|
return;
|
|
2029
|
-
|
|
2038
|
+
let contextWindow = this.model?.contextWindow ?? 0;
|
|
2030
2039
|
// Skip overflow check if the message came from a different model.
|
|
2031
2040
|
// This handles the case where user switched from a smaller-context model (e.g. opus)
|
|
2032
2041
|
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
|
@@ -2042,6 +2051,23 @@ export class AgentSession {
|
|
|
2042
2051
|
}
|
|
2043
2052
|
// Case 1: Overflow - LLM returned context overflow error
|
|
2044
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;
|
|
2045
2071
|
if (this._overflowRecoveryAttempted) {
|
|
2046
2072
|
this._emit({
|
|
2047
2073
|
type: "auto_compaction_end",
|
|
@@ -2055,10 +2081,7 @@ export class AgentSession {
|
|
|
2055
2081
|
this._overflowRecoveryAttempted = true;
|
|
2056
2082
|
// Remove the error message from agent state (it IS saved to session for history,
|
|
2057
2083
|
// but we don't want it in context for the retry)
|
|
2058
|
-
|
|
2059
|
-
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
2060
|
-
this.agent.replaceMessages(messages.slice(0, -1));
|
|
2061
|
-
}
|
|
2084
|
+
this._removeLastAssistantMessage();
|
|
2062
2085
|
await this._runAutoCompaction("overflow", true);
|
|
2063
2086
|
return;
|
|
2064
2087
|
}
|
|
@@ -2085,10 +2108,65 @@ export class AgentSession {
|
|
|
2085
2108
|
else {
|
|
2086
2109
|
contextTokens = calculateContextTokens(assistantMessage.usage);
|
|
2087
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;
|
|
2088
2130
|
if (shouldCompact(contextTokens, contextWindow, settings)) {
|
|
2089
2131
|
await this._runAutoCompaction("threshold", false);
|
|
2090
2132
|
}
|
|
2091
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
|
+
}
|
|
2092
2170
|
/**
|
|
2093
2171
|
* Internal: Run auto-compaction with events.
|
|
2094
2172
|
*/
|
|
@@ -2158,6 +2236,11 @@ export class AgentSession {
|
|
|
2158
2236
|
const newEntries = this.sessionManager.getEntries();
|
|
2159
2237
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
2160
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
|
+
}
|
|
2161
2244
|
// Get the saved compaction entry for the extension event
|
|
2162
2245
|
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
2163
2246
|
if (this._extensionRunner && savedCompactionEntry) {
|
|
@@ -2300,7 +2383,7 @@ export class AgentSession {
|
|
|
2300
2383
|
if (!refreshedModel || refreshedModel === currentModel) {
|
|
2301
2384
|
return;
|
|
2302
2385
|
}
|
|
2303
|
-
this.agent.setModel(refreshedModel);
|
|
2386
|
+
this.agent.setModel(this._applyContextTier(refreshedModel));
|
|
2304
2387
|
this._refreshThinkingDisplay(refreshedModel);
|
|
2305
2388
|
}
|
|
2306
2389
|
_bindExtensionCore(runner) {
|
|
@@ -2443,9 +2526,15 @@ export class AgentSession {
|
|
|
2443
2526
|
const wrappedExtensionTools = this._extensionRunner
|
|
2444
2527
|
? wrapRegisteredTools(allCustomTools, this._extensionRunner)
|
|
2445
2528
|
: [];
|
|
2529
|
+
// Give base tools a per-execution extension context so built-ins like
|
|
2530
|
+
// ask_user can reach ctx.ui. createContext() snapshots hasUI at call time,
|
|
2531
|
+
// so print/RPC-without-host modes degrade to the no-op UI (hasUI === false).
|
|
2532
|
+
const baseToolCtxFactory = this._extensionRunner
|
|
2533
|
+
? () => this._extensionRunner.createContext()
|
|
2534
|
+
: undefined;
|
|
2446
2535
|
const toolRegistry = new Map(Array.from(this._baseToolDefinitions.values()).map((definition) => [
|
|
2447
2536
|
definition.name,
|
|
2448
|
-
wrapToolDefinition(definition),
|
|
2537
|
+
wrapToolDefinition(definition, baseToolCtxFactory),
|
|
2449
2538
|
]));
|
|
2450
2539
|
for (const tool of wrappedExtensionTools) {
|
|
2451
2540
|
toolRegistry.set(tool.name, tool);
|
|
@@ -2544,12 +2633,11 @@ export class AgentSession {
|
|
|
2544
2633
|
extensionsResult.runtime.flagValues.set(name, value);
|
|
2545
2634
|
}
|
|
2546
2635
|
}
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
: undefined;
|
|
2636
|
+
// The runner also owns the cross-surface UI context used by built-in
|
|
2637
|
+
// tools such as ask_user. Create it even when no third-party extensions
|
|
2638
|
+
// are loaded; otherwise ordinary TUI/Dashboard sessions give base tools
|
|
2639
|
+
// no ctx.ui and ask_user can never open its dialog.
|
|
2640
|
+
this._extensionRunner = new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry);
|
|
2553
2641
|
if (this._extensionRunnerRef) {
|
|
2554
2642
|
this._extensionRunnerRef.current = this._extensionRunner;
|
|
2555
2643
|
}
|
|
@@ -2572,6 +2660,7 @@ export class AgentSession {
|
|
|
2572
2660
|
"subagent",
|
|
2573
2661
|
"wait",
|
|
2574
2662
|
"search",
|
|
2663
|
+
"ask_user",
|
|
2575
2664
|
"skill",
|
|
2576
2665
|
"tasks_update",
|
|
2577
2666
|
"suggest_next",
|
|
@@ -2864,7 +2953,7 @@ export class AgentSession {
|
|
|
2864
2953
|
const availableModels = await this._modelRegistry.getAvailable();
|
|
2865
2954
|
const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
|
|
2866
2955
|
if (match) {
|
|
2867
|
-
this.agent.setModel(match);
|
|
2956
|
+
this.agent.setModel(this._applyContextTier(match));
|
|
2868
2957
|
this._refreshThinkingDisplay(match);
|
|
2869
2958
|
await this._emitModelSelect(match, previousModel, "restore");
|
|
2870
2959
|
}
|