feat(tools): integrate Profile tools config with runner
Add mergeToolsConfig function to combine Profile tools config with CLI options. Profile config serves as base, CLI options override. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9467ac9fff
commit
3c0f132ff8
2 changed files with 104 additions and 12 deletions
|
|
@ -11,6 +11,7 @@ import {
|
|||
DEFAULT_CONTEXT_TOKENS,
|
||||
type ContextWindowGuardResult,
|
||||
} from "./context-window/index.js";
|
||||
import { mergeToolsConfig, type ToolsConfig } from "./tools/policy.js";
|
||||
|
||||
/**
|
||||
* Get API Key based on provider.
|
||||
|
|
@ -249,7 +250,21 @@ export class Agent {
|
|||
}
|
||||
|
||||
this.agent.setModel(model);
|
||||
this.agent.setTools(resolveTools(options));
|
||||
|
||||
// Merge Profile tools config with options.tools (options takes precedence)
|
||||
const profileToolsConfig = this.profile?.getToolsConfig();
|
||||
const mergedToolsConfig = mergeToolsConfig(profileToolsConfig, options.tools);
|
||||
const toolsOptions = mergedToolsConfig ? { ...options, tools: mergedToolsConfig } : options;
|
||||
|
||||
const tools = resolveTools(toolsOptions);
|
||||
if (this.debug) {
|
||||
if (profileToolsConfig) {
|
||||
console.error(`[debug] Profile tools config: ${JSON.stringify(profileToolsConfig)}`);
|
||||
}
|
||||
console.error(`[debug] Merged tools config: ${JSON.stringify(mergedToolsConfig)}`);
|
||||
console.error(`[debug] Resolved ${tools.length} tools: ${tools.map(t => t.name).join(", ") || "(none)"}`);
|
||||
}
|
||||
this.agent.setTools(tools);
|
||||
|
||||
const restoredMessages = this.session.loadMessages();
|
||||
if (restoredMessages.length > 0) {
|
||||
|
|
|
|||
|
|
@ -170,9 +170,11 @@ function resolveProviderPolicy(
|
|||
* Get subagent tool policy.
|
||||
*/
|
||||
export function getSubagentPolicy(extraDeny?: string[]): ToolPolicy {
|
||||
return {
|
||||
deny: mergeDeny(DEFAULT_SUBAGENT_TOOL_DENY, extraDeny),
|
||||
};
|
||||
const deny = mergeDeny(DEFAULT_SUBAGENT_TOOL_DENY, extraDeny);
|
||||
if (deny) {
|
||||
return { deny };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -214,10 +216,13 @@ export function filterTools(
|
|||
|
||||
// Layer 2: Global allow/deny
|
||||
if (config?.allow || config?.deny) {
|
||||
const globalPolicy: ToolPolicy = {
|
||||
allow: config.allow,
|
||||
deny: config.deny,
|
||||
};
|
||||
const globalPolicy: ToolPolicy = {};
|
||||
if (config.allow) {
|
||||
globalPolicy.allow = config.allow;
|
||||
}
|
||||
if (config.deny) {
|
||||
globalPolicy.deny = config.deny;
|
||||
}
|
||||
filtered = filterToolsByPolicy(filtered, globalPolicy);
|
||||
}
|
||||
|
||||
|
|
@ -238,6 +243,75 @@ export function filterTools(
|
|||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two ToolsConfig objects.
|
||||
* The override config takes precedence:
|
||||
* - profile: override wins if set
|
||||
* - allow: union of both
|
||||
* - deny: union of both
|
||||
* - byProvider: deep merge with override taking precedence
|
||||
*/
|
||||
export function mergeToolsConfig(
|
||||
base?: ToolsConfig,
|
||||
override?: ToolsConfig,
|
||||
): ToolsConfig | undefined {
|
||||
if (!base && !override) return undefined;
|
||||
if (!base) return override;
|
||||
if (!override) return base;
|
||||
|
||||
const result: ToolsConfig = {};
|
||||
|
||||
// profile: override wins
|
||||
const profile = override.profile ?? base.profile;
|
||||
if (profile) {
|
||||
result.profile = profile;
|
||||
}
|
||||
|
||||
// allow: union
|
||||
const allow = mergeAllow(base.allow, override.allow);
|
||||
if (allow) {
|
||||
result.allow = allow;
|
||||
}
|
||||
|
||||
// deny: union
|
||||
const deny = mergeDeny(base.deny, override.deny);
|
||||
if (deny) {
|
||||
result.deny = deny;
|
||||
}
|
||||
|
||||
// byProvider: deep merge
|
||||
if (base.byProvider || override.byProvider) {
|
||||
const providers = new Set([
|
||||
...Object.keys(base.byProvider ?? {}),
|
||||
...Object.keys(override.byProvider ?? {}),
|
||||
]);
|
||||
|
||||
const byProvider: Record<string, ToolPolicy> = {};
|
||||
for (const provider of providers) {
|
||||
const basePolicy = base.byProvider?.[provider];
|
||||
const overridePolicy = override.byProvider?.[provider];
|
||||
|
||||
if (basePolicy && overridePolicy) {
|
||||
const merged: ToolPolicy = {};
|
||||
const pAllow = mergeAllow(basePolicy.allow, overridePolicy.allow);
|
||||
if (pAllow) {
|
||||
merged.allow = pAllow;
|
||||
}
|
||||
const pDeny = mergeDeny(basePolicy.deny, overridePolicy.deny);
|
||||
if (pDeny) {
|
||||
merged.deny = pDeny;
|
||||
}
|
||||
byProvider[provider] = merged;
|
||||
} else {
|
||||
byProvider[provider] = overridePolicy ?? basePolicy!;
|
||||
}
|
||||
}
|
||||
result.byProvider = byProvider;
|
||||
}
|
||||
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific tool would be allowed given the options.
|
||||
*/
|
||||
|
|
@ -257,10 +331,13 @@ export function wouldToolBeAllowed(
|
|||
|
||||
// Layer 2: Global allow/deny
|
||||
if (config?.allow || config?.deny) {
|
||||
const globalPolicy: ToolPolicy = {
|
||||
allow: config.allow,
|
||||
deny: config.deny,
|
||||
};
|
||||
const globalPolicy: ToolPolicy = {};
|
||||
if (config.allow) {
|
||||
globalPolicy.allow = config.allow;
|
||||
}
|
||||
if (config.deny) {
|
||||
globalPolicy.deny = config.deny;
|
||||
}
|
||||
if (!isToolAllowed(toolName, globalPolicy)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue