lsd-pi 1.1.9 → 1.2.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/dist/resources/extensions/slash-commands/context.js +15 -8
- package/dist/resources/extensions/slash-commands/index.js +2 -0
- package/dist/resources/extensions/slash-commands/init.js +47 -0
- package/dist/resources/extensions/slash-commands/plan.js +241 -54
- package/dist/resources/extensions/slash-commands/tools.js +47 -21
- package/dist/resources/extensions/subagent/index.js +5 -10
- package/dist/startup-model-validation.d.ts +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +2 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/types.ts +2 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +10 -3
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -2
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +2 -2
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +2 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +2 -0
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/google.js +2 -0
- package/packages/pi-ai/dist/providers/google.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +2 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +2 -1
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +2 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +6 -2
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/amazon-bedrock.ts +11 -4
- package/packages/pi-ai/src/providers/anthropic-shared.ts +5 -2
- package/packages/pi-ai/src/providers/anthropic.ts +2 -2
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +2 -1
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +3 -1
- package/packages/pi-ai/src/providers/google-vertex.ts +3 -1
- package/packages/pi-ai/src/providers/google.ts +3 -1
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +2 -1
- package/packages/pi-ai/src/providers/openai-completions.ts +2 -1
- package/packages/pi-ai/src/providers/openai-responses.ts +2 -1
- package/packages/pi-ai/src/providers/simple-options.ts +5 -3
- package/packages/pi-ai/src/types.ts +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +57 -20
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/classifier-service.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/classifier-service.js +34 -61
- package/packages/pi-coding-agent/dist/core/classifier-service.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.js +3 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader-lsd-md.test.js +59 -7
- package/packages/pi-coding-agent/dist/core/resource-loader-lsd-md.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +4 -4
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +19 -20
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.js +80 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +15 -5
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +31 -5
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +28 -68
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +44 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +41 -5
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +62 -19
- package/packages/pi-coding-agent/src/core/classifier-service.ts +35 -63
- package/packages/pi-coding-agent/src/core/compaction/utils.ts +3 -1
- package/packages/pi-coding-agent/src/core/resource-loader-lsd-md.test.ts +67 -7
- package/packages/pi-coding-agent/src/core/resource-loader.ts +4 -4
- package/packages/pi-coding-agent/src/core/sdk.test.ts +100 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +24 -21
- package/packages/pi-coding-agent/src/core/settings-manager.ts +42 -8
- package/packages/pi-coding-agent/src/core/system-prompt.ts +39 -82
- package/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts +6 -0
- package/packages/pi-coding-agent/src/core/tools/bash-interceptor.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +26 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +53 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/thinking-selector.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +41 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +50 -7
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +3 -1
- package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +2 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/slash-commands/context.ts +15 -8
- package/src/resources/extensions/slash-commands/index.ts +2 -0
- package/src/resources/extensions/slash-commands/init.ts +55 -0
- package/src/resources/extensions/slash-commands/plan.ts +277 -55
- package/src/resources/extensions/slash-commands/tools.ts +47 -21
- package/src/resources/extensions/subagent/index.ts +5 -10
|
@@ -25,7 +25,7 @@ import type {
|
|
|
25
25
|
ThinkingLevel,
|
|
26
26
|
} from "@gsd/pi-agent-core";
|
|
27
27
|
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@gsd/pi-ai";
|
|
28
|
-
import { modelsAreEqual, resetApiProviders, supportsXhigh } from "@gsd/pi-ai";
|
|
28
|
+
import { modelsAreEqual, resetApiProviders, supportsAdaptiveThinking, supportsXhigh } from "@gsd/pi-ai";
|
|
29
29
|
import { Type } from "@sinclair/typebox";
|
|
30
30
|
import { getDocsPath } from "../config.js";
|
|
31
31
|
import { getErrorMessage } from "../utils/error.js";
|
|
@@ -237,6 +237,12 @@ const THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medium", "hi
|
|
|
237
237
|
/** Thinking levels including xhigh (for supported models) */
|
|
238
238
|
const THINKING_LEVELS_WITH_XHIGH: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
239
239
|
|
|
240
|
+
/** Thinking levels for models supporting adaptive thinking (Claude Opus/Sonnet 4.6+) */
|
|
241
|
+
const THINKING_LEVELS_WITH_ADAPTIVE: ThinkingLevel[] = ["off", "adaptive", "minimal", "low", "medium", "high"];
|
|
242
|
+
|
|
243
|
+
/** Thinking levels for models supporting both adaptive and xhigh */
|
|
244
|
+
const THINKING_LEVELS_WITH_ADAPTIVE_AND_XHIGH: ThinkingLevel[] = ["off", "adaptive", "minimal", "low", "medium", "high", "xhigh"];
|
|
245
|
+
|
|
240
246
|
// ============================================================================
|
|
241
247
|
// AgentSession Class
|
|
242
248
|
// ============================================================================
|
|
@@ -257,6 +263,8 @@ export class AgentSession {
|
|
|
257
263
|
private _steeringMessages: string[] = [];
|
|
258
264
|
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
|
259
265
|
private _followUpMessages: string[] = [];
|
|
266
|
+
/** Tracks texts of extension-injected steer messages that should be hidden from UI. */
|
|
267
|
+
private _hiddenSteeringTexts = new Set<string>();
|
|
260
268
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
261
269
|
private _pendingNextTurnMessages: CustomMessage[] = [];
|
|
262
270
|
|
|
@@ -367,10 +375,16 @@ export class AgentSession {
|
|
|
367
375
|
|
|
368
376
|
this._buildRuntime({
|
|
369
377
|
activeToolNames: this._initialActiveToolNames,
|
|
370
|
-
includeAllExtensionTools:
|
|
378
|
+
includeAllExtensionTools: this._shouldIncludeAllExtensionTools(),
|
|
371
379
|
});
|
|
372
380
|
}
|
|
373
381
|
|
|
382
|
+
private _shouldIncludeAllExtensionTools(): boolean {
|
|
383
|
+
// Tool profiles now provide curated active sets. Do not auto-activate every
|
|
384
|
+
// extension tool at startup, or the saved profile gets effectively ignored.
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
374
388
|
private async _waitForAutomatedFollowUps(): Promise<void> {
|
|
375
389
|
while (this._retryHandler.isRetrying || this._compactionOrchestrator.hasPendingAutoCompaction) {
|
|
376
390
|
await this._retryHandler.waitForRetry();
|
|
@@ -1118,10 +1132,11 @@ export class AgentSession {
|
|
|
1118
1132
|
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.",
|
|
1119
1133
|
);
|
|
1120
1134
|
}
|
|
1135
|
+
const isExtensionSteer = options.streamingBehavior === "steer" && options.source === "extension";
|
|
1121
1136
|
if (options.streamingBehavior === "followUp") {
|
|
1122
1137
|
await this._queueFollowUp(expandedText, currentImages);
|
|
1123
1138
|
} else {
|
|
1124
|
-
await this._queueSteer(expandedText, currentImages);
|
|
1139
|
+
await this._queueSteer(expandedText, currentImages, isExtensionSteer);
|
|
1125
1140
|
}
|
|
1126
1141
|
return;
|
|
1127
1142
|
}
|
|
@@ -1181,6 +1196,10 @@ export class AgentSession {
|
|
|
1181
1196
|
if (currentImages) {
|
|
1182
1197
|
userContent.push(...currentImages);
|
|
1183
1198
|
}
|
|
1199
|
+
// Track extension-injected messages so the UI can hide them
|
|
1200
|
+
if (options?.source === "extension") {
|
|
1201
|
+
this._hiddenSteeringTexts.add(expandedText);
|
|
1202
|
+
}
|
|
1184
1203
|
messages.push({
|
|
1185
1204
|
role: "user",
|
|
1186
1205
|
content: userContent,
|
|
@@ -1422,9 +1441,13 @@ export class AgentSession {
|
|
|
1422
1441
|
|
|
1423
1442
|
/**
|
|
1424
1443
|
* Internal: Queue a steering message (already expanded, no extension command check).
|
|
1444
|
+
* @param hidden If true, the message will be hidden from the UI (used for extension-injected steers).
|
|
1425
1445
|
*/
|
|
1426
|
-
private async _queueSteer(text: string, images?: ImageContent[]): Promise<void> {
|
|
1446
|
+
private async _queueSteer(text: string, images?: ImageContent[], hidden?: boolean): Promise<void> {
|
|
1427
1447
|
this._steeringMessages.push(text);
|
|
1448
|
+
if (hidden) {
|
|
1449
|
+
this._hiddenSteeringTexts.add(text);
|
|
1450
|
+
}
|
|
1428
1451
|
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
1429
1452
|
if (images) {
|
|
1430
1453
|
content.push(...images);
|
|
@@ -1617,6 +1640,11 @@ export class AgentSession {
|
|
|
1617
1640
|
return this._steeringMessages.length + this._followUpMessages.length;
|
|
1618
1641
|
}
|
|
1619
1642
|
|
|
1643
|
+
/** Returns true if the given message text was injected by an extension and should be hidden from the UI. */
|
|
1644
|
+
isHiddenSteeringMessage(text: string): boolean {
|
|
1645
|
+
return this._hiddenSteeringTexts.has(text);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1620
1648
|
/** Get pending steering messages (read-only) */
|
|
1621
1649
|
getSteeringMessages(): readonly string[] {
|
|
1622
1650
|
return this._steeringMessages;
|
|
@@ -1687,6 +1715,7 @@ export class AgentSession {
|
|
|
1687
1715
|
this._steeringMessages = [];
|
|
1688
1716
|
this._followUpMessages = [];
|
|
1689
1717
|
this._pendingNextTurnMessages = [];
|
|
1718
|
+
this._hiddenSteeringTexts = new Set();
|
|
1690
1719
|
|
|
1691
1720
|
this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
|
|
1692
1721
|
|
|
@@ -1697,7 +1726,7 @@ export class AgentSession {
|
|
|
1697
1726
|
if (this._cwd !== previousCwd) {
|
|
1698
1727
|
this._buildRuntime({
|
|
1699
1728
|
activeToolNames: this.getActiveToolNames(),
|
|
1700
|
-
includeAllExtensionTools:
|
|
1729
|
+
includeAllExtensionTools: this._shouldIncludeAllExtensionTools(),
|
|
1701
1730
|
});
|
|
1702
1731
|
}
|
|
1703
1732
|
|
|
@@ -1775,7 +1804,7 @@ export class AgentSession {
|
|
|
1775
1804
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
1776
1805
|
}
|
|
1777
1806
|
|
|
1778
|
-
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1807
|
+
const thinkingLevel = this._getThinkingLevelForModelSwitch(model);
|
|
1779
1808
|
await this._applyModelChange(model, thinkingLevel, "set", options);
|
|
1780
1809
|
}
|
|
1781
1810
|
|
|
@@ -1812,7 +1841,7 @@ export class AgentSession {
|
|
|
1812
1841
|
|
|
1813
1842
|
// Explicit scoped model thinking level overrides current session level;
|
|
1814
1843
|
// undefined scoped model thinking level inherits the current session preference.
|
|
1815
|
-
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
|
|
1844
|
+
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.model, next.thinkingLevel);
|
|
1816
1845
|
await this._applyModelChange(next.model, thinkingLevel, "cycle", options);
|
|
1817
1846
|
|
|
1818
1847
|
return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
|
|
@@ -1830,7 +1859,7 @@ export class AgentSession {
|
|
|
1830
1859
|
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
|
1831
1860
|
const nextModel = availableModels[nextIndex];
|
|
1832
1861
|
|
|
1833
|
-
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1862
|
+
const thinkingLevel = this._getThinkingLevelForModelSwitch(nextModel);
|
|
1834
1863
|
await this._applyModelChange(nextModel, thinkingLevel, "cycle", options);
|
|
1835
1864
|
|
|
1836
1865
|
return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
|
|
@@ -1885,7 +1914,18 @@ export class AgentSession {
|
|
|
1885
1914
|
*/
|
|
1886
1915
|
getAvailableThinkingLevels(): ThinkingLevel[] {
|
|
1887
1916
|
if (!this.supportsThinking()) return ["off"];
|
|
1888
|
-
|
|
1917
|
+
const adaptive = this.supportsAdaptiveThinking();
|
|
1918
|
+
const xhigh = this.supportsXhighThinking();
|
|
1919
|
+
if (adaptive && xhigh) return THINKING_LEVELS_WITH_ADAPTIVE_AND_XHIGH;
|
|
1920
|
+
if (adaptive) return THINKING_LEVELS_WITH_ADAPTIVE;
|
|
1921
|
+
return xhigh ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
/**
|
|
1925
|
+
* Check if current model supports adaptive thinking (Claude Opus/Sonnet 4.6+).
|
|
1926
|
+
*/
|
|
1927
|
+
supportsAdaptiveThinking(): boolean {
|
|
1928
|
+
return this.model ? supportsAdaptiveThinking(this.model.id) : false;
|
|
1889
1929
|
}
|
|
1890
1930
|
|
|
1891
1931
|
/**
|
|
@@ -1902,18 +1942,26 @@ export class AgentSession {
|
|
|
1902
1942
|
return !!this.model?.reasoning;
|
|
1903
1943
|
}
|
|
1904
1944
|
|
|
1905
|
-
private _getThinkingLevelForModelSwitch(explicitLevel?: ThinkingLevel): ThinkingLevel {
|
|
1945
|
+
private _getThinkingLevelForModelSwitch(targetModel: Model<any>, explicitLevel?: ThinkingLevel): ThinkingLevel {
|
|
1906
1946
|
if (explicitLevel !== undefined) {
|
|
1907
1947
|
return explicitLevel;
|
|
1908
1948
|
}
|
|
1909
|
-
if (
|
|
1949
|
+
if (
|
|
1950
|
+
targetModel.provider === "anthropic" &&
|
|
1951
|
+
targetModel.reasoning === true &&
|
|
1952
|
+
this.settingsManager.getAnthropicAdaptiveByDefault() &&
|
|
1953
|
+
supportsAdaptiveThinking(targetModel.id)
|
|
1954
|
+
) {
|
|
1955
|
+
return "adaptive";
|
|
1956
|
+
}
|
|
1957
|
+
if (!targetModel.reasoning) {
|
|
1910
1958
|
return this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
|
|
1911
1959
|
}
|
|
1912
1960
|
return this.thinkingLevel;
|
|
1913
1961
|
}
|
|
1914
1962
|
|
|
1915
1963
|
private _clampThinkingLevel(level: ThinkingLevel, availableLevels: ThinkingLevel[]): ThinkingLevel {
|
|
1916
|
-
const ordered =
|
|
1964
|
+
const ordered = THINKING_LEVELS_WITH_ADAPTIVE_AND_XHIGH;
|
|
1917
1965
|
const available = new Set(availableLevels);
|
|
1918
1966
|
const requestedIndex = ordered.indexOf(level);
|
|
1919
1967
|
if (requestedIndex === -1) {
|
|
@@ -2294,12 +2342,6 @@ export class AgentSession {
|
|
|
2294
2342
|
for (const tool of wrappedExtensionTools) {
|
|
2295
2343
|
nextActiveToolNames.push(tool.name);
|
|
2296
2344
|
}
|
|
2297
|
-
} else if (!options?.activeToolNames) {
|
|
2298
|
-
for (const toolName of this._toolRegistry.keys()) {
|
|
2299
|
-
if (!previousRegistryNames.has(toolName)) {
|
|
2300
|
-
nextActiveToolNames.push(toolName);
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
2345
|
}
|
|
2304
2346
|
|
|
2305
2347
|
this.setActiveToolsByName([...new Set(nextActiveToolNames)]);
|
|
@@ -2375,7 +2417,7 @@ export class AgentSession {
|
|
|
2375
2417
|
this._buildRuntime({
|
|
2376
2418
|
activeToolNames: this.getActiveToolNames(),
|
|
2377
2419
|
flagValues: previousFlagValues,
|
|
2378
|
-
includeAllExtensionTools:
|
|
2420
|
+
includeAllExtensionTools: this._shouldIncludeAllExtensionTools(),
|
|
2379
2421
|
});
|
|
2380
2422
|
|
|
2381
2423
|
const hasBindings =
|
|
@@ -2585,6 +2627,7 @@ export class AgentSession {
|
|
|
2585
2627
|
this._steeringMessages = [];
|
|
2586
2628
|
this._followUpMessages = [];
|
|
2587
2629
|
this._pendingNextTurnMessages = [];
|
|
2630
|
+
this._hiddenSteeringTexts = new Set();
|
|
2588
2631
|
|
|
2589
2632
|
// Set new session
|
|
2590
2633
|
this.sessionManager.setSessionFile(sessionPath);
|
|
@@ -110,6 +110,39 @@ const BUILT_IN_BASH_ALLOW_PATTERNS = [
|
|
|
110
110
|
"more *",
|
|
111
111
|
] as const;
|
|
112
112
|
|
|
113
|
+
const CLASSIFIER_SYSTEM_PROMPT = `You are a security classifier for an autonomous coding agent. Your job is to decide ALLOW or DENY for a pending tool call.
|
|
114
|
+
|
|
115
|
+
DEFAULT: ALLOW. The user enabled auto mode because they trust the agent. Only deny clear security violations.
|
|
116
|
+
|
|
117
|
+
ALWAYS ALLOW:
|
|
118
|
+
- Read-only commands: find, grep, ls, cat, head, tail, wc, stat, file, du, df, echo, pwd, env, which, sort, awk, sed -n
|
|
119
|
+
- Git read operations: status, log, diff, show, branch, blame, remote
|
|
120
|
+
- Build/test/lint: npm run *, npx *, tsc, eslint, jest, vitest, cargo, make
|
|
121
|
+
- Writing or editing files inside the project directory
|
|
122
|
+
- Installing packages: npm install, pip install, cargo add
|
|
123
|
+
- Operations clearly implied by the user's recent messages
|
|
124
|
+
|
|
125
|
+
DENY only these specific violations:
|
|
126
|
+
- Deleting files with rm, unlink, rmdir (unless user explicitly asked to delete/remove)
|
|
127
|
+
- Commands targeting system paths outside the project (/etc, /usr, /bin, /System, ~/.ssh)
|
|
128
|
+
- sudo, su, chmod, chown on system paths
|
|
129
|
+
- curl/wget piped directly to sh/bash/zsh
|
|
130
|
+
- curl/wget sending local files or env vars to external hosts (e.g. -d @~/.ssh/id_rsa, -d "$(env)")
|
|
131
|
+
- ssh, scp, sftp — remote shell/file access is out of scope
|
|
132
|
+
- nc/ncat in listen/server mode (-l flag)
|
|
133
|
+
- kill -9, killall
|
|
134
|
+
- crontab modifications
|
|
135
|
+
- dd, mkfs — disk/filesystem writes
|
|
136
|
+
- sed -i — in-place editing that can silently corrupt files
|
|
137
|
+
- npm publish, pip publish, twine upload
|
|
138
|
+
- git push --force / git push -f
|
|
139
|
+
- Accessing credential files unrelated to the task (~/.aws, ~/.ssh/id_rsa, /etc/passwd)
|
|
140
|
+
- Commands that clearly contradict the user's stated intent
|
|
141
|
+
|
|
142
|
+
Use the user's recent messages to judge intent. If the user asked for something and the tool call implements it, ALLOW.
|
|
143
|
+
|
|
144
|
+
Output exactly one word: ALLOW or DENY.`;
|
|
145
|
+
|
|
113
146
|
export interface ClassifierContext {
|
|
114
147
|
userMessages: string[];
|
|
115
148
|
projectInstructions?: string;
|
|
@@ -259,46 +292,13 @@ export class ClassifierService {
|
|
|
259
292
|
argsJson: string,
|
|
260
293
|
context: ClassifierContext,
|
|
261
294
|
): Promise<boolean> {
|
|
262
|
-
const systemPrompt = `You are a security classifier for an autonomous coding agent. Your job is to decide ALLOW or DENY for a pending tool call.
|
|
263
|
-
|
|
264
|
-
DEFAULT: ALLOW. The user enabled auto mode because they trust the agent. Only deny clear security violations.
|
|
265
|
-
|
|
266
|
-
ALWAYS ALLOW:
|
|
267
|
-
- Any read-only operation: find, grep, ls, cat, head, tail, wc, stat, file, du, df, echo, pwd, env, which, sort, awk, sed -n
|
|
268
|
-
- Any git read operation: git status, git log, git diff, git show, git branch, git blame, git remote
|
|
269
|
-
- Build/test/lint: npm run *, npx *, tsc, eslint, jest, vitest, cargo, make
|
|
270
|
-
- Writing or editing files inside the project directory
|
|
271
|
-
- Installing packages: npm install, pip install, cargo add
|
|
272
|
-
- Operations clearly implied by the user's recent messages
|
|
273
|
-
|
|
274
|
-
DENY only these specific violations:
|
|
275
|
-
- Deleting files with rm, unlink, rmdir (unless user explicitly said "delete" or "remove" that file)
|
|
276
|
-
- Commands outside the project directory targeting system paths (/etc, /usr, /bin, /System, ~/.ssh)
|
|
277
|
-
- sudo, su, chmod/chown on system paths
|
|
278
|
-
- curl/wget piped directly to sh/bash (arbitrary code execution)
|
|
279
|
-
- curl/wget sending local files or env vars to external hosts (e.g. -d @~/.ssh/id_rsa, -d "$(env)")
|
|
280
|
-
- ssh, scp, sftp — remote shell/file access is out of scope for a coding agent
|
|
281
|
-
- nc/ncat in listen/server mode (-l flag) — reverse shell risk
|
|
282
|
-
- kill -9, killall — terminating arbitrary processes
|
|
283
|
-
- crontab — modifying scheduled jobs
|
|
284
|
-
- dd, mkfs — disk/filesystem writes
|
|
285
|
-
- sed -i — in-place file editing that can silently corrupt files; agent should use proper edit tools
|
|
286
|
-
- npm publish, pip publish, twine upload — publishing packages requires explicit user action
|
|
287
|
-
- git push --force / git push -f — destructive remote history rewrite
|
|
288
|
-
- Accessing credential files unrelated to the current task (~/.aws, ~/.ssh/id_rsa, /etc/passwd)
|
|
289
|
-
- Commands that clearly contradict the user's stated intent
|
|
290
|
-
|
|
291
|
-
Use the user's recent messages to judge intent. If the user asked for something and the tool call implements it, ALLOW.
|
|
292
|
-
|
|
293
|
-
Output exactly one word: ALLOW or DENY.`;
|
|
294
|
-
|
|
295
295
|
const userPrompt = this.buildUserPrompt(toolName, argsJson, context);
|
|
296
296
|
|
|
297
297
|
return new Promise<boolean>((resolve, reject) => {
|
|
298
298
|
const data = JSON.stringify({
|
|
299
299
|
model: modelId,
|
|
300
300
|
max_tokens: 10,
|
|
301
|
-
system:
|
|
301
|
+
system: CLASSIFIER_SYSTEM_PROMPT,
|
|
302
302
|
messages: [{ role: "user", content: userPrompt }],
|
|
303
303
|
});
|
|
304
304
|
const headers: Record<string, string | number> = {
|
|
@@ -349,39 +349,11 @@ Output exactly one word: ALLOW or DENY.`;
|
|
|
349
349
|
argsJson: string,
|
|
350
350
|
context: ClassifierContext,
|
|
351
351
|
): Promise<boolean> {
|
|
352
|
-
const systemPrompt = `You are a security classifier for an autonomous coding agent. Decide ALLOW or DENY for a pending tool call.
|
|
353
|
-
|
|
354
|
-
DEFAULT: ALLOW. The user enabled auto mode because they trust the agent. Only deny clear security violations.
|
|
355
|
-
|
|
356
|
-
ALLOW:
|
|
357
|
-
- Read-only commands
|
|
358
|
-
- Git read commands
|
|
359
|
-
- Build/test/lint commands
|
|
360
|
-
- Writing/editing files inside the project
|
|
361
|
-
- Commands clearly requested by the user
|
|
362
|
-
|
|
363
|
-
DENY:
|
|
364
|
-
- rm/unlink/rmdir unless the user explicitly asked to delete/remove that file
|
|
365
|
-
- system-path modifications (/etc, /usr, /bin, /System, ~/.ssh)
|
|
366
|
-
- sudo/su
|
|
367
|
-
- curl/wget piped to shell
|
|
368
|
-
- exfiltrating local secrets/files
|
|
369
|
-
- ssh/scp/sftp
|
|
370
|
-
- nc/ncat -l
|
|
371
|
-
- kill -9 / killall
|
|
372
|
-
- crontab
|
|
373
|
-
- dd / mkfs
|
|
374
|
-
- sed -i
|
|
375
|
-
- npm/pip/twine publish
|
|
376
|
-
- git push --force / -f
|
|
377
|
-
|
|
378
|
-
Output exactly one word: ALLOW or DENY.`;
|
|
379
|
-
|
|
380
352
|
const userPrompt = this.buildUserPrompt(toolName, argsJson, context);
|
|
381
353
|
|
|
382
354
|
return new Promise<boolean>((resolve, reject) => {
|
|
383
355
|
const data = JSON.stringify({
|
|
384
|
-
system_instruction: { parts: [{ text:
|
|
356
|
+
system_instruction: { parts: [{ text: CLASSIFIER_SYSTEM_PROMPT }] },
|
|
385
357
|
contents: [{ role: "user", parts: [{ text: userPrompt }] }],
|
|
386
358
|
generationConfig: { temperature: 0, maxOutputTokens: 8 },
|
|
387
359
|
});
|
|
@@ -267,4 +267,6 @@ export function serializeConversation(messages: Message[]): string {
|
|
|
267
267
|
|
|
268
268
|
export const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.
|
|
269
269
|
|
|
270
|
-
Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary
|
|
270
|
+
Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.
|
|
271
|
+
|
|
272
|
+
CRITICAL: Never reproduce secrets, API keys, tokens, passwords, or credentials in the summary. If you encounter any, redact them as [REDACTED].`;
|
|
@@ -7,7 +7,7 @@ import test from "node:test";
|
|
|
7
7
|
import { DefaultResourceLoader } from "./resource-loader.js";
|
|
8
8
|
import { SettingsManager } from "./settings-manager.js";
|
|
9
9
|
|
|
10
|
-
test("resource loader reads global
|
|
10
|
+
test("resource loader reads global LSD.md from the app root", async (t) => {
|
|
11
11
|
const tmp = mkdtempSync(join(tmpdir(), "resource-loader-lsd-md-"));
|
|
12
12
|
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ test("resource loader reads global lsd.md from the app root", async (t) => {
|
|
|
16
16
|
const cwd = join(tmp, "project");
|
|
17
17
|
mkdirSync(agentDir, { recursive: true });
|
|
18
18
|
mkdirSync(cwd, { recursive: true });
|
|
19
|
-
writeFileSync(join(appRoot, "
|
|
19
|
+
writeFileSync(join(appRoot, "LSD.md"), "# global lsd\n", "utf-8");
|
|
20
20
|
|
|
21
21
|
const loader = new DefaultResourceLoader({
|
|
22
22
|
cwd,
|
|
@@ -31,11 +31,11 @@ test("resource loader reads global lsd.md from the app root", async (t) => {
|
|
|
31
31
|
|
|
32
32
|
const contextFiles = loader.getAgentsFiles().agentsFiles;
|
|
33
33
|
assert.equal(contextFiles.length, 1);
|
|
34
|
-
assert.equal(contextFiles[0]?.path, join(appRoot, "
|
|
34
|
+
assert.equal(contextFiles[0]?.path, join(appRoot, "LSD.md"));
|
|
35
35
|
assert.equal(contextFiles[0]?.content, "# global lsd\n");
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
test("resource loader
|
|
38
|
+
test("resource loader uses project LSD.md before CLAUDE.md or AGENTS.md in the same directory", async (t) => {
|
|
39
39
|
const tmp = mkdtempSync(join(tmpdir(), "resource-loader-lsd-md-"));
|
|
40
40
|
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
41
41
|
|
|
@@ -44,9 +44,9 @@ test("resource loader reads ancestor .lsd/lsd.md before AGENTS.md in the same di
|
|
|
44
44
|
const projectRoot = join(tmp, "project");
|
|
45
45
|
const cwd = join(projectRoot, "src", "feature");
|
|
46
46
|
mkdirSync(agentDir, { recursive: true });
|
|
47
|
-
mkdirSync(join(projectRoot, ".lsd"), { recursive: true });
|
|
48
47
|
mkdirSync(cwd, { recursive: true });
|
|
49
|
-
writeFileSync(join(projectRoot, ".
|
|
48
|
+
writeFileSync(join(projectRoot, "LSD.md"), "# project lsd\n", "utf-8");
|
|
49
|
+
writeFileSync(join(projectRoot, "CLAUDE.md"), "# claude fallback\n", "utf-8");
|
|
50
50
|
writeFileSync(join(projectRoot, "AGENTS.md"), "# agents fallback\n", "utf-8");
|
|
51
51
|
|
|
52
52
|
const loader = new DefaultResourceLoader({
|
|
@@ -62,6 +62,66 @@ test("resource loader reads ancestor .lsd/lsd.md before AGENTS.md in the same di
|
|
|
62
62
|
|
|
63
63
|
const contextFiles = loader.getAgentsFiles().agentsFiles;
|
|
64
64
|
assert.equal(contextFiles.length, 1);
|
|
65
|
-
assert.equal(contextFiles[0]?.path, join(projectRoot, ".
|
|
65
|
+
assert.equal(contextFiles[0]?.path, join(projectRoot, "LSD.md"));
|
|
66
66
|
assert.equal(contextFiles[0]?.content, "# project lsd\n");
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
test("resource loader loads both global and project LSD.md files", async (t) => {
|
|
70
|
+
const tmp = mkdtempSync(join(tmpdir(), "resource-loader-lsd-md-"));
|
|
71
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
72
|
+
|
|
73
|
+
const appRoot = join(tmp, ".lsd");
|
|
74
|
+
const agentDir = join(appRoot, "agent");
|
|
75
|
+
const projectRoot = join(tmp, "project");
|
|
76
|
+
const cwd = join(projectRoot, "src", "feature");
|
|
77
|
+
mkdirSync(agentDir, { recursive: true });
|
|
78
|
+
mkdirSync(cwd, { recursive: true });
|
|
79
|
+
writeFileSync(join(appRoot, "LSD.md"), "# global lsd\n", "utf-8");
|
|
80
|
+
writeFileSync(join(projectRoot, "LSD.md"), "# project lsd\n", "utf-8");
|
|
81
|
+
|
|
82
|
+
const loader = new DefaultResourceLoader({
|
|
83
|
+
cwd,
|
|
84
|
+
agentDir,
|
|
85
|
+
settingsManager: SettingsManager.inMemory(),
|
|
86
|
+
noExtensions: true,
|
|
87
|
+
noSkills: true,
|
|
88
|
+
noPromptTemplates: true,
|
|
89
|
+
noThemes: true,
|
|
90
|
+
});
|
|
91
|
+
await loader.reload();
|
|
92
|
+
|
|
93
|
+
const contextFiles = loader.getAgentsFiles().agentsFiles;
|
|
94
|
+
assert.equal(contextFiles.length, 2);
|
|
95
|
+
assert.equal(contextFiles[0]?.path, join(appRoot, "LSD.md"));
|
|
96
|
+
assert.equal(contextFiles[1]?.path, join(projectRoot, "LSD.md"));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("resource loader falls back to CLAUDE.md when no LSD.md exists", async (t) => {
|
|
100
|
+
const tmp = mkdtempSync(join(tmpdir(), "resource-loader-lsd-md-"));
|
|
101
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
102
|
+
|
|
103
|
+
const appRoot = join(tmp, ".lsd");
|
|
104
|
+
const agentDir = join(appRoot, "agent");
|
|
105
|
+
const projectRoot = join(tmp, "project");
|
|
106
|
+
const cwd = join(projectRoot, "src", "feature");
|
|
107
|
+
mkdirSync(agentDir, { recursive: true });
|
|
108
|
+
mkdirSync(cwd, { recursive: true });
|
|
109
|
+
writeFileSync(join(projectRoot, "CLAUDE.md"), "# claude fallback\n", "utf-8");
|
|
110
|
+
writeFileSync(join(projectRoot, "AGENTS.md"), "# agents fallback\n", "utf-8");
|
|
111
|
+
|
|
112
|
+
const loader = new DefaultResourceLoader({
|
|
113
|
+
cwd,
|
|
114
|
+
agentDir,
|
|
115
|
+
settingsManager: SettingsManager.inMemory(),
|
|
116
|
+
noExtensions: true,
|
|
117
|
+
noSkills: true,
|
|
118
|
+
noPromptTemplates: true,
|
|
119
|
+
noThemes: true,
|
|
120
|
+
});
|
|
121
|
+
await loader.reload();
|
|
122
|
+
|
|
123
|
+
const contextFiles = loader.getAgentsFiles().agentsFiles;
|
|
124
|
+
assert.equal(contextFiles.length, 1);
|
|
125
|
+
assert.equal(contextFiles[0]?.path, join(projectRoot, "CLAUDE.md"));
|
|
126
|
+
assert.equal(contextFiles[0]?.content, "# claude fallback\n");
|
|
127
|
+
});
|
|
@@ -71,14 +71,14 @@ function tryReadContextFile(filePath: string): { path: string; content: string }
|
|
|
71
71
|
|
|
72
72
|
function loadContextFileFromDir(dir: string): { path: string; content: string } | null {
|
|
73
73
|
const candidates = [
|
|
74
|
-
join(dir, "lsd.md"),
|
|
75
|
-
join(dir, ".lsd", "lsd.md"),
|
|
76
|
-
join(dir, CONFIG_DIR_NAME, "lsd.md"),
|
|
77
74
|
join(dir, "LSD.md"),
|
|
78
75
|
join(dir, ".lsd", "LSD.md"),
|
|
79
76
|
join(dir, CONFIG_DIR_NAME, "LSD.md"),
|
|
80
|
-
join(dir, "
|
|
77
|
+
join(dir, "lsd.md"),
|
|
78
|
+
join(dir, ".lsd", "lsd.md"),
|
|
79
|
+
join(dir, CONFIG_DIR_NAME, "lsd.md"),
|
|
81
80
|
join(dir, "CLAUDE.md"),
|
|
81
|
+
join(dir, "AGENTS.md"),
|
|
82
82
|
];
|
|
83
83
|
for (const filePath of new Set(candidates)) {
|
|
84
84
|
const loaded = tryReadContextFile(filePath);
|
|
@@ -80,3 +80,103 @@ test("createAgentSession restores last session model even when API key cannot be
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
test("createAgentSession defaults to adaptive for supported Anthropic models when enabled", async () => {
|
|
85
|
+
const tempDir = join(
|
|
86
|
+
process.cwd(),
|
|
87
|
+
".tmp-tests",
|
|
88
|
+
`sdk-anthropic-adaptive-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
89
|
+
);
|
|
90
|
+
mkdirSync(tempDir, { recursive: true });
|
|
91
|
+
tempDirs.push(tempDir);
|
|
92
|
+
|
|
93
|
+
const authStorage = AuthStorage.inMemory({
|
|
94
|
+
anthropic: { type: "api_key", key: "test-anthropic-key" },
|
|
95
|
+
});
|
|
96
|
+
const modelRegistry = new ModelRegistry(authStorage, join(tempDir, "models.json"));
|
|
97
|
+
const settingsManager = SettingsManager.inMemory();
|
|
98
|
+
settingsManager.setDefaultModelAndProvider("anthropic", "claude-sonnet-4-6");
|
|
99
|
+
settingsManager.setDefaultThinkingLevel("high");
|
|
100
|
+
settingsManager.setAnthropicAdaptiveByDefault(true);
|
|
101
|
+
const sessionManager = SessionManager.inMemory(tempDir);
|
|
102
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
103
|
+
cwd: tempDir,
|
|
104
|
+
agentDir: tempDir,
|
|
105
|
+
settingsManager,
|
|
106
|
+
noExtensions: true,
|
|
107
|
+
noSkills: true,
|
|
108
|
+
noPromptTemplates: true,
|
|
109
|
+
noThemes: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const { session } = await createAgentSession({
|
|
113
|
+
cwd: tempDir,
|
|
114
|
+
agentDir: tempDir,
|
|
115
|
+
authStorage,
|
|
116
|
+
modelRegistry,
|
|
117
|
+
settingsManager,
|
|
118
|
+
sessionManager,
|
|
119
|
+
resourceLoader,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
assert.equal(session.model?.provider, "anthropic");
|
|
123
|
+
assert.equal(session.model?.id, "claude-sonnet-4-6");
|
|
124
|
+
assert.equal(session.thinkingLevel, "adaptive");
|
|
125
|
+
assert.equal(settingsManager.getDefaultThinkingLevel(), "high");
|
|
126
|
+
session.dispose();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("AgentSession switches supported Anthropic models to adaptive when the setting is enabled", async () => {
|
|
130
|
+
const tempDir = join(
|
|
131
|
+
process.cwd(),
|
|
132
|
+
".tmp-tests",
|
|
133
|
+
`sdk-anthropic-switch-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
134
|
+
);
|
|
135
|
+
mkdirSync(tempDir, { recursive: true });
|
|
136
|
+
tempDirs.push(tempDir);
|
|
137
|
+
|
|
138
|
+
const authStorage = AuthStorage.inMemory({
|
|
139
|
+
anthropic: { type: "api_key", key: "test-anthropic-key" },
|
|
140
|
+
openai: { type: "api_key", key: "test-openai-key" },
|
|
141
|
+
});
|
|
142
|
+
const modelRegistry = new ModelRegistry(authStorage, join(tempDir, "models.json"));
|
|
143
|
+
const settingsManager = SettingsManager.inMemory();
|
|
144
|
+
settingsManager.setDefaultModelAndProvider("openai", "gpt-5.4");
|
|
145
|
+
settingsManager.setDefaultThinkingLevel("high");
|
|
146
|
+
settingsManager.setAnthropicAdaptiveByDefault(true);
|
|
147
|
+
const sessionManager = SessionManager.inMemory(tempDir);
|
|
148
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
149
|
+
cwd: tempDir,
|
|
150
|
+
agentDir: tempDir,
|
|
151
|
+
settingsManager,
|
|
152
|
+
noExtensions: true,
|
|
153
|
+
noSkills: true,
|
|
154
|
+
noPromptTemplates: true,
|
|
155
|
+
noThemes: true,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const { session } = await createAgentSession({
|
|
159
|
+
cwd: tempDir,
|
|
160
|
+
agentDir: tempDir,
|
|
161
|
+
authStorage,
|
|
162
|
+
modelRegistry,
|
|
163
|
+
settingsManager,
|
|
164
|
+
sessionManager,
|
|
165
|
+
resourceLoader,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
assert.equal(session.model?.provider, "openai");
|
|
169
|
+
assert.equal(session.thinkingLevel, "high");
|
|
170
|
+
|
|
171
|
+
const anthropicModel = modelRegistry.getAvailable().find((model) =>
|
|
172
|
+
model.provider === "anthropic" && model.id === "claude-sonnet-4-6"
|
|
173
|
+
);
|
|
174
|
+
if (!anthropicModel) throw new Error("expected claude-sonnet-4-6 to be available");
|
|
175
|
+
|
|
176
|
+
await session.setModel(anthropicModel);
|
|
177
|
+
|
|
178
|
+
assert.equal(session.model?.provider, "anthropic");
|
|
179
|
+
assert.equal(session.model?.id, "claude-sonnet-4-6");
|
|
180
|
+
assert.equal(session.thinkingLevel, "adaptive");
|
|
181
|
+
session.dispose();
|
|
182
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { Agent, type AgentMessage, type ThinkingLevel } from "@gsd/pi-agent-core";
|
|
3
|
-
import type
|
|
3
|
+
import { supportsAdaptiveThinking, type Message, type Model } from "@gsd/pi-ai";
|
|
4
4
|
import { getAgentDir, getDocsPath } from "../config.js";
|
|
5
5
|
import { AgentSession } from "./agent-session.js";
|
|
6
6
|
import { AuthStorage } from "./auth-storage.js";
|
|
@@ -140,6 +140,17 @@ function getDefaultAgentDir(): string {
|
|
|
140
140
|
return getAgentDir();
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
function shouldPreferAdaptiveThinkingByDefault(
|
|
144
|
+
model: Model<any> | undefined,
|
|
145
|
+
settingsManager: SettingsManager,
|
|
146
|
+
): boolean {
|
|
147
|
+
return !!model &&
|
|
148
|
+
model.provider === "anthropic" &&
|
|
149
|
+
model.reasoning === true &&
|
|
150
|
+
settingsManager.getAnthropicAdaptiveByDefault() &&
|
|
151
|
+
supportsAdaptiveThinking(model.id);
|
|
152
|
+
}
|
|
153
|
+
|
|
143
154
|
/**
|
|
144
155
|
* Create an AgentSession with the specified options.
|
|
145
156
|
*
|
|
@@ -252,32 +263,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
252
263
|
thinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
|
|
253
264
|
}
|
|
254
265
|
|
|
266
|
+
const preferAdaptiveByDefault = shouldPreferAdaptiveThinkingByDefault(model, settingsManager);
|
|
267
|
+
if (preferAdaptiveByDefault) {
|
|
268
|
+
thinkingLevel = "adaptive";
|
|
269
|
+
}
|
|
270
|
+
|
|
255
271
|
// Clamp to model capabilities
|
|
256
272
|
if (!model || !model.reasoning) {
|
|
257
273
|
thinkingLevel = "off";
|
|
258
274
|
}
|
|
259
275
|
|
|
260
276
|
const editMode = settingsManager.getEditMode();
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
?
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
"hashline_read",
|
|
269
|
-
"bash",
|
|
270
|
-
"hashline_edit",
|
|
271
|
-
"write",
|
|
272
|
-
"lsp",
|
|
273
|
-
"pty_start",
|
|
274
|
-
"pty_send",
|
|
275
|
-
"pty_read",
|
|
276
|
-
"pty_wait",
|
|
277
|
-
"pty_resize",
|
|
278
|
-
"pty_kill",
|
|
279
|
-
]
|
|
280
|
-
: ["read", "bash", "edit", "write", "lsp", "pty_start", "pty_send", "pty_read", "pty_wait", "pty_resize", "pty_kill"];
|
|
277
|
+
const toolProfile = settingsManager.getToolProfile();
|
|
278
|
+
const balancedToolNames = editMode === "hashline"
|
|
279
|
+
? ["hashline_read", "bash", "hashline_edit", "write", "lsp", "bg_shell", "tool_search", "tool_enable", "Skill", "subagent", "await_subagent", "ask_user_questions"]
|
|
280
|
+
: ["read", "bash", "edit", "write", "lsp", "bg_shell", "tool_search", "tool_enable", "Skill", "subagent", "await_subagent", "ask_user_questions"];
|
|
281
|
+
const defaultActiveToolNames: string[] = toolProfile === "full"
|
|
282
|
+
? Object.keys(allTools)
|
|
283
|
+
: balancedToolNames;
|
|
281
284
|
const initialActiveToolNames: string[] = options.tools
|
|
282
285
|
? options.tools.map((t) => t.name).filter((n): n is string => typeof n === "string" && n in allTools)
|
|
283
286
|
: defaultActiveToolNames;
|