praxis-agent 0.49.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/application/session-service.d.ts +1 -0
- package/dist/application/session-service.js +28 -8
- package/dist/application/subagent-service.d.ts +1 -0
- package/dist/application/subagent-service.js +11 -6
- package/dist/cli-runtime.d.ts +5 -0
- package/dist/cli-runtime.js +16 -0
- package/dist/core/runtime.js +3 -3
- package/dist/hooks/claude-hook-tools.d.ts +3 -1
- package/dist/hooks/claude-hook-tools.js +8 -1
- package/dist/permissions/claude-permission-resolver.js +1 -0
- package/dist/tools/deferred-tool-catalog.d.ts +11 -0
- package/dist/tools/deferred-tool-catalog.js +145 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -212,7 +212,11 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
212
212
|
imports, memory, skills, commands, agents, hooks, settings, MCP servers,
|
|
213
213
|
plugins, and append-only `praxis.transcript` JSONL sessions under `~/.praxis`,
|
|
214
214
|
with bounded MCP connection, discovery, and tool operations plus safe
|
|
215
|
-
disconnect recovery that never replays an already-dispatched call.
|
|
215
|
+
disconnect recovery that never replays an already-dispatched call. Default
|
|
216
|
+
tool selection defers `mcp__*` schemas behind a turn-scoped `ToolSearch`;
|
|
217
|
+
each query activates at most eight deterministic matches for the next model
|
|
218
|
+
request. Explicit concrete `--tools` selections load selected tools directly, while
|
|
219
|
+
`--disallowedTools ToolSearch` restores the complete tool list.
|
|
216
220
|
- **Provider-neutral models** — native Provider Registry/Vault routing, API
|
|
217
221
|
adapters, an experimental Codex OAuth adapter, explicit capability checks,
|
|
218
222
|
separate per-attempt connect, byte-idle, and absolute-total timeouts, typed
|
|
@@ -304,7 +308,7 @@ normal/low-capability full-frame p95 budgets of `<16.7/<33 ms`.
|
|
|
304
308
|
`npm run test:coverage` measures all production code under `src/**` with V8 and
|
|
305
309
|
enforces global floors of 79% statements, 70% branches, 85% functions, and 81% lines,
|
|
306
310
|
and rejects any production runtime module with zero covered statements (while allowing
|
|
307
|
-
type-only modules). `npm run test:fixtures` executes the
|
|
311
|
+
type-only modules). `npm run test:fixtures` executes the 70-behavior native contract; 62 behaviors
|
|
308
312
|
are qualified and 8 are explicitly excluded. `npm run verify:fixture-contracts`
|
|
309
313
|
performs the structural check and is part of `npm run check`.
|
|
310
314
|
`npm run test:core-completion` is retained as a compatibility alias for
|
|
@@ -39,6 +39,7 @@ export interface ClaudeSessionServiceOptions {
|
|
|
39
39
|
claudeVersion: string;
|
|
40
40
|
provider?: ModelProvider;
|
|
41
41
|
tools?: ToolRegistry;
|
|
42
|
+
deferMcpTools?: boolean;
|
|
42
43
|
permissions?: PermissionResolver;
|
|
43
44
|
permissionResolverForMode?: (mode: AgentPermissionMode) => PermissionResolver;
|
|
44
45
|
permissionMode?: ClaudePermissionMode;
|
|
@@ -49,6 +49,7 @@ import { ClaudeWorktreeToolRegistry } from '../tools/claude-worktree-tools.js';
|
|
|
49
49
|
import { completeMeteredModelRequest } from './metered-model-completion.js';
|
|
50
50
|
import { SessionMemoryController, SessionMemoryStateError, SessionMemoryStore, } from './session-memory.js';
|
|
51
51
|
import { FilteredToolRegistry } from '../tools/filtered-tool-registry.js';
|
|
52
|
+
import { DeferredToolCatalog } from '../tools/deferred-tool-catalog.js';
|
|
52
53
|
import { ClaudeCapabilityToolRegistry, resolveClaudeToolCapabilities, } from '../tools/claude-capabilities.js';
|
|
53
54
|
import { generateToolUseSummary } from './tool-use-summary.js';
|
|
54
55
|
import { ClaudeUserMessageToolRegistry, } from '../tools/claude-user-message.js';
|
|
@@ -1108,6 +1109,9 @@ export class ClaudeSessionService {
|
|
|
1108
1109
|
? { providerForModel: this.options.providerForModel }
|
|
1109
1110
|
: {}),
|
|
1110
1111
|
baseTools: wrappedBase,
|
|
1112
|
+
...(this.options.deferMcpTools === undefined
|
|
1113
|
+
? {}
|
|
1114
|
+
: { deferMcpTools: this.options.deferMcpTools }),
|
|
1111
1115
|
permissions: this.options.permissions,
|
|
1112
1116
|
...(this.options.permissionResolverForMode
|
|
1113
1117
|
? {
|
|
@@ -2846,6 +2850,9 @@ export class ClaudeSessionService {
|
|
|
2846
2850
|
? { providerForModel: this.options.providerForModel }
|
|
2847
2851
|
: {}),
|
|
2848
2852
|
baseTools,
|
|
2853
|
+
...(this.options.deferMcpTools === undefined
|
|
2854
|
+
? {}
|
|
2855
|
+
: { deferMcpTools: this.options.deferMcpTools }),
|
|
2849
2856
|
permissions: turnPermissions,
|
|
2850
2857
|
...(this.options.permissionResolverForMode
|
|
2851
2858
|
? {
|
|
@@ -3024,9 +3031,16 @@ export class ClaudeSessionService {
|
|
|
3024
3031
|
const structuredTools = this.options.structuredOutputSchema && structuredCapture
|
|
3025
3032
|
? new StructuredOutputRegistry(agentScopedTools ?? this.options.tools ?? emptyToolRegistry, this.options.structuredOutputSchema, structuredCapture)
|
|
3026
3033
|
: agentScopedTools;
|
|
3027
|
-
const
|
|
3034
|
+
const activeTurnTools = structuredTools
|
|
3035
|
+
? new DeferredToolCatalog(structuredTools).startTurn({
|
|
3036
|
+
enabled: this.options.deferMcpTools !== false &&
|
|
3037
|
+
!(agent && agent.tools !== undefined),
|
|
3038
|
+
restoredToolNames: unresolvedToolCalls.map((call) => call.name),
|
|
3039
|
+
})
|
|
3040
|
+
: undefined;
|
|
3041
|
+
const hookTools = this.options.hooks && activeTurnTools && turnPermissions
|
|
3028
3042
|
? new ClaudeHookToolCoordinator({
|
|
3029
|
-
tools:
|
|
3043
|
+
tools: activeTurnTools,
|
|
3030
3044
|
permissions: turnPermissions,
|
|
3031
3045
|
hooks: this.options.hooks,
|
|
3032
3046
|
session: hookSession,
|
|
@@ -3063,7 +3077,7 @@ export class ClaudeSessionService {
|
|
|
3063
3077
|
...(hookTools
|
|
3064
3078
|
? { tools: hookTools, permissions: hookTools }
|
|
3065
3079
|
: {
|
|
3066
|
-
...(
|
|
3080
|
+
...(activeTurnTools ? { tools: activeTurnTools } : {}),
|
|
3067
3081
|
...(turnPermissions ? { permissions: turnPermissions } : {}),
|
|
3068
3082
|
}),
|
|
3069
3083
|
});
|
|
@@ -3446,8 +3460,8 @@ export class ClaudeSessionService {
|
|
|
3446
3460
|
let compactionDurationMs;
|
|
3447
3461
|
let compactionDurationWithoutRetriesMs;
|
|
3448
3462
|
let compactionModelUsage;
|
|
3449
|
-
const
|
|
3450
|
-
? (
|
|
3463
|
+
const currentDefinitions = () => provider.capabilities.tools
|
|
3464
|
+
? (activeTurnTools?.definitions() ?? [])
|
|
3451
3465
|
: [];
|
|
3452
3466
|
const budget = this.contextBudget(provider);
|
|
3453
3467
|
const contextEngine = new ContextEngine({
|
|
@@ -3526,7 +3540,7 @@ export class ClaudeSessionService {
|
|
|
3526
3540
|
...pendingMessages,
|
|
3527
3541
|
]),
|
|
3528
3542
|
],
|
|
3529
|
-
tools:
|
|
3543
|
+
tools: currentDefinitions(),
|
|
3530
3544
|
};
|
|
3531
3545
|
},
|
|
3532
3546
|
irreducible: () => ({
|
|
@@ -3540,12 +3554,13 @@ export class ClaudeSessionService {
|
|
|
3540
3554
|
})),
|
|
3541
3555
|
]),
|
|
3542
3556
|
],
|
|
3543
|
-
tools:
|
|
3557
|
+
tools: currentDefinitions(),
|
|
3544
3558
|
}),
|
|
3545
3559
|
propose: async () => {
|
|
3546
3560
|
const activeNativeLease = nativeLease;
|
|
3547
3561
|
if (!budget)
|
|
3548
3562
|
throw new Error('Context budget is unavailable');
|
|
3563
|
+
const definitions = currentDefinitions();
|
|
3549
3564
|
const historyMessages = activeTurnMessages();
|
|
3550
3565
|
if (historyMessages.length === 0)
|
|
3551
3566
|
throw new Error('Cannot compact an empty native transcript');
|
|
@@ -3754,7 +3769,10 @@ export class ClaudeSessionService {
|
|
|
3754
3769
|
]),
|
|
3755
3770
|
];
|
|
3756
3771
|
return {
|
|
3757
|
-
envelope: {
|
|
3772
|
+
envelope: {
|
|
3773
|
+
messages: proposedMessages,
|
|
3774
|
+
tools: definitions,
|
|
3775
|
+
},
|
|
3758
3776
|
commit: async () => {
|
|
3759
3777
|
if (signal?.aborted)
|
|
3760
3778
|
throw new AgentRunCancelledError();
|
|
@@ -4062,6 +4080,7 @@ export class ClaudeSessionService {
|
|
|
4062
4080
|
};
|
|
4063
4081
|
}
|
|
4064
4082
|
if (shellCommand === undefined && budget) {
|
|
4083
|
+
const definitions = currentDefinitions();
|
|
4065
4084
|
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4066
4085
|
budget.assertFits(budget.evaluate([
|
|
4067
4086
|
...contextMessages,
|
|
@@ -4387,6 +4406,7 @@ export class ClaudeSessionService {
|
|
|
4387
4406
|
...contextMessages,
|
|
4388
4407
|
...injectTurnContext(memorySnapshot),
|
|
4389
4408
|
];
|
|
4409
|
+
const definitions = currentDefinitions();
|
|
4390
4410
|
const currentContextTokens = contextEngine.report({
|
|
4391
4411
|
messages: providerVisibleMessages,
|
|
4392
4412
|
tools: definitions,
|
|
@@ -59,6 +59,7 @@ export interface ClaudeSubagentExecutorOptions {
|
|
|
59
59
|
claudeVersion: string;
|
|
60
60
|
provider: ModelProvider;
|
|
61
61
|
baseTools: ToolRegistry;
|
|
62
|
+
deferMcpTools?: boolean;
|
|
62
63
|
permissions: PermissionResolver;
|
|
63
64
|
permissionResolverForMode?: (mode: AgentPermissionMode) => PermissionResolver;
|
|
64
65
|
parentPermissionMode?: () => AgentPermissionMode;
|
|
@@ -10,6 +10,7 @@ import { ContextBudget } from '../core/context-budget.js';
|
|
|
10
10
|
import { AgentRuntime, } from '../core/runtime.js';
|
|
11
11
|
import { composePermissionResolvers } from '../core/permission-policy.js';
|
|
12
12
|
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
13
|
+
import { DeferredToolCatalog } from '../tools/deferred-tool-catalog.js';
|
|
13
14
|
import { BUILTIN_STATUSLINE_AGENT_PATH, } from '../extensions/claude-extensions.js';
|
|
14
15
|
import { ClaudeHookToolCoordinator } from '../hooks/claude-hook-tools.js';
|
|
15
16
|
import { InMemorySidechainStore } from '../persistence/in-memory-sidechain-store.js';
|
|
@@ -2059,9 +2060,14 @@ export class ClaudeSubagentExecutor {
|
|
|
2059
2060
|
? new RestrictedToolRegistry(structuredOnlyTools, ['Read', 'Edit'])
|
|
2060
2061
|
: structuredOnlyTools, options.outputSchema, options.structuredOutput)
|
|
2061
2062
|
: scopedTools;
|
|
2063
|
+
const activeAgentTools = new DeferredToolCatalog(agentTools).startTurn({
|
|
2064
|
+
enabled: this.options.deferMcpTools !== false &&
|
|
2065
|
+
customAgent?.tools === undefined,
|
|
2066
|
+
restoredToolNames: nativeRecoveryCalls.map((call) => call.name),
|
|
2067
|
+
});
|
|
2062
2068
|
const runtimeTools = scopedHooks
|
|
2063
2069
|
? new ClaudeHookToolCoordinator({
|
|
2064
|
-
tools:
|
|
2070
|
+
tools: activeAgentTools,
|
|
2065
2071
|
permissions,
|
|
2066
2072
|
hooks: scopedHooks,
|
|
2067
2073
|
session: hookSession,
|
|
@@ -2072,7 +2078,7 @@ export class ClaudeSubagentExecutor {
|
|
|
2072
2078
|
}
|
|
2073
2079
|
: {}),
|
|
2074
2080
|
})
|
|
2075
|
-
:
|
|
2081
|
+
: activeAgentTools;
|
|
2076
2082
|
const runtimePermissions = scopedHooks
|
|
2077
2083
|
? runtimeTools
|
|
2078
2084
|
: permissions;
|
|
@@ -2241,9 +2247,7 @@ export class ClaudeSubagentExecutor {
|
|
|
2241
2247
|
: { reserveTokens: this.options.contextReserveTokens }),
|
|
2242
2248
|
})
|
|
2243
2249
|
: null;
|
|
2244
|
-
const
|
|
2245
|
-
? runtimeTools.definitions()
|
|
2246
|
-
: [];
|
|
2250
|
+
const currentDefinitions = () => options.provider.capabilities.tools ? runtimeTools.definitions() : [];
|
|
2247
2251
|
let observedMessages;
|
|
2248
2252
|
let stableSystemMessageCount = 0;
|
|
2249
2253
|
const assembleMessages = async () => {
|
|
@@ -2272,6 +2276,7 @@ export class ClaudeSubagentExecutor {
|
|
|
2272
2276
|
];
|
|
2273
2277
|
observedMessages = messages;
|
|
2274
2278
|
if (contextBudget) {
|
|
2279
|
+
const definitions = currentDefinitions();
|
|
2275
2280
|
contextBudget.assertFits(contextBudget.evaluate(messages, definitions));
|
|
2276
2281
|
}
|
|
2277
2282
|
return messages;
|
|
@@ -2393,7 +2398,7 @@ export class ClaudeSubagentExecutor {
|
|
|
2393
2398
|
...(options.signal ? { signal: options.signal } : {}),
|
|
2394
2399
|
};
|
|
2395
2400
|
const result = await runtime.run(runtimeRequest);
|
|
2396
|
-
contextBudget?.observeUsage(result.usage, observedMessages ?? [],
|
|
2401
|
+
contextBudget?.observeUsage(result.usage, observedMessages ?? [], currentDefinitions());
|
|
2397
2402
|
this.options.eventSink?.({
|
|
2398
2403
|
type: 'task-progress',
|
|
2399
2404
|
taskId: options.agentId,
|
package/dist/cli-runtime.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ import { executeProviderAuthCommand } from './cli/provider-auth-command.js';
|
|
|
24
24
|
import { type PluginEvalDependencies } from './plugins/claude-plugin-eval.js';
|
|
25
25
|
import { type ProjectEvalDependencies } from './evals/project-eval.js';
|
|
26
26
|
import { type SelfUpdateResult } from './maintenance/self-update.js';
|
|
27
|
+
export declare function shouldDeferMcpTools(input: {
|
|
28
|
+
simpleMode: boolean;
|
|
29
|
+
tools?: readonly string[] | undefined;
|
|
30
|
+
disallowedTools: readonly string[];
|
|
31
|
+
}): boolean;
|
|
27
32
|
export { parseContextEnvironment, parseProviderEnvironment };
|
|
28
33
|
export interface CliIO {
|
|
29
34
|
stdout(message: string | Uint8Array): void;
|
package/dist/cli-runtime.js
CHANGED
|
@@ -77,6 +77,16 @@ import { formatDoctorReport, runDoctor } from './maintenance/doctor.js';
|
|
|
77
77
|
import { runSelfUpdate, } from './maintenance/self-update.js';
|
|
78
78
|
import { executeClaudeProjectPurge, planClaudeProjectPurge, } from './application/claude-project-purge.js';
|
|
79
79
|
const VERSION = createRequire(import.meta.url)('../package.json').version;
|
|
80
|
+
export function shouldDeferMcpTools(input) {
|
|
81
|
+
if (input.tools?.includes('ToolSearch')) {
|
|
82
|
+
throw new Error('Unknown tool in --tools: ToolSearch');
|
|
83
|
+
}
|
|
84
|
+
return (!input.simpleMode &&
|
|
85
|
+
!input.disallowedTools.includes('ToolSearch') &&
|
|
86
|
+
(input.tools === undefined ||
|
|
87
|
+
(input.tools.length > 0 &&
|
|
88
|
+
input.tools.every((tool) => tool === 'default'))));
|
|
89
|
+
}
|
|
80
90
|
function fileResourceBaseUrl(environment, providerEnvironment) {
|
|
81
91
|
return (environment.PRAXIS_FILES_BASE_URL ??
|
|
82
92
|
providerEnvironment?.baseUrl ??
|
|
@@ -981,6 +991,11 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
981
991
|
? {}
|
|
982
992
|
: { permissionMode: interactivePermissionMode }),
|
|
983
993
|
}, cwd);
|
|
994
|
+
const deferMcpTools = shouldDeferMcpTools({
|
|
995
|
+
simpleMode,
|
|
996
|
+
tools: cli.tools,
|
|
997
|
+
disallowedTools: cli.disallowedTools,
|
|
998
|
+
});
|
|
984
999
|
if (experimentalNativeTranscriptWrites) {
|
|
985
1000
|
const incompatible = [
|
|
986
1001
|
['sessionPersistence', cli.sessionPersistence === false],
|
|
@@ -1208,6 +1223,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1208
1223
|
pricing: ModelPricingRegistry.fromEnvironment(runtimeEnvironment.PRAXIS_PRICING_JSON),
|
|
1209
1224
|
}),
|
|
1210
1225
|
collectMetrics: true,
|
|
1226
|
+
deferMcpTools,
|
|
1211
1227
|
...(!experimentalNativeTranscriptWrites && sessionMemoryProviderFactory
|
|
1212
1228
|
? { sessionMemoryProviderFactory }
|
|
1213
1229
|
: {}),
|
package/dist/core/runtime.js
CHANGED
|
@@ -337,9 +337,6 @@ export class AgentRuntime {
|
|
|
337
337
|
let linesRemoved = 0;
|
|
338
338
|
let activeAttemptHasPresentation = false;
|
|
339
339
|
let activeAttemptDiscarded = false;
|
|
340
|
-
const definitions = this.provider.capabilities.tools
|
|
341
|
-
? (this.options.tools?.definitions() ?? [])
|
|
342
|
-
: [];
|
|
343
340
|
const maxModelTurns = request.maxModelTurns ?? this.options.maxModelTurns;
|
|
344
341
|
if (maxModelTurns !== undefined &&
|
|
345
342
|
(!Number.isSafeInteger(maxModelTurns) || maxModelTurns <= 0)) {
|
|
@@ -414,6 +411,9 @@ export class AgentRuntime {
|
|
|
414
411
|
stableSystemMessageCount: request.stableSystemMessageCount,
|
|
415
412
|
}),
|
|
416
413
|
};
|
|
414
|
+
const definitions = this.provider.capabilities.tools
|
|
415
|
+
? (this.options.tools?.definitions() ?? [])
|
|
416
|
+
: [];
|
|
417
417
|
if (definitions.length > 0)
|
|
418
418
|
providerRequest.tools = definitions;
|
|
419
419
|
if (request.signal)
|
|
@@ -20,9 +20,11 @@ export declare class ClaudeHookToolCoordinator implements ToolRegistry, Permissi
|
|
|
20
20
|
private readonly prepared;
|
|
21
21
|
constructor(options: ClaudeHookToolCoordinatorOptions);
|
|
22
22
|
definitions(): readonly import("../core/runtime.js").ModelToolDefinition[];
|
|
23
|
-
schedulingPolicy(): {
|
|
23
|
+
schedulingPolicy(call: ModelToolCall): {
|
|
24
24
|
concurrency: "exclusive";
|
|
25
25
|
startAfterAssistant: boolean;
|
|
26
|
+
abortGroupOnError?: boolean;
|
|
27
|
+
cancelOnInterrupt?: boolean;
|
|
26
28
|
};
|
|
27
29
|
prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
|
|
28
30
|
resolve(call: ModelToolCall, context?: PermissionResolutionContext): Promise<PermissionDecision>;
|
|
@@ -8,8 +8,15 @@ export class ClaudeHookToolCoordinator {
|
|
|
8
8
|
definitions() {
|
|
9
9
|
return this.options.tools.definitions();
|
|
10
10
|
}
|
|
11
|
-
schedulingPolicy() {
|
|
11
|
+
schedulingPolicy(call) {
|
|
12
|
+
const wrapped = this.options.tools.schedulingPolicy?.(call);
|
|
12
13
|
return {
|
|
14
|
+
...(wrapped?.cancelOnInterrupt === undefined
|
|
15
|
+
? {}
|
|
16
|
+
: { cancelOnInterrupt: wrapped.cancelOnInterrupt }),
|
|
17
|
+
...(wrapped?.abortGroupOnError === undefined
|
|
18
|
+
? {}
|
|
19
|
+
: { abortGroupOnError: wrapped.abortGroupOnError }),
|
|
13
20
|
concurrency: 'exclusive',
|
|
14
21
|
startAfterAssistant: true,
|
|
15
22
|
};
|
|
@@ -12,6 +12,7 @@ import { validateSedSafety } from './sed-safety.js';
|
|
|
12
12
|
import { parseShellRule, shellRuleMatches } from './shell-rule-matching.js';
|
|
13
13
|
import { effectiveAdditionalDirectories, effectivePermissionMode, filePermissionSuggestions, permissionRuleValueFromString, permissionRuleStringIsValid, permissionRuleValueToString, shellInputIsReadOnly, shellPermissionSuggestions, shellSubcommands, skillPermissionSuggestions, } from './permission-updates.js';
|
|
14
14
|
const DEFAULT_BEHAVIOR = {
|
|
15
|
+
ToolSearch: 'allow',
|
|
15
16
|
Agent: 'allow',
|
|
16
17
|
SendMessage: 'allow',
|
|
17
18
|
SendUserMessage: 'allow',
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolRegistry } from '../core/runtime.js';
|
|
2
|
+
export declare class DeferredToolCatalog {
|
|
3
|
+
private readonly base;
|
|
4
|
+
private readonly definitions;
|
|
5
|
+
constructor(base: ToolRegistry);
|
|
6
|
+
startTurn(options?: {
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
restoredToolNames?: readonly string[];
|
|
9
|
+
}): ToolRegistry;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=deferred-tool-catalog.d.ts.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
2
|
+
const TOOL_SEARCH = 'ToolSearch';
|
|
3
|
+
const MAX_MATCHES = 8;
|
|
4
|
+
const isDeferred = (definition) => definition.name.startsWith('mcp__');
|
|
5
|
+
const truncateCodePoints = (value, max) => Array.from(value).slice(0, max).join('');
|
|
6
|
+
const toolSearchDefinition = {
|
|
7
|
+
name: TOOL_SEARCH,
|
|
8
|
+
description: 'Search available MCP tools. A successful search activates matching tools, whose schemas appear on the next model request.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
query: { type: 'string', minLength: 1, maxLength: 256 },
|
|
13
|
+
},
|
|
14
|
+
required: ['query'],
|
|
15
|
+
additionalProperties: false,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
function invalidSearch() {
|
|
19
|
+
return { content: 'Invalid ToolSearch input', isError: true };
|
|
20
|
+
}
|
|
21
|
+
function summary(definition) {
|
|
22
|
+
const text = definition.description || '(no description)';
|
|
23
|
+
return Array.from(text).length > 240
|
|
24
|
+
? `${truncateCodePoints(text, 237)}...`
|
|
25
|
+
: text;
|
|
26
|
+
}
|
|
27
|
+
class ActiveDeferredToolRegistry {
|
|
28
|
+
base;
|
|
29
|
+
definitionsByOrder;
|
|
30
|
+
active = new Set();
|
|
31
|
+
deferred;
|
|
32
|
+
constructor(base, definitionsByOrder, restoredToolNames) {
|
|
33
|
+
this.base = base;
|
|
34
|
+
this.definitionsByOrder = definitionsByOrder;
|
|
35
|
+
this.deferred = definitionsByOrder.filter(isDeferred);
|
|
36
|
+
for (const name of restoredToolNames) {
|
|
37
|
+
if (this.deferred.some((definition) => definition.name === name))
|
|
38
|
+
this.active.add(name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
definitions() {
|
|
42
|
+
const visible = this.definitionsByOrder.filter((definition) => !isDeferred(definition) || this.active.has(definition.name));
|
|
43
|
+
return [...visible, toolSearchDefinition];
|
|
44
|
+
}
|
|
45
|
+
schedulingPolicy(call) {
|
|
46
|
+
if (call.name === TOOL_SEARCH)
|
|
47
|
+
return { concurrency: 'exclusive', cancelOnInterrupt: true };
|
|
48
|
+
this.assertAvailable(call.name);
|
|
49
|
+
return resolveToolSchedulingPolicy(this.base, call);
|
|
50
|
+
}
|
|
51
|
+
async prepare(call, context) {
|
|
52
|
+
if (call.name === TOOL_SEARCH)
|
|
53
|
+
return call;
|
|
54
|
+
this.assertAvailable(call.name);
|
|
55
|
+
return this.base.prepare(call, context);
|
|
56
|
+
}
|
|
57
|
+
async execute(call, context) {
|
|
58
|
+
if (call.name === TOOL_SEARCH)
|
|
59
|
+
return this.search(call);
|
|
60
|
+
this.assertAvailable(call.name);
|
|
61
|
+
return this.base.execute(call, context);
|
|
62
|
+
}
|
|
63
|
+
assertAvailable(name) {
|
|
64
|
+
if (this.deferred.some((definition) => definition.name === name) &&
|
|
65
|
+
!this.active.has(name)) {
|
|
66
|
+
throw new Error(`Tool ${name} is inactive; call ToolSearch first`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
search(call) {
|
|
70
|
+
const input = call.input;
|
|
71
|
+
if (!input ||
|
|
72
|
+
typeof input !== 'object' ||
|
|
73
|
+
Array.isArray(input) ||
|
|
74
|
+
Object.keys(input).length !== 1 ||
|
|
75
|
+
typeof input.query !== 'string' ||
|
|
76
|
+
input.query.trim().length < 1 ||
|
|
77
|
+
Array.from(input.query).length > 256)
|
|
78
|
+
return invalidSearch();
|
|
79
|
+
const query = input.query.trim().toLowerCase();
|
|
80
|
+
const tokens = query.split(/\s+/);
|
|
81
|
+
const candidates = this.deferred
|
|
82
|
+
.map((definition, index) => ({ definition, index }))
|
|
83
|
+
.filter(({ definition }) => {
|
|
84
|
+
const haystack = `${definition.name} ${definition.description}`.toLowerCase();
|
|
85
|
+
return tokens.every((token) => haystack.includes(token));
|
|
86
|
+
})
|
|
87
|
+
.sort((left, right) => {
|
|
88
|
+
const score = (item) => {
|
|
89
|
+
const name = item.definition.name.toLowerCase();
|
|
90
|
+
if (name === query)
|
|
91
|
+
return 0;
|
|
92
|
+
if (name.startsWith(query))
|
|
93
|
+
return 1;
|
|
94
|
+
if (name.includes(query))
|
|
95
|
+
return 2;
|
|
96
|
+
if (item.definition.description.toLowerCase().includes(query))
|
|
97
|
+
return 3;
|
|
98
|
+
return 4;
|
|
99
|
+
};
|
|
100
|
+
return score(left) - score(right) || left.index - right.index;
|
|
101
|
+
})
|
|
102
|
+
.slice(0, MAX_MATCHES);
|
|
103
|
+
const newlyActivated = candidates.filter(({ definition }) => !this.active.has(definition.name));
|
|
104
|
+
const alreadyActive = candidates.filter(({ definition }) => this.active.has(definition.name));
|
|
105
|
+
for (const { definition } of candidates)
|
|
106
|
+
this.active.add(definition.name);
|
|
107
|
+
const names = candidates.map(({ definition }) => definition.name);
|
|
108
|
+
let content = names.length
|
|
109
|
+
? 'Matching MCP tools were found; their schemas will appear on the next model request.'
|
|
110
|
+
: 'No matching MCP tools found; no schemas were activated.';
|
|
111
|
+
if (names.length) {
|
|
112
|
+
if (newlyActivated.length) {
|
|
113
|
+
content += `\nActivated: ${newlyActivated.map(({ definition }) => definition.name).join(', ')}`;
|
|
114
|
+
}
|
|
115
|
+
if (alreadyActive.length) {
|
|
116
|
+
content += `\nAlready active: ${alreadyActive.map(({ definition }) => definition.name).join(', ')}`;
|
|
117
|
+
}
|
|
118
|
+
content += `\n${candidates.map(({ definition }) => `${definition.name}: ${summary(definition)}`).join('\n')}`;
|
|
119
|
+
}
|
|
120
|
+
return { content: truncateCodePoints(content, 4096), isError: false };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export class DeferredToolCatalog {
|
|
124
|
+
base;
|
|
125
|
+
definitions;
|
|
126
|
+
constructor(base) {
|
|
127
|
+
this.base = base;
|
|
128
|
+
this.definitions = [...base.definitions()];
|
|
129
|
+
}
|
|
130
|
+
startTurn(options = {}) {
|
|
131
|
+
const deferred = this.definitions.some(isDeferred);
|
|
132
|
+
if (options.enabled === false || !deferred)
|
|
133
|
+
return this.base;
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
for (const definition of this.definitions) {
|
|
136
|
+
if (seen.has(definition.name))
|
|
137
|
+
throw new Error(`Duplicate tool definition: ${definition.name}`);
|
|
138
|
+
seen.add(definition.name);
|
|
139
|
+
}
|
|
140
|
+
if (seen.has(TOOL_SEARCH))
|
|
141
|
+
throw new Error(`Tool definition collision: ${TOOL_SEARCH}`);
|
|
142
|
+
return new ActiveDeferredToolRegistry(this.base, this.definitions, options.restoredToolNames ?? []);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=deferred-tool-catalog.js.map
|