feat: add allowlist denylist and preset list for tools
This commit is contained in:
parent
48fbec6659
commit
62f285a558
|
|
@ -146,7 +146,9 @@ export class Agent {
|
||||||
maxTurns: this.config.maxTurns,
|
maxTurns: this.config.maxTurns,
|
||||||
maxTokens: this.config.maxTokens,
|
maxTokens: this.config.maxTokens,
|
||||||
temperature: this.config.temperature,
|
temperature: this.config.temperature,
|
||||||
|
toolPreset: this.config.toolPreset,
|
||||||
allowedTools: this.config.tools,
|
allowedTools: this.config.tools,
|
||||||
|
disallowedTools: this.config.disallowedTools,
|
||||||
agentName: this.name,
|
agentName: this.name,
|
||||||
agentRole: this.config.systemPrompt?.slice(0, 50) ?? 'assistant',
|
agentRole: this.config.systemPrompt?.slice(0, 50) ?? 'assistant',
|
||||||
loopDetection: this.config.loopDetection,
|
loopDetection: this.config.loopDetection,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import type {
|
||||||
TraceEvent,
|
TraceEvent,
|
||||||
LoopDetectionConfig,
|
LoopDetectionConfig,
|
||||||
LoopDetectionInfo,
|
LoopDetectionInfo,
|
||||||
|
LLMToolDef,
|
||||||
} from '../types.js'
|
} from '../types.js'
|
||||||
import { TokenBudgetExceededError } from '../errors.js'
|
import { TokenBudgetExceededError } from '../errors.js'
|
||||||
import { LoopDetector } from './loop-detector.js'
|
import { LoopDetector } from './loop-detector.js'
|
||||||
|
|
@ -35,6 +36,17 @@ import { emitTrace } from '../utils/trace.js'
|
||||||
import type { ToolRegistry } from '../tool/framework.js'
|
import type { ToolRegistry } from '../tool/framework.js'
|
||||||
import type { ToolExecutor } from '../tool/executor.js'
|
import type { ToolExecutor } from '../tool/executor.js'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Tool presets
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Predefined tool sets for common agent use cases. */
|
||||||
|
export const TOOL_PRESETS = {
|
||||||
|
readonly: ['file_read', 'grep'],
|
||||||
|
readwrite: ['file_read', 'file_write', 'file_edit', 'grep'],
|
||||||
|
full: ['file_read', 'file_write', 'file_edit', 'grep', 'bash'],
|
||||||
|
} as const satisfies Record<string, readonly string[]>
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Public interfaces
|
// Public interfaces
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -60,11 +72,15 @@ export interface RunnerOptions {
|
||||||
/** AbortSignal that cancels any in-flight adapter call and stops the loop. */
|
/** AbortSignal that cancels any in-flight adapter call and stops the loop. */
|
||||||
readonly abortSignal?: AbortSignal
|
readonly abortSignal?: AbortSignal
|
||||||
/**
|
/**
|
||||||
* Whitelist of tool names this runner is allowed to use.
|
* Tool access control configuration.
|
||||||
* When provided, only tools whose name appears in this list are sent to the
|
* - `toolPreset`: Predefined tool sets for common use cases
|
||||||
* LLM. When omitted, all registered tools are available.
|
* - `allowedTools`: Whitelist of tool names (allowlist)
|
||||||
|
* - `disallowedTools`: Blacklist of tool names (denylist)
|
||||||
|
* Tools are resolved in order: preset → allowlist → denylist
|
||||||
*/
|
*/
|
||||||
|
readonly toolPreset?: 'readonly' | 'readwrite' | 'full'
|
||||||
readonly allowedTools?: readonly string[]
|
readonly allowedTools?: readonly string[]
|
||||||
|
readonly disallowedTools?: readonly string[]
|
||||||
/** Display name of the agent driving this runner (used in tool context). */
|
/** Display name of the agent driving this runner (used in tool context). */
|
||||||
readonly agentName?: string
|
readonly agentName?: string
|
||||||
/** Short role description of the agent (used in tool context). */
|
/** Short role description of the agent (used in tool context). */
|
||||||
|
|
@ -180,6 +196,52 @@ export class AgentRunner {
|
||||||
this.maxTurns = options.maxTurns ?? 10
|
this.maxTurns = options.maxTurns ?? 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Tool resolution
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the final set of tools available to this agent based on the
|
||||||
|
* three-layer configuration: preset → allowlist → denylist.
|
||||||
|
*
|
||||||
|
* Returns LLMToolDef[] for direct use with LLM adapters.
|
||||||
|
*/
|
||||||
|
private resolveTools(): LLMToolDef[] {
|
||||||
|
// Validate configuration for contradictions
|
||||||
|
if (this.options.allowedTools && this.options.disallowedTools) {
|
||||||
|
const overlap = this.options.allowedTools.filter(tool =>
|
||||||
|
this.options.disallowedTools!.includes(tool)
|
||||||
|
)
|
||||||
|
if (overlap.length > 0) {
|
||||||
|
console.warn(
|
||||||
|
`AgentRunner: tool "${overlap[0]}" appears in both allowedTools and disallowedTools. ` +
|
||||||
|
'This is contradictory and may lead to unexpected behavior.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tools = this.toolRegistry.toToolDefs()
|
||||||
|
|
||||||
|
// 1. Apply preset filter if set
|
||||||
|
if (this.options.toolPreset) {
|
||||||
|
const presetTools = new Set(TOOL_PRESETS[this.options.toolPreset] as readonly string[])
|
||||||
|
tools = tools.filter(t => presetTools.has(t.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Apply allowlist filter if set
|
||||||
|
if (this.options.allowedTools) {
|
||||||
|
tools = tools.filter(t => this.options.allowedTools!.includes(t.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Apply denylist filter if set
|
||||||
|
if (this.options.disallowedTools) {
|
||||||
|
const denied = new Set(this.options.disallowedTools)
|
||||||
|
tools = tools.filter(t => !denied.has(t.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return tools
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Public API
|
// Public API
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
@ -241,12 +303,8 @@ export class AgentRunner {
|
||||||
let budgetExceeded = false
|
let budgetExceeded = false
|
||||||
|
|
||||||
// Build the stable LLM options once; model / tokens / temp don't change.
|
// Build the stable LLM options once; model / tokens / temp don't change.
|
||||||
// toToolDefs() returns LLMToolDef[] (inputSchema, camelCase) — matches
|
// resolveTools() returns LLMToolDef[] with three-layer filtering applied.
|
||||||
// LLMChatOptions.tools from types.ts directly.
|
const toolDefs = this.resolveTools()
|
||||||
const allDefs = this.toolRegistry.toToolDefs()
|
|
||||||
const toolDefs = this.options.allowedTools
|
|
||||||
? allDefs.filter(d => this.options.allowedTools!.includes(d.name))
|
|
||||||
: allDefs
|
|
||||||
|
|
||||||
// Per-call abortSignal takes precedence over the static one.
|
// Per-call abortSignal takes precedence over the static one.
|
||||||
const effectiveAbortSignal = options.abortSignal ?? this.options.abortSignal
|
const effectiveAbortSignal = options.abortSignal ?? this.options.abortSignal
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,10 @@ export interface AgentConfig {
|
||||||
readonly systemPrompt?: string
|
readonly systemPrompt?: string
|
||||||
/** Names of tools (from the tool registry) available to this agent. */
|
/** Names of tools (from the tool registry) available to this agent. */
|
||||||
readonly tools?: readonly string[]
|
readonly tools?: readonly string[]
|
||||||
|
/** Names of tools explicitly disallowed for this agent. */
|
||||||
|
readonly disallowedTools?: readonly string[]
|
||||||
|
/** Predefined tool preset for common use cases. */
|
||||||
|
readonly toolPreset?: 'readonly' | 'readwrite' | 'full'
|
||||||
readonly maxTurns?: number
|
readonly maxTurns?: number
|
||||||
readonly maxTokens?: number
|
readonly maxTokens?: number
|
||||||
/** Maximum cumulative tokens (input + output) allowed for this run. */
|
/** Maximum cumulative tokens (input + output) allowed for this run. */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue