import { Disposable } from '#/_base/di/lifecycle'; import { LifecycleScope } from '#/app/scopes'; import { ScopeActivation, registerScopedService } from '#/_base/di/scope'; import { IAgentProfileService, ProfileError, ProfileErrors } from '#/agent/profile/profile'; import { TOOLS_SECTION, type ToolsConfig } from './configSection'; import { IAgentToolExecutorService } from '#/agent/toolExecutor/toolExecutor'; import { IConfigService } from '#/app/config/config'; import { ISessionToolPolicy } from '#/session/sessionToolPolicy/sessionToolPolicy'; import { ISessionToolPolicyGate } from '#/session/sessionToolPolicyGate/sessionToolPolicyGate'; import { SELECT_TOOLS_TOOL_NAME } from '#/agent/toolSelect/toolSelect'; import type { ToolSource } from '#/tool/toolContract'; import { isToolActiveComposed, type ToolActivationPolicy } from './evaluate'; import { IAgentToolPolicyService } from './toolPolicy'; export class AgentToolPolicyService extends Disposable implements IAgentToolPolicyService { declare readonly _serviceBrand: undefined; constructor( @IAgentProfileService private readonly profile: IAgentProfileService, @IConfigService private readonly config: IConfigService, @ISessionToolPolicy private readonly sessionToolPolicy: ISessionToolPolicy, @ISessionToolPolicyGate private readonly toolPolicyGate: ISessionToolPolicyGate, @IAgentToolExecutorService toolExecutor: IAgentToolExecutorService, ) { super(); this._register( toolExecutor.registerToolCallGuard(({ name, source }) => { const active = name === SELECT_TOOLS_TOOL_NAME ? this.isToolActiveForDisclosure(name, source) : this.isToolActive(name, source); return active ? undefined : `Tool "${name}" is disabled by the active tool policy`; }), ); } isToolActive(name: string, source: ToolSource = 'builtin'): boolean { const profile = this.profile.data(); return this.isToolActiveForProfile( { tools: profile.activeToolNames, disallowedTools: profile.disallowedTools, }, name, source, ); } isToolActiveForDisclosure(name: string, source: ToolSource = 'builtin'): boolean { const profile = this.profile.data(); return isToolActiveComposed( { workspaceDisabledTools: this.toolPolicyGate.disabledTools, profile: { disallowedTools: profile.disallowedTools }, global: this.config.get(TOOLS_SECTION), sessionDisabledTools: this.sessionToolPolicy.disabledTools(), }, name, source, ); } isToolActiveForProfile( profile: ToolActivationPolicy, name: string, source: ToolSource = 'builtin', ): boolean { return isToolActiveComposed( { workspaceDisabledTools: this.toolPolicyGate.disabledTools, profile, global: this.config.get(TOOLS_SECTION), sessionDisabledTools: this.sessionToolPolicy.disabledTools(), }, name, source, ); } async setSessionDisabledTools(names: readonly string[]): Promise { if (this.profile.data().profileName === undefined) { throw new ProfileError( ProfileErrors.codes.PROFILE_NOT_BOUND, 'Cannot set session disabled tools: agent profile is not bound', ); } await this.sessionToolPolicy.setDisabledTools(names); } } registerScopedService( LifecycleScope.Agent, IAgentToolPolicyService, AgentToolPolicyService, ScopeActivation.OnScopeCreated, 'toolPolicy', );