@oh-my-pi/pi-coding-agent 1.338.0 → 1.341.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/CHANGELOG.md +60 -1
- package/package.json +3 -3
- package/src/cli/args.ts +8 -0
- package/src/core/agent-session.ts +32 -14
- package/src/core/export-html/index.ts +48 -15
- package/src/core/export-html/template.html +3 -11
- package/src/core/mcp/client.ts +43 -16
- package/src/core/mcp/config.ts +152 -6
- package/src/core/mcp/index.ts +6 -2
- package/src/core/mcp/loader.ts +30 -3
- package/src/core/mcp/manager.ts +69 -10
- package/src/core/mcp/types.ts +9 -3
- package/src/core/model-resolver.ts +101 -0
- package/src/core/sdk.ts +65 -18
- package/src/core/session-manager.ts +117 -14
- package/src/core/settings-manager.ts +107 -19
- package/src/core/title-generator.ts +94 -0
- package/src/core/tools/bash.ts +1 -2
- package/src/core/tools/edit-diff.ts +2 -2
- package/src/core/tools/edit.ts +43 -5
- package/src/core/tools/grep.ts +3 -2
- package/src/core/tools/index.ts +73 -13
- package/src/core/tools/lsp/client.ts +45 -20
- package/src/core/tools/lsp/config.ts +708 -34
- package/src/core/tools/lsp/index.ts +423 -23
- package/src/core/tools/lsp/types.ts +5 -0
- package/src/core/tools/task/bundled-agents/explore.md +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +1 -1
- package/src/core/tools/task/model-resolver.ts +52 -3
- package/src/core/tools/write.ts +67 -4
- package/src/index.ts +5 -0
- package/src/main.ts +23 -2
- package/src/modes/interactive/components/model-selector.ts +96 -18
- package/src/modes/interactive/components/session-selector.ts +20 -7
- package/src/modes/interactive/components/settings-defs.ts +59 -2
- package/src/modes/interactive/components/settings-selector.ts +8 -11
- package/src/modes/interactive/components/tool-execution.ts +18 -0
- package/src/modes/interactive/components/tree-selector.ts +2 -2
- package/src/modes/interactive/components/welcome.ts +40 -3
- package/src/modes/interactive/interactive-mode.ts +87 -10
- package/src/core/export-html/vendor/highlight.min.js +0 -1213
- package/src/core/export-html/vendor/marked.min.js +0 -6
package/src/core/mcp/loader.ts
CHANGED
|
@@ -17,24 +17,49 @@ export interface MCPToolsLoadResult {
|
|
|
17
17
|
errors: Array<{ path: string; error: string }>;
|
|
18
18
|
/** Connected server names */
|
|
19
19
|
connectedServers: string[];
|
|
20
|
+
/** Extracted Exa API keys from filtered MCP servers */
|
|
21
|
+
exaApiKeys: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Options for loading MCP tools */
|
|
25
|
+
export interface MCPToolsLoadOptions {
|
|
26
|
+
/** Additional environment variables for expansion */
|
|
27
|
+
extraEnv?: Record<string, string>;
|
|
28
|
+
/** Called when starting to connect to servers */
|
|
29
|
+
onConnecting?: (serverNames: string[]) => void;
|
|
30
|
+
/** Whether to load project-level config (default: true) */
|
|
31
|
+
enableProjectConfig?: boolean;
|
|
32
|
+
/** Whether to filter out Exa MCP servers (default: true) */
|
|
33
|
+
filterExa?: boolean;
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
/**
|
|
23
37
|
* Discover and load MCP tools from .mcp.json files.
|
|
24
38
|
*
|
|
25
39
|
* @param cwd Working directory (project root)
|
|
26
|
-
* @param
|
|
40
|
+
* @param options Load options including extraEnv and progress callbacks
|
|
27
41
|
* @returns MCP tools in LoadedCustomTool format for integration
|
|
28
42
|
*/
|
|
29
43
|
export async function discoverAndLoadMCPTools(
|
|
30
44
|
cwd: string,
|
|
31
|
-
|
|
45
|
+
options?: MCPToolsLoadOptions | Record<string, string>,
|
|
32
46
|
): Promise<MCPToolsLoadResult> {
|
|
47
|
+
// Support old signature: discoverAndLoadMCPTools(cwd, extraEnv)
|
|
48
|
+
const opts: MCPToolsLoadOptions =
|
|
49
|
+
options && ("extraEnv" in options || "onConnecting" in options || "enableProjectConfig" in options)
|
|
50
|
+
? (options as MCPToolsLoadOptions)
|
|
51
|
+
: { extraEnv: options as Record<string, string> | undefined };
|
|
52
|
+
|
|
33
53
|
const manager = new MCPManager(cwd);
|
|
34
54
|
|
|
35
55
|
let result: MCPLoadResult;
|
|
36
56
|
try {
|
|
37
|
-
result = await manager.discoverAndConnect(
|
|
57
|
+
result = await manager.discoverAndConnect({
|
|
58
|
+
extraEnv: opts.extraEnv,
|
|
59
|
+
onConnecting: opts.onConnecting,
|
|
60
|
+
enableProjectConfig: opts.enableProjectConfig,
|
|
61
|
+
filterExa: opts.filterExa,
|
|
62
|
+
});
|
|
38
63
|
} catch (error) {
|
|
39
64
|
// If discovery fails entirely, return empty result
|
|
40
65
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -43,6 +68,7 @@ export async function discoverAndLoadMCPTools(
|
|
|
43
68
|
tools: [],
|
|
44
69
|
errors: [{ path: ".mcp.json", error: message }],
|
|
45
70
|
connectedServers: [],
|
|
71
|
+
exaApiKeys: [],
|
|
46
72
|
};
|
|
47
73
|
}
|
|
48
74
|
|
|
@@ -64,5 +90,6 @@ export async function discoverAndLoadMCPTools(
|
|
|
64
90
|
tools: loadedTools,
|
|
65
91
|
errors,
|
|
66
92
|
connectedServers: result.connectedServers,
|
|
93
|
+
exaApiKeys: result.exaApiKeys,
|
|
67
94
|
};
|
|
68
95
|
}
|
package/src/core/mcp/manager.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { TSchema } from "@sinclair/typebox";
|
|
9
9
|
import type { CustomTool } from "../custom-tools/types.js";
|
|
10
10
|
import { connectToServer, disconnectServer, listTools } from "./client.js";
|
|
11
|
-
import { loadAllMCPConfigs, validateServerConfig } from "./config.js";
|
|
11
|
+
import { type LoadMCPConfigsOptions, loadAllMCPConfigs, validateServerConfig } from "./config.js";
|
|
12
12
|
import type { MCPToolDetails } from "./tool-bridge.js";
|
|
13
13
|
import { createMCPTools } from "./tool-bridge.js";
|
|
14
14
|
import type { MCPServerConfig, MCPServerConnection } from "./types.js";
|
|
@@ -21,6 +21,14 @@ export interface MCPLoadResult {
|
|
|
21
21
|
errors: Map<string, string>;
|
|
22
22
|
/** Connected server names */
|
|
23
23
|
connectedServers: string[];
|
|
24
|
+
/** Extracted Exa API keys from filtered MCP servers */
|
|
25
|
+
exaApiKeys: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Options for discovering and connecting to MCP servers */
|
|
29
|
+
export interface MCPDiscoverOptions extends LoadMCPConfigsOptions {
|
|
30
|
+
/** Called when starting to connect to servers */
|
|
31
|
+
onConnecting?: (serverNames: string[]) => void;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
/**
|
|
@@ -38,19 +46,49 @@ export class MCPManager {
|
|
|
38
46
|
* Discover and connect to all MCP servers from .mcp.json files.
|
|
39
47
|
* Returns tools and any connection errors.
|
|
40
48
|
*/
|
|
41
|
-
async discoverAndConnect(
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
async discoverAndConnect(
|
|
50
|
+
extraEnvOrOptions?: Record<string, string> | MCPDiscoverOptions,
|
|
51
|
+
onConnecting?: (serverNames: string[]) => void,
|
|
52
|
+
): Promise<MCPLoadResult> {
|
|
53
|
+
// Support old signature: discoverAndConnect(extraEnv, onConnecting)
|
|
54
|
+
const opts: MCPDiscoverOptions =
|
|
55
|
+
extraEnvOrOptions &&
|
|
56
|
+
("extraEnv" in extraEnvOrOptions ||
|
|
57
|
+
"enableProjectConfig" in extraEnvOrOptions ||
|
|
58
|
+
"filterExa" in extraEnvOrOptions ||
|
|
59
|
+
"onConnecting" in extraEnvOrOptions)
|
|
60
|
+
? (extraEnvOrOptions as MCPDiscoverOptions)
|
|
61
|
+
: { extraEnv: extraEnvOrOptions as Record<string, string> | undefined, onConnecting };
|
|
62
|
+
|
|
63
|
+
const { configs, exaApiKeys } = loadAllMCPConfigs(this.cwd, {
|
|
64
|
+
extraEnv: opts.extraEnv,
|
|
65
|
+
enableProjectConfig: opts.enableProjectConfig,
|
|
66
|
+
filterExa: opts.filterExa,
|
|
67
|
+
});
|
|
68
|
+
const result = await this.connectServers(configs, opts.onConnecting);
|
|
69
|
+
result.exaApiKeys = exaApiKeys;
|
|
70
|
+
return result;
|
|
44
71
|
}
|
|
45
72
|
|
|
46
73
|
/**
|
|
47
74
|
* Connect to specific MCP servers.
|
|
75
|
+
* Connections are made in parallel for faster startup.
|
|
48
76
|
*/
|
|
49
|
-
async connectServers(
|
|
77
|
+
async connectServers(
|
|
78
|
+
configs: Record<string, MCPServerConfig>,
|
|
79
|
+
onConnecting?: (serverNames: string[]) => void,
|
|
80
|
+
): Promise<MCPLoadResult> {
|
|
50
81
|
const errors = new Map<string, string>();
|
|
51
82
|
const connectedServers: string[] = [];
|
|
52
83
|
const allTools: CustomTool<TSchema, MCPToolDetails>[] = [];
|
|
53
84
|
|
|
85
|
+
// Prepare connection tasks
|
|
86
|
+
const connectionTasks: Array<{
|
|
87
|
+
name: string;
|
|
88
|
+
config: MCPServerConfig;
|
|
89
|
+
validationErrors: string[];
|
|
90
|
+
}> = [];
|
|
91
|
+
|
|
54
92
|
for (const [name, config] of Object.entries(configs)) {
|
|
55
93
|
// Skip if already connected
|
|
56
94
|
if (this.connections.has(name)) {
|
|
@@ -65,17 +103,37 @@ export class MCPManager {
|
|
|
65
103
|
continue;
|
|
66
104
|
}
|
|
67
105
|
|
|
68
|
-
|
|
106
|
+
connectionTasks.push({ name, config, validationErrors });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Notify about servers we're connecting to
|
|
110
|
+
if (connectionTasks.length > 0 && onConnecting) {
|
|
111
|
+
onConnecting(connectionTasks.map((t) => t.name));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Connect to all servers in parallel
|
|
115
|
+
const results = await Promise.allSettled(
|
|
116
|
+
connectionTasks.map(async ({ name, config }) => {
|
|
69
117
|
const connection = await connectToServer(name, config);
|
|
118
|
+
const serverTools = await listTools(connection);
|
|
119
|
+
return { name, connection, serverTools };
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Process results
|
|
124
|
+
for (let i = 0; i < results.length; i++) {
|
|
125
|
+
const result = results[i];
|
|
126
|
+
const { name } = connectionTasks[i];
|
|
127
|
+
|
|
128
|
+
if (result.status === "fulfilled") {
|
|
129
|
+
const { connection, serverTools } = result.value;
|
|
70
130
|
this.connections.set(name, connection);
|
|
71
131
|
connectedServers.push(name);
|
|
72
132
|
|
|
73
|
-
// Load tools from this server
|
|
74
|
-
const serverTools = await listTools(connection);
|
|
75
133
|
const customTools = createMCPTools(connection, serverTools);
|
|
76
134
|
allTools.push(...customTools);
|
|
77
|
-
}
|
|
78
|
-
const message =
|
|
135
|
+
} else {
|
|
136
|
+
const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
79
137
|
errors.set(name, message);
|
|
80
138
|
}
|
|
81
139
|
}
|
|
@@ -87,6 +145,7 @@ export class MCPManager {
|
|
|
87
145
|
tools: allTools,
|
|
88
146
|
errors,
|
|
89
147
|
connectedServers,
|
|
148
|
+
exaApiKeys: [], // Will be populated by discoverAndConnect
|
|
90
149
|
};
|
|
91
150
|
}
|
|
92
151
|
|
package/src/core/mcp/types.ts
CHANGED
|
@@ -41,8 +41,14 @@ export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcRespo
|
|
|
41
41
|
// MCP Server Configuration (.mcp.json format)
|
|
42
42
|
// =============================================================================
|
|
43
43
|
|
|
44
|
+
/** Base server config with shared options */
|
|
45
|
+
interface MCPServerConfigBase {
|
|
46
|
+
/** Connection timeout in milliseconds (default: 30000) */
|
|
47
|
+
timeout?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
/** Stdio server configuration */
|
|
45
|
-
export interface MCPStdioServerConfig {
|
|
51
|
+
export interface MCPStdioServerConfig extends MCPServerConfigBase {
|
|
46
52
|
type?: "stdio"; // Default if not specified
|
|
47
53
|
command: string;
|
|
48
54
|
args?: string[];
|
|
@@ -51,14 +57,14 @@ export interface MCPStdioServerConfig {
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/** HTTP server configuration (Streamable HTTP transport) */
|
|
54
|
-
export interface MCPHttpServerConfig {
|
|
60
|
+
export interface MCPHttpServerConfig extends MCPServerConfigBase {
|
|
55
61
|
type: "http";
|
|
56
62
|
url: string;
|
|
57
63
|
headers?: Record<string, string>;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
/** SSE server configuration (deprecated, use HTTP) */
|
|
61
|
-
export interface MCPSseServerConfig {
|
|
67
|
+
export interface MCPSseServerConfig extends MCPServerConfigBase {
|
|
62
68
|
type: "sse";
|
|
63
69
|
url: string;
|
|
64
70
|
headers?: Record<string, string>;
|
|
@@ -30,6 +30,29 @@ export interface ScopedModel {
|
|
|
30
30
|
thinkingLevel: ThinkingLevel;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Priority chain for auto-discovering smol/fast models */
|
|
34
|
+
export const SMOL_MODEL_PRIORITY = ["claude-haiku-4-5", "haiku", "flash", "mini"];
|
|
35
|
+
|
|
36
|
+
/** Priority chain for auto-discovering slow/comprehensive models (reasoning, codex) */
|
|
37
|
+
export const SLOW_MODEL_PRIORITY = ["gpt-5.2-codex", "gpt-5.2", "codex", "gpt", "opus", "pro"];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse a model string in "provider/modelId" format.
|
|
41
|
+
* Returns undefined if the format is invalid.
|
|
42
|
+
*/
|
|
43
|
+
export function parseModelString(modelStr: string): { provider: string; id: string } | undefined {
|
|
44
|
+
const slashIdx = modelStr.indexOf("/");
|
|
45
|
+
if (slashIdx <= 0) return undefined;
|
|
46
|
+
return { provider: modelStr.slice(0, slashIdx), id: modelStr.slice(slashIdx + 1) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format a model as "provider/modelId" string.
|
|
51
|
+
*/
|
|
52
|
+
export function formatModelString(model: Model<Api>): string {
|
|
53
|
+
return `${model.provider}/${model.id}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
/**
|
|
34
57
|
* Helper to check if a model ID looks like an alias (no date suffix)
|
|
35
58
|
* Dates are typically in format: -20241022 or -20250929
|
|
@@ -391,3 +414,81 @@ export async function restoreModelFromSession(
|
|
|
391
414
|
// No models available
|
|
392
415
|
return { model: undefined, fallbackMessage: undefined };
|
|
393
416
|
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Find a smol/fast model using the priority chain.
|
|
420
|
+
* Tries exact matches first, then fuzzy matches.
|
|
421
|
+
*
|
|
422
|
+
* @param modelRegistry The model registry to search
|
|
423
|
+
* @param savedModel Optional saved model string from settings (provider/modelId)
|
|
424
|
+
* @returns The best available smol model, or undefined if none found
|
|
425
|
+
*/
|
|
426
|
+
export async function findSmolModel(
|
|
427
|
+
modelRegistry: ModelRegistry,
|
|
428
|
+
savedModel?: string,
|
|
429
|
+
): Promise<Model<Api> | undefined> {
|
|
430
|
+
const availableModels = await modelRegistry.getAvailable();
|
|
431
|
+
if (availableModels.length === 0) return undefined;
|
|
432
|
+
|
|
433
|
+
// 1. Try saved model from settings
|
|
434
|
+
if (savedModel) {
|
|
435
|
+
const parsed = parseModelString(savedModel);
|
|
436
|
+
if (parsed) {
|
|
437
|
+
const match = availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
|
|
438
|
+
if (match) return match;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// 2. Try priority chain
|
|
443
|
+
for (const pattern of SMOL_MODEL_PRIORITY) {
|
|
444
|
+
// Try exact match first
|
|
445
|
+
const exactMatch = availableModels.find((m) => m.id.toLowerCase() === pattern.toLowerCase());
|
|
446
|
+
if (exactMatch) return exactMatch;
|
|
447
|
+
|
|
448
|
+
// Try fuzzy match (substring)
|
|
449
|
+
const fuzzyMatch = availableModels.find((m) => m.id.toLowerCase().includes(pattern.toLowerCase()));
|
|
450
|
+
if (fuzzyMatch) return fuzzyMatch;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 3. Fallback to first available (same as default)
|
|
454
|
+
return availableModels[0];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Find a slow/comprehensive model using the priority chain.
|
|
459
|
+
* Prioritizes reasoning and codex models for thorough analysis.
|
|
460
|
+
*
|
|
461
|
+
* @param modelRegistry The model registry to search
|
|
462
|
+
* @param savedModel Optional saved model string from settings (provider/modelId)
|
|
463
|
+
* @returns The best available slow model, or undefined if none found
|
|
464
|
+
*/
|
|
465
|
+
export async function findSlowModel(
|
|
466
|
+
modelRegistry: ModelRegistry,
|
|
467
|
+
savedModel?: string,
|
|
468
|
+
): Promise<Model<Api> | undefined> {
|
|
469
|
+
const availableModels = await modelRegistry.getAvailable();
|
|
470
|
+
if (availableModels.length === 0) return undefined;
|
|
471
|
+
|
|
472
|
+
// 1. Try saved model from settings
|
|
473
|
+
if (savedModel) {
|
|
474
|
+
const parsed = parseModelString(savedModel);
|
|
475
|
+
if (parsed) {
|
|
476
|
+
const match = availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
|
|
477
|
+
if (match) return match;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// 2. Try priority chain
|
|
482
|
+
for (const pattern of SLOW_MODEL_PRIORITY) {
|
|
483
|
+
// Try exact match first
|
|
484
|
+
const exactMatch = availableModels.find((m) => m.id.toLowerCase() === pattern.toLowerCase());
|
|
485
|
+
if (exactMatch) return exactMatch;
|
|
486
|
+
|
|
487
|
+
// Try fuzzy match (substring)
|
|
488
|
+
const fuzzyMatch = availableModels.find((m) => m.id.toLowerCase().includes(pattern.toLowerCase()));
|
|
489
|
+
if (fuzzyMatch) return fuzzyMatch;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 3. Fallback to first available (same as default)
|
|
493
|
+
return availableModels[0];
|
|
494
|
+
}
|
package/src/core/sdk.ts
CHANGED
|
@@ -78,6 +78,7 @@ import {
|
|
|
78
78
|
readOnlyTools,
|
|
79
79
|
readTool,
|
|
80
80
|
type Tool,
|
|
81
|
+
warmupLspServers,
|
|
81
82
|
writeTool,
|
|
82
83
|
} from "./tools/index.js";
|
|
83
84
|
|
|
@@ -146,6 +147,8 @@ export interface CreateAgentSessionResult {
|
|
|
146
147
|
mcpManager?: MCPManager;
|
|
147
148
|
/** Warning if session was restored with a different model than saved */
|
|
148
149
|
modelFallbackMessage?: string;
|
|
150
|
+
/** LSP servers that were warmed up at startup */
|
|
151
|
+
lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>;
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
// Re-exports
|
|
@@ -323,8 +326,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
323
326
|
export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
|
324
327
|
const manager = SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
|
|
325
328
|
return {
|
|
326
|
-
|
|
327
|
-
defaultModel: manager.getDefaultModel(),
|
|
329
|
+
modelRoles: manager.getModelRoles(),
|
|
328
330
|
defaultThinkingLevel: manager.getDefaultThinkingLevel(),
|
|
329
331
|
queueMode: manager.getQueueMode(),
|
|
330
332
|
theme: manager.getTheme(),
|
|
@@ -479,24 +481,34 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
479
481
|
let modelFallbackMessage: string | undefined;
|
|
480
482
|
|
|
481
483
|
// If session has data, try to restore model from it
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
484
|
+
const defaultModelStr = existingSession.models.default;
|
|
485
|
+
if (!model && hasExistingSession && defaultModelStr) {
|
|
486
|
+
const slashIdx = defaultModelStr.indexOf("/");
|
|
487
|
+
if (slashIdx > 0) {
|
|
488
|
+
const provider = defaultModelStr.slice(0, slashIdx);
|
|
489
|
+
const modelId = defaultModelStr.slice(slashIdx + 1);
|
|
490
|
+
const restoredModel = modelRegistry.find(provider, modelId);
|
|
491
|
+
if (restoredModel && (await modelRegistry.getApiKey(restoredModel))) {
|
|
492
|
+
model = restoredModel;
|
|
493
|
+
}
|
|
494
|
+
if (!model) {
|
|
495
|
+
modelFallbackMessage = `Could not restore model ${defaultModelStr}`;
|
|
496
|
+
}
|
|
489
497
|
}
|
|
490
498
|
}
|
|
491
499
|
|
|
492
500
|
// If still no model, try settings default
|
|
493
501
|
if (!model) {
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
502
|
+
const settingsDefaultModel = settingsManager.getModelRole("default");
|
|
503
|
+
if (settingsDefaultModel) {
|
|
504
|
+
const slashIdx = settingsDefaultModel.indexOf("/");
|
|
505
|
+
if (slashIdx > 0) {
|
|
506
|
+
const provider = settingsDefaultModel.slice(0, slashIdx);
|
|
507
|
+
const modelId = settingsDefaultModel.slice(slashIdx + 1);
|
|
508
|
+
const settingsModel = modelRegistry.find(provider, modelId);
|
|
509
|
+
if (settingsModel && (await modelRegistry.getApiKey(settingsModel))) {
|
|
510
|
+
model = settingsModel;
|
|
511
|
+
}
|
|
500
512
|
}
|
|
501
513
|
}
|
|
502
514
|
}
|
|
@@ -566,7 +578,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
566
578
|
const sessionContext = {
|
|
567
579
|
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
568
580
|
};
|
|
569
|
-
const builtInTools =
|
|
581
|
+
const builtInTools =
|
|
582
|
+
options.tools ??
|
|
583
|
+
createCodingTools(cwd, options.hasUI ?? false, sessionContext, {
|
|
584
|
+
lspFormatOnWrite: settingsManager.getLspFormatOnWrite(),
|
|
585
|
+
lspDiagnosticsOnWrite: settingsManager.getLspDiagnosticsOnWrite(),
|
|
586
|
+
lspDiagnosticsOnEdit: settingsManager.getLspDiagnosticsOnEdit(),
|
|
587
|
+
editFuzzyMatch: settingsManager.getEditFuzzyMatch(),
|
|
588
|
+
});
|
|
570
589
|
time("createCodingTools");
|
|
571
590
|
|
|
572
591
|
let customToolsResult: CustomToolsLoadResult;
|
|
@@ -596,10 +615,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
596
615
|
let mcpManager: MCPManager | undefined;
|
|
597
616
|
const enableMCP = options.enableMCP ?? true;
|
|
598
617
|
if (enableMCP) {
|
|
599
|
-
const mcpResult = await discoverAndLoadMCPTools(cwd
|
|
618
|
+
const mcpResult = await discoverAndLoadMCPTools(cwd, {
|
|
619
|
+
onConnecting: (serverNames) => {
|
|
620
|
+
if (options.hasUI && serverNames.length > 0) {
|
|
621
|
+
process.stderr.write(`\x1b[90mConnecting to MCP servers: ${serverNames.join(", ")}...\x1b[0m\n`);
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
enableProjectConfig: settingsManager.getMCPProjectConfigEnabled(),
|
|
625
|
+
// Always filter Exa - we have native integration
|
|
626
|
+
filterExa: true,
|
|
627
|
+
});
|
|
600
628
|
time("discoverAndLoadMCPTools");
|
|
601
629
|
mcpManager = mcpResult.manager;
|
|
602
630
|
|
|
631
|
+
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
632
|
+
if (mcpResult.exaApiKeys.length > 0 && !process.env.EXA_API_KEY) {
|
|
633
|
+
process.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
|
|
634
|
+
}
|
|
635
|
+
|
|
603
636
|
// Log MCP errors
|
|
604
637
|
for (const { path, error } of mcpResult.errors) {
|
|
605
638
|
console.error(`MCP "${path}": ${error}`);
|
|
@@ -714,6 +747,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
714
747
|
}
|
|
715
748
|
: undefined,
|
|
716
749
|
queueMode: settingsManager.getQueueMode(),
|
|
750
|
+
interruptMode: settingsManager.getInterruptMode(),
|
|
717
751
|
getToolContext: toolContextStore.getContext,
|
|
718
752
|
getApiKey: async () => {
|
|
719
753
|
const currentModel = agent.state.model;
|
|
@@ -735,7 +769,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
735
769
|
} else {
|
|
736
770
|
// Save initial model and thinking level for new sessions so they can be restored on resume
|
|
737
771
|
if (model) {
|
|
738
|
-
sessionManager.appendModelChange(model.provider
|
|
772
|
+
sessionManager.appendModelChange(`${model.provider}/${model.id}`);
|
|
739
773
|
}
|
|
740
774
|
sessionManager.appendThinkingLevelChange(thinkingLevel);
|
|
741
775
|
}
|
|
@@ -753,10 +787,23 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
753
787
|
});
|
|
754
788
|
time("createAgentSession");
|
|
755
789
|
|
|
790
|
+
// Warm up LSP servers (connects to detected servers)
|
|
791
|
+
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
792
|
+
if (settingsManager.getLspDiagnosticsOnWrite()) {
|
|
793
|
+
try {
|
|
794
|
+
const result = await warmupLspServers(cwd);
|
|
795
|
+
lspServers = result.servers;
|
|
796
|
+
time("warmupLspServers");
|
|
797
|
+
} catch {
|
|
798
|
+
// Ignore warmup errors
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
756
802
|
return {
|
|
757
803
|
session,
|
|
758
804
|
customToolsResult,
|
|
759
805
|
mcpManager,
|
|
760
806
|
modelFallbackMessage,
|
|
807
|
+
lspServers,
|
|
761
808
|
};
|
|
762
809
|
}
|