wave-agent-sdk 0.16.8 → 0.16.9
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/constants/tools.d.ts +0 -1
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +0 -1
- package/dist/managers/aiManager.d.ts +0 -8
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +0 -45
- package/dist/managers/mcpManager.d.ts +5 -0
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +107 -12
- package/dist/managers/toolManager.d.ts +0 -6
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +1 -28
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +1 -12
- package/dist/services/authService.d.ts +10 -0
- package/dist/services/authService.d.ts.map +1 -1
- package/dist/services/authService.js +45 -0
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +8 -14
- package/dist/services/pluginLoader.d.ts.map +1 -1
- package/dist/services/pluginLoader.js +2 -2
- package/dist/telemetry/instrumentation.d.ts +5 -2
- package/dist/telemetry/instrumentation.d.ts.map +1 -1
- package/dist/telemetry/instrumentation.js +8 -4
- package/dist/tools/buildTool.d.ts +0 -2
- package/dist/tools/buildTool.d.ts.map +1 -1
- package/dist/tools/buildTool.js +0 -2
- package/dist/tools/cronCreateTool.d.ts.map +1 -1
- package/dist/tools/cronCreateTool.js +0 -1
- package/dist/tools/cronDeleteTool.d.ts.map +1 -1
- package/dist/tools/cronDeleteTool.js +0 -1
- package/dist/tools/cronListTool.d.ts.map +1 -1
- package/dist/tools/cronListTool.js +0 -1
- package/dist/tools/enterWorktreeTool.d.ts.map +1 -1
- package/dist/tools/enterWorktreeTool.js +0 -1
- package/dist/tools/exitWorktreeTool.d.ts.map +1 -1
- package/dist/tools/exitWorktreeTool.js +0 -1
- package/dist/tools/taskManagementTools.d.ts.map +1 -1
- package/dist/tools/taskManagementTools.js +0 -4
- package/dist/tools/types.d.ts +0 -15
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts.map +1 -1
- package/dist/tools/webFetchTool.js +0 -1
- package/dist/types/mcp.d.ts +3 -1
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +0 -1
- package/package.json +1 -1
- package/src/constants/tools.ts +0 -1
- package/src/managers/aiManager.ts +0 -48
- package/src/managers/mcpManager.ts +122 -16
- package/src/managers/toolManager.ts +1 -32
- package/src/prompts/index.ts +0 -13
- package/src/services/authService.ts +56 -0
- package/src/services/configurationService.ts +8 -18
- package/src/services/pluginLoader.ts +2 -2
- package/src/telemetry/instrumentation.ts +12 -4
- package/src/tools/buildTool.ts +0 -4
- package/src/tools/cronCreateTool.ts +0 -1
- package/src/tools/cronDeleteTool.ts +0 -1
- package/src/tools/cronListTool.ts +0 -1
- package/src/tools/enterWorktreeTool.ts +0 -1
- package/src/tools/exitWorktreeTool.ts +0 -1
- package/src/tools/taskManagementTools.ts +0 -4
- package/src/tools/types.ts +0 -15
- package/src/tools/webFetchTool.ts +0 -1
- package/src/types/mcp.ts +8 -1
- package/src/utils/mcpUtils.ts +0 -1
- package/dist/tools/toolSearchTool.d.ts +0 -15
- package/dist/tools/toolSearchTool.d.ts.map +0 -1
- package/dist/tools/toolSearchTool.js +0 -200
- package/dist/utils/isDeferredTool.d.ts +0 -19
- package/dist/utils/isDeferredTool.d.ts.map +0 -1
- package/dist/utils/isDeferredTool.js +0 -31
- package/src/tools/toolSearchTool.ts +0 -245
- package/src/utils/isDeferredTool.ts +0 -36
|
@@ -45,8 +45,6 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
45
45
|
|
|
46
46
|
import type { SubagentConfiguration } from "../utils/subagentParser.js";
|
|
47
47
|
import type { SkillMetadata } from "../types/skills.js";
|
|
48
|
-
import { toolSearchTool } from "../tools/toolSearchTool.js";
|
|
49
|
-
import { isDeferredTool } from "../utils/isDeferredTool.js";
|
|
50
48
|
import { startToolSpan, endToolSpan } from "../telemetry/sessionTracing.js";
|
|
51
49
|
|
|
52
50
|
export interface ToolManagerOptions {
|
|
@@ -134,7 +132,6 @@ class ToolManager {
|
|
|
134
132
|
webFetchTool,
|
|
135
133
|
enterWorktreeTool,
|
|
136
134
|
exitWorktreeTool,
|
|
137
|
-
toolSearchTool,
|
|
138
135
|
];
|
|
139
136
|
|
|
140
137
|
for (const tool of builtInTools) {
|
|
@@ -210,7 +207,7 @@ class ToolManager {
|
|
|
210
207
|
permissionMode: effectivePermissionMode,
|
|
211
208
|
canUseToolCallback,
|
|
212
209
|
permissionManager,
|
|
213
|
-
toolManager: this,
|
|
210
|
+
toolManager: this,
|
|
214
211
|
taskManager:
|
|
215
212
|
this.container.get<import("../services/taskManager.js").TaskManager>(
|
|
216
213
|
"TaskManager",
|
|
@@ -349,13 +346,10 @@ class ToolManager {
|
|
|
349
346
|
availableSkills?: SkillMetadata[];
|
|
350
347
|
workdir?: string;
|
|
351
348
|
isSubagent?: boolean;
|
|
352
|
-
/** Set of discovered deferred tool names to include in the API call */
|
|
353
|
-
discoveredTools?: Set<string>;
|
|
354
349
|
}): ChatCompletionFunctionTool[] {
|
|
355
350
|
const permissionManager =
|
|
356
351
|
this.container.get<PermissionManager>("PermissionManager");
|
|
357
352
|
const effectivePermissionMode = this.getPermissionMode();
|
|
358
|
-
const discoveredTools = options?.discoveredTools;
|
|
359
353
|
const builtInToolsConfig = Array.from(this.toolsRegistry.values())
|
|
360
354
|
.filter((tool) => {
|
|
361
355
|
// If tool is explicitly denied by name in permission rules, filter it out
|
|
@@ -380,10 +374,6 @@ class ToolManager {
|
|
|
380
374
|
effectivePermissionMode !== "bypassPermissions"
|
|
381
375
|
);
|
|
382
376
|
}
|
|
383
|
-
// Exclude deferred tools that haven't been discovered yet
|
|
384
|
-
if (isDeferredTool(tool) && !discoveredTools?.has(tool.name)) {
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
387
377
|
return true;
|
|
388
378
|
})
|
|
389
379
|
.map((tool) => {
|
|
@@ -406,10 +396,6 @@ class ToolManager {
|
|
|
406
396
|
if (permissionManager?.isToolDenied(tool.function.name)) {
|
|
407
397
|
return false;
|
|
408
398
|
}
|
|
409
|
-
// Exclude MCP tools that haven't been discovered yet
|
|
410
|
-
if (discoveredTools && !discoveredTools.has(tool.function.name)) {
|
|
411
|
-
return false;
|
|
412
|
-
}
|
|
413
399
|
return true;
|
|
414
400
|
});
|
|
415
401
|
return [...builtInToolsConfig, ...mcpToolsConfig];
|
|
@@ -453,23 +439,6 @@ class ToolManager {
|
|
|
453
439
|
return this.container.get<PermissionManager>("PermissionManager");
|
|
454
440
|
}
|
|
455
441
|
|
|
456
|
-
/**
|
|
457
|
-
* Get the names of all deferred tools (those that require ToolSearch to discover).
|
|
458
|
-
*/
|
|
459
|
-
public getDeferredToolNames(): string[] {
|
|
460
|
-
const permissionManager =
|
|
461
|
-
this.container.get<PermissionManager>("PermissionManager");
|
|
462
|
-
const builtInDeferred = Array.from(this.toolsRegistry.values())
|
|
463
|
-
.filter((tool) => isDeferredTool(tool))
|
|
464
|
-
.filter((tool) => !permissionManager?.isToolDenied(tool.name))
|
|
465
|
-
.map((tool) => tool.name);
|
|
466
|
-
const mcpDeferred = this.mcpManager
|
|
467
|
-
.getMcpToolsConfig()
|
|
468
|
-
.filter((tool) => !permissionManager?.isToolDenied(tool.function.name))
|
|
469
|
-
.map((tool) => tool.function.name);
|
|
470
|
-
return [...builtInDeferred, ...mcpDeferred];
|
|
471
|
-
}
|
|
472
|
-
|
|
473
442
|
/**
|
|
474
443
|
* Get the task manager
|
|
475
444
|
*/
|
package/src/prompts/index.ts
CHANGED
|
@@ -18,9 +18,7 @@ import {
|
|
|
18
18
|
READ_TOOL_NAME,
|
|
19
19
|
GLOB_TOOL_NAME,
|
|
20
20
|
GREP_TOOL_NAME,
|
|
21
|
-
TOOL_SEARCH_TOOL_NAME,
|
|
22
21
|
} from "../constants/tools.js";
|
|
23
|
-
import { isDeferredTool } from "../utils/isDeferredTool.js";
|
|
24
22
|
|
|
25
23
|
export const BASE_SYSTEM_PROMPT = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.`;
|
|
26
24
|
|
|
@@ -260,17 +258,6 @@ export function buildSystemPrompt(
|
|
|
260
258
|
prompt += `\n\n${TOOL_POLICY}`;
|
|
261
259
|
}
|
|
262
260
|
|
|
263
|
-
// List available deferred tool names with descriptions so the model knows they exist
|
|
264
|
-
// and can decide which ones to discover via ToolSearch
|
|
265
|
-
const deferredTools = tools.filter(isDeferredTool);
|
|
266
|
-
if (deferredTools.length > 0) {
|
|
267
|
-
const lines = deferredTools.map((t) => {
|
|
268
|
-
const desc = t.config.function?.description;
|
|
269
|
-
return desc ? `${t.name} - ${desc}` : t.name;
|
|
270
|
-
});
|
|
271
|
-
prompt += `\n\n<available-deferred-tools>\n${lines.join("\n")}\nThese tools are NOT loaded yet — call ${TOOL_SEARCH_TOOL_NAME} first to discover their schemas before invoking them.</available-deferred-tools>`;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
261
|
prompt += `\n\n${OUTPUT_EFFICIENCY_PROMPT}`;
|
|
275
262
|
prompt += `\n\n${TONE_AND_STYLE_PROMPT}`;
|
|
276
263
|
|
|
@@ -15,12 +15,16 @@ import {
|
|
|
15
15
|
} from "fs";
|
|
16
16
|
import * as path from "path";
|
|
17
17
|
import * as os from "os";
|
|
18
|
+
import { randomBytes } from "crypto";
|
|
18
19
|
import { createServer, Server } from "http";
|
|
19
20
|
import { URL } from "url";
|
|
20
21
|
import { execFile } from "child_process";
|
|
21
22
|
import { promisify } from "util";
|
|
22
23
|
import type { AuthConfig, AuthUser } from "../types/auth.js";
|
|
23
24
|
|
|
25
|
+
/** Persistent anonymous ID for telemetry fallback when SSO is not authenticated. */
|
|
26
|
+
let _anonymousId: string | undefined;
|
|
27
|
+
|
|
24
28
|
const execFileAsync = promisify(execFile);
|
|
25
29
|
|
|
26
30
|
export class AuthService {
|
|
@@ -312,3 +316,55 @@ export class AuthService {
|
|
|
312
316
|
}
|
|
313
317
|
|
|
314
318
|
export const authService = AuthService.getInstance();
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get or create a persistent anonymous ID for telemetry.
|
|
322
|
+
*
|
|
323
|
+
* Stored in ~/.wave/config.json as { anonymousId: "..." }.
|
|
324
|
+
* Generated once on first run (32-byte random hex) and reused thereafter.
|
|
325
|
+
* Falls back to an in-memory ID if file I/O fails.
|
|
326
|
+
*/
|
|
327
|
+
export function getOrCreateAnonymousId(): string {
|
|
328
|
+
if (_anonymousId) return _anonymousId;
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const configPath = path.join(os.homedir(), ".wave", "config.json");
|
|
332
|
+
if (existsSync(configPath)) {
|
|
333
|
+
const content = readFileSync(configPath, "utf-8");
|
|
334
|
+
const config = JSON.parse(content) as { anonymousId?: string };
|
|
335
|
+
if (config.anonymousId) {
|
|
336
|
+
_anonymousId = config.anonymousId;
|
|
337
|
+
return _anonymousId;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Generate and persist
|
|
342
|
+
_anonymousId = randomBytes(32).toString("hex");
|
|
343
|
+
const waveDir = path.dirname(configPath);
|
|
344
|
+
if (!existsSync(waveDir)) {
|
|
345
|
+
mkdirSync(waveDir, { recursive: true });
|
|
346
|
+
}
|
|
347
|
+
const existing = existsSync(configPath)
|
|
348
|
+
? (JSON.parse(readFileSync(configPath, "utf-8")) as Record<
|
|
349
|
+
string,
|
|
350
|
+
unknown
|
|
351
|
+
>)
|
|
352
|
+
: {};
|
|
353
|
+
writeFileSync(
|
|
354
|
+
configPath,
|
|
355
|
+
JSON.stringify({ ...existing, anonymousId: _anonymousId }, null, 2),
|
|
356
|
+
"utf-8",
|
|
357
|
+
);
|
|
358
|
+
chmodSync(configPath, 0o600);
|
|
359
|
+
} catch {
|
|
360
|
+
// File I/O failed — use in-memory fallback
|
|
361
|
+
_anonymousId = randomBytes(32).toString("hex");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return _anonymousId;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** @internal — reset anonymous ID cache for testing only */
|
|
368
|
+
export function __resetAnonymousIdForTesting(): void {
|
|
369
|
+
_anonymousId = undefined;
|
|
370
|
+
}
|
|
@@ -461,8 +461,7 @@ export class ConfigurationService {
|
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
// Resolve custom headers from environment: env (settings.json) > process.env
|
|
464
|
-
const envCustomHeaders =
|
|
465
|
-
process.env.WAVE_CUSTOM_HEADERS || process.env.WAVE_CUSTOM_HEADERS || "";
|
|
464
|
+
const envCustomHeaders = process.env.WAVE_CUSTOM_HEADERS || "";
|
|
466
465
|
const parsedEnvHeaders = parseCustomHeaders(envCustomHeaders);
|
|
467
466
|
|
|
468
467
|
// Merge headers: env headers < options < override
|
|
@@ -497,19 +496,13 @@ export class ConfigurationService {
|
|
|
497
496
|
maxTokens?: number,
|
|
498
497
|
permissionMode?: PermissionMode,
|
|
499
498
|
): ModelConfig {
|
|
500
|
-
// Resolve agent model: override > options > env (settings.json
|
|
499
|
+
// Resolve agent model: override > options > process.env (includes settings.json env)
|
|
501
500
|
const resolvedAgentModel =
|
|
502
|
-
model ||
|
|
503
|
-
this.options.model ||
|
|
504
|
-
process.env.WAVE_MODEL ||
|
|
505
|
-
process.env.WAVE_MODEL;
|
|
501
|
+
model || this.options.model || process.env.WAVE_MODEL;
|
|
506
502
|
|
|
507
|
-
// Resolve fast model: override > options > env (settings.json
|
|
503
|
+
// Resolve fast model: override > options > process.env (includes settings.json env)
|
|
508
504
|
const resolvedFastModel =
|
|
509
|
-
fastModel ||
|
|
510
|
-
this.options.fastModel ||
|
|
511
|
-
process.env.WAVE_FAST_MODEL ||
|
|
512
|
-
process.env.WAVE_FAST_MODEL;
|
|
505
|
+
fastModel || this.options.fastModel || process.env.WAVE_FAST_MODEL;
|
|
513
506
|
|
|
514
507
|
// Validate required fields
|
|
515
508
|
if (!resolvedAgentModel) {
|
|
@@ -572,8 +565,7 @@ export class ConfigurationService {
|
|
|
572
565
|
}
|
|
573
566
|
|
|
574
567
|
// Try env (settings.json) first, then process.env
|
|
575
|
-
const envMaxInputTokens =
|
|
576
|
-
process.env.WAVE_MAX_INPUT_TOKENS || process.env.WAVE_MAX_INPUT_TOKENS;
|
|
568
|
+
const envMaxInputTokens = process.env.WAVE_MAX_INPUT_TOKENS;
|
|
577
569
|
if (envMaxInputTokens) {
|
|
578
570
|
const parsed = parseInt(envMaxInputTokens, 10);
|
|
579
571
|
if (!isNaN(parsed)) {
|
|
@@ -677,8 +669,7 @@ export class ConfigurationService {
|
|
|
677
669
|
}
|
|
678
670
|
|
|
679
671
|
// Try env (settings.json) first, then process.env
|
|
680
|
-
const envMaxOutputTokens =
|
|
681
|
-
process.env.WAVE_MAX_OUTPUT_TOKENS || process.env.WAVE_MAX_OUTPUT_TOKENS;
|
|
672
|
+
const envMaxOutputTokens = process.env.WAVE_MAX_OUTPUT_TOKENS;
|
|
682
673
|
if (envMaxOutputTokens) {
|
|
683
674
|
const parsed = parseInt(envMaxOutputTokens, 10);
|
|
684
675
|
if (!isNaN(parsed) && parsed > 0) {
|
|
@@ -704,8 +695,7 @@ export class ConfigurationService {
|
|
|
704
695
|
const models = new Set<string>();
|
|
705
696
|
|
|
706
697
|
// Add current model from options or environment
|
|
707
|
-
const currentModel =
|
|
708
|
-
this.options.model || process.env.WAVE_MODEL || process.env.WAVE_MODEL;
|
|
698
|
+
const currentModel = this.options.model || process.env.WAVE_MODEL;
|
|
709
699
|
if (currentModel) {
|
|
710
700
|
models.add(currentModel);
|
|
711
701
|
}
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
parseAgentFile,
|
|
15
15
|
type SubagentConfiguration,
|
|
16
16
|
} from "../utils/subagentParser.js";
|
|
17
|
-
import { resolveMcpConfig } from "../managers/mcpManager.js";
|
|
18
17
|
import { logger } from "../utils/globalLogger.js";
|
|
19
18
|
|
|
20
19
|
export class PluginLoader {
|
|
@@ -143,7 +142,8 @@ export class PluginLoader {
|
|
|
143
142
|
const mcpPath = path.join(pluginPath, ".mcp.json");
|
|
144
143
|
try {
|
|
145
144
|
const content = await fs.readFile(mcpPath, "utf-8");
|
|
146
|
-
|
|
145
|
+
// Return raw config — let McpManager resolve templates and capture originalUrl
|
|
146
|
+
return JSON.parse(content) as McpConfig;
|
|
147
147
|
} catch {
|
|
148
148
|
return undefined;
|
|
149
149
|
}
|
|
@@ -18,7 +18,10 @@ import type {
|
|
|
18
18
|
LogRecordExporter,
|
|
19
19
|
ReadableLogRecord,
|
|
20
20
|
} from "@opentelemetry/sdk-logs";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
AuthService,
|
|
23
|
+
getOrCreateAnonymousId,
|
|
24
|
+
} from "../services/authService.js";
|
|
22
25
|
|
|
23
26
|
// Lazy-loaded OTEL modules — only imported when telemetry is initialized
|
|
24
27
|
let sdkNode: typeof import("@opentelemetry/sdk-node") | undefined;
|
|
@@ -471,8 +474,11 @@ export function isInitialized(): boolean {
|
|
|
471
474
|
export { JsonlSpanExporter, JsonlLogExporter };
|
|
472
475
|
|
|
473
476
|
/**
|
|
474
|
-
* Get telemetry attributes
|
|
475
|
-
*
|
|
477
|
+
* Get telemetry attributes for the current session.
|
|
478
|
+
*
|
|
479
|
+
* Priority:
|
|
480
|
+
* 1. SSO authenticated → server-provided user.id + user.email
|
|
481
|
+
* 2. Not authenticated → persistent anonymous ID from ~/.wave/config.json
|
|
476
482
|
*/
|
|
477
483
|
export function getTelemetryAttributes(): Record<string, string> {
|
|
478
484
|
try {
|
|
@@ -487,5 +493,7 @@ export function getTelemetryAttributes(): Record<string, string> {
|
|
|
487
493
|
} catch {
|
|
488
494
|
// AuthService not available or not authenticated
|
|
489
495
|
}
|
|
490
|
-
|
|
496
|
+
|
|
497
|
+
// Fallback to anonymous ID
|
|
498
|
+
return { "user.id": getOrCreateAnonymousId() };
|
|
491
499
|
}
|
package/src/tools/buildTool.ts
CHANGED
|
@@ -24,8 +24,6 @@ export interface ToolDef {
|
|
|
24
24
|
params: Record<string, unknown>,
|
|
25
25
|
context: ToolContext,
|
|
26
26
|
) => string;
|
|
27
|
-
shouldDefer?: boolean;
|
|
28
|
-
alwaysLoad?: boolean;
|
|
29
27
|
additionalProperties?: boolean;
|
|
30
28
|
}
|
|
31
29
|
|
|
@@ -59,7 +57,5 @@ export function buildTool(def: ToolDef): ToolPlugin {
|
|
|
59
57
|
execute: def.execute,
|
|
60
58
|
prompt: promptFn,
|
|
61
59
|
formatCompactParams: def.formatCompactParams,
|
|
62
|
-
shouldDefer: def.shouldDefer ?? false,
|
|
63
|
-
alwaysLoad: def.alwaysLoad ?? false,
|
|
64
60
|
};
|
|
65
61
|
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
|
|
10
10
|
export const taskCreateTool: ToolPlugin = {
|
|
11
11
|
name: TASK_CREATE_TOOL_NAME,
|
|
12
|
-
shouldDefer: true,
|
|
13
12
|
config: {
|
|
14
13
|
type: "function",
|
|
15
14
|
function: {
|
|
@@ -144,7 +143,6 @@ NOTE that you should not use this tool if there is only one trivial task to do.
|
|
|
144
143
|
|
|
145
144
|
export const taskGetTool: ToolPlugin = {
|
|
146
145
|
name: TASK_GET_TOOL_NAME,
|
|
147
|
-
shouldDefer: true,
|
|
148
146
|
config: {
|
|
149
147
|
type: "function",
|
|
150
148
|
function: {
|
|
@@ -204,7 +202,6 @@ Returns full task details:
|
|
|
204
202
|
|
|
205
203
|
export const taskUpdateTool: ToolPlugin = {
|
|
206
204
|
name: TASK_UPDATE_TOOL_NAME,
|
|
207
|
-
shouldDefer: true,
|
|
208
205
|
config: {
|
|
209
206
|
type: "function",
|
|
210
207
|
function: {
|
|
@@ -559,7 +556,6 @@ Set up task dependencies:
|
|
|
559
556
|
|
|
560
557
|
export const taskListTool: ToolPlugin = {
|
|
561
558
|
name: TASK_LIST_TOOL_NAME,
|
|
562
|
-
shouldDefer: true,
|
|
563
559
|
config: {
|
|
564
560
|
type: "function",
|
|
565
561
|
function: {
|
package/src/tools/types.ts
CHANGED
|
@@ -31,21 +31,6 @@ export interface ToolPlugin {
|
|
|
31
31
|
workdir?: string;
|
|
32
32
|
isSubagent?: boolean;
|
|
33
33
|
}) => string;
|
|
34
|
-
/**
|
|
35
|
-
* When true, this tool is deferred — it's not sent to the API until the model
|
|
36
|
-
* discovers it via ToolSearch. MCP tools are always deferred.
|
|
37
|
-
*/
|
|
38
|
-
shouldDefer?: boolean;
|
|
39
|
-
/**
|
|
40
|
-
* When true, this tool is never deferred — its full schema always appears in
|
|
41
|
-
* the initial prompt even when tool search is enabled.
|
|
42
|
-
*/
|
|
43
|
-
alwaysLoad?: boolean;
|
|
44
|
-
/**
|
|
45
|
-
* When true, this is an MCP tool (auto-set by McpManager). MCP tools are
|
|
46
|
-
* always deferred unless they have alwaysLoad: true.
|
|
47
|
-
*/
|
|
48
|
-
isMcp?: boolean;
|
|
49
34
|
}
|
|
50
35
|
|
|
51
36
|
export interface ToolResult {
|
package/src/types/mcp.ts
CHANGED
|
@@ -26,7 +26,14 @@ export interface McpTool {
|
|
|
26
26
|
export interface McpServerStatus {
|
|
27
27
|
name: string;
|
|
28
28
|
config: McpServerConfig;
|
|
29
|
-
|
|
29
|
+
/** Pre-resolution URL with template variables (e.g. ${WAVE_SSO_TOKEN}) preserved for safe display */
|
|
30
|
+
originalUrl?: string;
|
|
31
|
+
status:
|
|
32
|
+
| "disconnected"
|
|
33
|
+
| "connected"
|
|
34
|
+
| "connecting"
|
|
35
|
+
| "reconnecting"
|
|
36
|
+
| "error";
|
|
30
37
|
tools?: McpTool[];
|
|
31
38
|
toolCount?: number;
|
|
32
39
|
capabilities?: string[];
|
package/src/utils/mcpUtils.ts
CHANGED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ToolSearchTool - Discovers deferred tool schemas on demand.
|
|
3
|
-
*
|
|
4
|
-
* When tool deferral is enabled, deferred tools are not sent to the API.
|
|
5
|
-
* The model must call this tool to discover a deferred tool's full schema
|
|
6
|
-
* before it can invoke it.
|
|
7
|
-
*
|
|
8
|
-
* Query formats:
|
|
9
|
-
* - "select:ToolName" — direct selection by name (comma-separated for multiple)
|
|
10
|
-
* - "notebook jupyter" — keyword search, up to max_results best matches
|
|
11
|
-
* - "+slack send" — require "slack" in the name, rank by remaining terms
|
|
12
|
-
*/
|
|
13
|
-
import { ToolPlugin } from "./types.js";
|
|
14
|
-
export declare const toolSearchTool: ToolPlugin;
|
|
15
|
-
//# sourceMappingURL=toolSearchTool.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"toolSearchTool.d.ts","sourceRoot":"","sources":["../../src/tools/toolSearchTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AA4GjE,eAAO,MAAM,cAAc,EAAE,UAuH5B,CAAC"}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ToolSearchTool - Discovers deferred tool schemas on demand.
|
|
3
|
-
*
|
|
4
|
-
* When tool deferral is enabled, deferred tools are not sent to the API.
|
|
5
|
-
* The model must call this tool to discover a deferred tool's full schema
|
|
6
|
-
* before it can invoke it.
|
|
7
|
-
*
|
|
8
|
-
* Query formats:
|
|
9
|
-
* - "select:ToolName" — direct selection by name (comma-separated for multiple)
|
|
10
|
-
* - "notebook jupyter" — keyword search, up to max_results best matches
|
|
11
|
-
* - "+slack send" — require "slack" in the name, rank by remaining terms
|
|
12
|
-
*/
|
|
13
|
-
import { isDeferredTool, TOOL_SEARCH_TOOL_NAME, } from "../utils/isDeferredTool.js";
|
|
14
|
-
function formatSchema(tool) {
|
|
15
|
-
const desc = tool.config.function.description || "";
|
|
16
|
-
const params = JSON.stringify(tool.config.function.parameters || {}, null, 2);
|
|
17
|
-
return `${tool.name}: ${desc}\nParameters: ${params}`;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Parse tool name into searchable parts (handles CamelCase and underscores).
|
|
21
|
-
*/
|
|
22
|
-
function parseToolName(name) {
|
|
23
|
-
return name
|
|
24
|
-
.replace(/([a-z])([A-Z])/g, "$1 $2") // CamelCase to spaces
|
|
25
|
-
.replace(/_/g, " ")
|
|
26
|
-
.toLowerCase()
|
|
27
|
-
.split(/\s+/)
|
|
28
|
-
.filter(Boolean);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Keyword search over deferred tools by name and description.
|
|
32
|
-
* Matches Claude Code's scoring: required terms (+prefix) must all match,
|
|
33
|
-
* optional terms contribute to ranking.
|
|
34
|
-
*/
|
|
35
|
-
function keywordSearch(query, deferredTools, maxResults) {
|
|
36
|
-
const queryLower = query.toLowerCase().trim();
|
|
37
|
-
const queryTerms = queryLower.split(/\s+/).filter(Boolean);
|
|
38
|
-
// Exact match fast path
|
|
39
|
-
const exact = deferredTools.find((t) => t.name.toLowerCase() === queryLower);
|
|
40
|
-
if (exact)
|
|
41
|
-
return [exact];
|
|
42
|
-
// Partition into required (+prefixed) and optional terms
|
|
43
|
-
const requiredTerms = [];
|
|
44
|
-
const optionalTerms = [];
|
|
45
|
-
for (const term of queryTerms) {
|
|
46
|
-
if (term.startsWith("+") && term.length > 1) {
|
|
47
|
-
requiredTerms.push(term.slice(1));
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
optionalTerms.push(term);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const allScoringTerms = requiredTerms.length > 0
|
|
54
|
-
? [...requiredTerms, ...optionalTerms]
|
|
55
|
-
: queryTerms;
|
|
56
|
-
// Pre-filter to tools matching ALL required terms
|
|
57
|
-
let candidateTools = deferredTools;
|
|
58
|
-
if (requiredTerms.length > 0) {
|
|
59
|
-
candidateTools = deferredTools.filter((tool) => {
|
|
60
|
-
const parts = parseToolName(tool.name);
|
|
61
|
-
const desc = (tool.config.function.description || "").toLowerCase();
|
|
62
|
-
return requiredTerms.every((term) => parts.includes(term) ||
|
|
63
|
-
parts.some((p) => p.includes(term)) ||
|
|
64
|
-
desc.includes(term));
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
// Score each tool
|
|
68
|
-
const scored = candidateTools
|
|
69
|
-
.map((tool) => {
|
|
70
|
-
const parts = parseToolName(tool.name);
|
|
71
|
-
const desc = (tool.config.function.description || "").toLowerCase();
|
|
72
|
-
let score = 0;
|
|
73
|
-
for (const term of allScoringTerms) {
|
|
74
|
-
// Exact part match (high weight)
|
|
75
|
-
if (parts.includes(term)) {
|
|
76
|
-
score += tool.isMcp ? 12 : 10;
|
|
77
|
-
}
|
|
78
|
-
else if (parts.some((p) => p.includes(term))) {
|
|
79
|
-
score += tool.isMcp ? 6 : 5;
|
|
80
|
-
}
|
|
81
|
-
// Full name fallback
|
|
82
|
-
if (tool.name.toLowerCase().includes(term) && score === 0) {
|
|
83
|
-
score += 3;
|
|
84
|
-
}
|
|
85
|
-
// Description match
|
|
86
|
-
if (desc.includes(term)) {
|
|
87
|
-
score += 2;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return { tool, score };
|
|
91
|
-
})
|
|
92
|
-
.filter((s) => s.score > 0)
|
|
93
|
-
.sort((a, b) => b.score - a.score)
|
|
94
|
-
.slice(0, maxResults)
|
|
95
|
-
.map((s) => s.tool);
|
|
96
|
-
return scored;
|
|
97
|
-
}
|
|
98
|
-
export const toolSearchTool = {
|
|
99
|
-
name: TOOL_SEARCH_TOOL_NAME,
|
|
100
|
-
config: {
|
|
101
|
-
type: "function",
|
|
102
|
-
function: {
|
|
103
|
-
name: TOOL_SEARCH_TOOL_NAME,
|
|
104
|
-
description: `Fetches full schema definitions for deferred tools so they can be called.
|
|
105
|
-
|
|
106
|
-
Deferred tools appear by name and description in <available-deferred-tools> messages. The full parameter schema is NOT loaded yet — use this tool to fetch it before invoking a deferred tool. This tool takes a query, matches it against the deferred tool list, and returns the matched tools' complete JSONSchema definitions inside a <functions> block. Once a tool's schema appears in that result, it is callable exactly like any tool defined at the top of the prompt.
|
|
107
|
-
|
|
108
|
-
Result format: each matched tool appears as one <function>{"description": "...", "name": "...", "parameters": {...}}`,
|
|
109
|
-
parameters: {
|
|
110
|
-
type: "object",
|
|
111
|
-
properties: {
|
|
112
|
-
query: {
|
|
113
|
-
type: "string",
|
|
114
|
-
description: 'Search query for finding deferred tools. Supports: "select:ToolName" for direct lookup, or keyword search like "notebook jupyter". Use "+term" to require a term (e.g. "+slack send").',
|
|
115
|
-
},
|
|
116
|
-
max_results: {
|
|
117
|
-
type: "number",
|
|
118
|
-
description: "Maximum number of results to return for keyword search (default: 5).",
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
required: ["query"],
|
|
122
|
-
additionalProperties: false,
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
shouldDefer: false, // Always available
|
|
127
|
-
execute: async (args, context) => {
|
|
128
|
-
const { query, max_results = 5 } = args;
|
|
129
|
-
if (!query) {
|
|
130
|
-
return {
|
|
131
|
-
success: false,
|
|
132
|
-
content: "",
|
|
133
|
-
error: "Missing required 'query' parameter",
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
if (!context.toolManager) {
|
|
137
|
-
return {
|
|
138
|
-
success: false,
|
|
139
|
-
content: "",
|
|
140
|
-
error: "ToolManager not available in context",
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
const allTools = context.toolManager.list();
|
|
144
|
-
const deferredTools = allTools.filter(isDeferredTool);
|
|
145
|
-
// Handle select: prefix
|
|
146
|
-
const selectMatch = query.match(/^select:(.+)$/i);
|
|
147
|
-
if (selectMatch) {
|
|
148
|
-
const requested = selectMatch[1]
|
|
149
|
-
.split(",")
|
|
150
|
-
.map((s) => s.trim())
|
|
151
|
-
.filter(Boolean);
|
|
152
|
-
const found = [];
|
|
153
|
-
const missing = [];
|
|
154
|
-
for (const toolName of requested) {
|
|
155
|
-
const tool = deferredTools.find((t) => t.name === toolName) ??
|
|
156
|
-
allTools.find((t) => t.name === toolName);
|
|
157
|
-
if (tool) {
|
|
158
|
-
if (!found.some((f) => f.name === tool.name))
|
|
159
|
-
found.push(tool);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
missing.push(toolName);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (found.length === 0) {
|
|
166
|
-
return {
|
|
167
|
-
success: false,
|
|
168
|
-
content: "",
|
|
169
|
-
error: `No matching deferred tools found for: ${missing.join(", ")}`,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const result = found.map(formatSchema).join("\n\n---\n\n");
|
|
173
|
-
const shortResult = `Discovered tools: ${found.map((t) => t.name).join(", ")}`;
|
|
174
|
-
return {
|
|
175
|
-
success: true,
|
|
176
|
-
content: result,
|
|
177
|
-
shortResult,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
// Keyword search
|
|
181
|
-
const matches = keywordSearch(query, deferredTools, max_results);
|
|
182
|
-
if (matches.length === 0) {
|
|
183
|
-
return {
|
|
184
|
-
success: false,
|
|
185
|
-
content: "",
|
|
186
|
-
error: `No matching deferred tools found for query: "${query}". Available deferred tools: ${getDeferredToolNamesList(deferredTools)}`,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
const result = matches.map(formatSchema).join("\n\n---\n\n");
|
|
190
|
-
const shortResult = `Found ${matches.length} tools: ${matches.map((t) => t.name).join(", ")}`;
|
|
191
|
-
return {
|
|
192
|
-
success: true,
|
|
193
|
-
content: result,
|
|
194
|
-
shortResult,
|
|
195
|
-
};
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
function getDeferredToolNamesList(tools) {
|
|
199
|
-
return tools.map((t) => t.name).join(", ");
|
|
200
|
-
}
|