import { Disposable } from '#/_base/di/lifecycle'; import { LifecycleScope } from '#/app/scopes'; import { ScopeActivation, registerScopedService } from '#/_base/di/scope'; import { defineState } from '#/state/state'; import { UNKNOWN_CAPABILITY, type ModelCapability } from '#/llm-adapter/contract/capability'; import { type ThinkingEffort } from '#human/llm/thinking'; import { IModelCatalog, type Model } from '#/llm-adapter/model/catalog'; import { type ModelOverrides } from '#/llm-adapter/model/model.types'; import { type ModelRequestParams, type SamplingOptions } from '#/llm-adapter/model/model-requester'; import { IProtocolAdapterRegistry } from '#/llm-adapter/protocol/protocol'; import { drivesThinkingThroughTraits, modelSupportsThinkingEffort, normalizeRequestedThinkingEffort, resolveForcedThinkingEffort, resolveThinkingEffortForModel, resolveThinkingKeep, requiresStrictThinkingValidation, type ThinkingConfig, } from '#/llm-adapter/model/thinking'; import { THINKING_SECTION } from '#/app/kosongConfig/configSection'; import { DEFAULT_AGENT_PROFILE_NAME } from '#/app/agentProfileCatalog/agentProfileCatalog'; import { IBuiltinAgentProfileLoader } from '#/app/agentProfileCatalog/builtinAgentProfileLoader'; import { ErrorCodes, Error2 } from "#/errors"; import { IAgentIdentity } from '#/app/agentIdentity/agentIdentity'; import { IBootstrapService } from '#/app/bootstrap/bootstrap'; import { IConfigService } from '#/app/config/config'; import type { LoopControl } from '#/agent/loop/configSection'; import { IAgentRuntimeService } from '#/agent/runtimeBinding/agentRuntime'; import { RuntimeWorkspaceView } from '#/runtime/runtimeWorkspaceView'; import { ISessionContext } from '#/session/sessionContext/sessionContext'; import type { ToolSource } from '#/tool/toolContract'; import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceContext'; import { ISessionInstructionsProvider } from '#/session/sessionInstructions/instructionsProvider'; import { ISessionSkillCatalog } from '#/features/skill/session/skillCatalog'; import { ISessionAgentProfileCatalog } from '#/session/sessionAgentProfileCatalog/sessionAgentProfileCatalog'; import { ISessionToolPolicy } from '#/session/sessionToolPolicy/sessionToolPolicy'; import { ISessionToolPolicyGate } from '#/session/sessionToolPolicyGate/sessionToolPolicyGate'; import { IPluginService } from '#/app/plugin/plugin'; import type { ResolvedAgentProfile, SystemPromptContext } from '#/agent/profile/profile'; import { IAgentScopeContext } from '#/agent/scopeContext/scopeContext'; import { IAgentStateService } from '#/agent/state/agentState'; import { IAgentAgentsMdReminderService } from '#/agent/agentsMdReminder/agentsMdReminder'; import { ITelemetryService } from '#/app/telemetry/telemetry'; import { IEventDispatcher } from '#/state/eventDispatcher'; import { extractAgentsMdPathsFromSystemPrompt, prepareSystemPromptContext, type LoadedAgentsMd, } from './context'; import type { ApplyProfileOptions, BindAgentInput, ProfileBindingSnapshot, ProfileData, ProfileModelContext, ProfileServiceOptions, ProfileSetModelResult, ProfileUpdateData, } from './profile'; import { IAgentProfileService, ProfileError, ProfileErrors } from './profile'; import { TOOLS_SECTION, type ToolsConfig } from '#/agent/toolPolicy/configSection'; import { isToolActiveComposed, findInactiveToolPatterns, literalToolNames, type InactiveToolPattern } from '#/agent/toolPolicy/evaluate'; import { IAgentToolRegistryService } from '#/agent/toolRegistry/toolRegistry'; import { ISessionNotify } from '#/features/notify/sessionNotify'; import { NOTIFY_USER_TOOL_NAME } from '#/features/notify/tools/notify-user/notify-user'; import { renderAgentProfilePrompt } from '#/app/agentProfileCatalog/profile-shared'; import { getAgentToolContributions } from '#/agent/toolRegistry/toolContribution'; import { profileActiveToolsKey, ConfigUpdate, ProfileBind, profileKey, ToolsResetActiveTools, ToolsSetActiveTools, WarningIssued, type ActiveToolsState, type ConfigUpdatePayload, type ProfileModelState, } from './profileOps'; import { AgentStatusUpdated } from '#/agent/usage/usageEvents'; export type { WarningEvent } from '#/errors'; function describeInactiveToolPattern( context: string, field: string, issue: InactiveToolPattern, ): string { switch (issue.kind) { case 'unknown-tool': return `Tool pattern "${issue.pattern}" in ${context} ${field} does not match any registered or built-in tool; it will never activate anything.`; case 'wildcard-not-mcp': return `Tool pattern "${issue.pattern}" in ${context} ${field} uses wildcards, which only match MCP tools (names starting with "mcp__"); it will never activate anything.`; case 'incomplete-mcp-name': return `Tool pattern "${issue.pattern}" in ${context} ${field} matches no tool; use "${issue.pattern}__*" to match the whole MCP server.`; } } export const PLUGIN_SECTIONS_MAX_BYTES = 64 * 1024; export const profileActiveToolNamesOverlayKey = defineState( 'profile.activeToolNamesOverlay', () => undefined as readonly string[] | undefined, ); export const profileAgentsMdWarningKey = defineState( 'profile.agentsMdWarning', () => undefined as string | undefined, ); export const profileEmittedThinkingEffortWarningsKey = defineState>( 'profile.emittedThinkingEffortWarnings', () => new Set(), ); export const profileEmittedToolPatternWarningsKey = defineState>( 'profile.emittedToolPatternWarnings', () => new Set(), ); export const profileEmittedPluginBudgetWarningsKey = defineState>( 'profile.emittedPluginBudgetWarnings', () => new Set(), ); export class AgentProfileService extends Disposable implements IAgentProfileService { declare readonly _serviceBrand: undefined; private optionsValue: ProfileServiceOptions = {}; private get activeToolNames(): ActiveToolsState { return ( this.activeToolNamesOverlay ?? (this.states.get(profileActiveToolsKey) as ActiveToolsState) ); } private activeProfile: ResolvedAgentProfile | undefined; private frozenSkillListing: string | undefined; private frozenPluginSections: string | undefined; constructor( @IEventDispatcher private readonly dispatcher: IEventDispatcher, @ITelemetryService private readonly telemetry: ITelemetryService, @IConfigService private readonly config: IConfigService, @IModelCatalog private readonly modelCatalog: IModelCatalog, @IProtocolAdapterRegistry private readonly protocolAdapters: IProtocolAdapterRegistry, @IAgentRuntimeService private readonly runtime: IAgentRuntimeService, @ISessionContext private readonly sessionContext: ISessionContext, @IBootstrapService private readonly bootstrap: IBootstrapService, @ISessionNotify private readonly notify: ISessionNotify, @ISessionWorkspaceContext private readonly workspace: ISessionWorkspaceContext, @ISessionAgentProfileCatalog private readonly catalog: ISessionAgentProfileCatalog, @ISessionSkillCatalog private readonly skillCatalog: ISessionSkillCatalog, @ISessionInstructionsProvider private readonly instructions: ISessionInstructionsProvider, @ISessionToolPolicy private readonly sessionToolPolicy: ISessionToolPolicy, @ISessionToolPolicyGate private readonly toolPolicyGate: ISessionToolPolicyGate, @IAgentToolRegistryService private readonly toolRegistry: IAgentToolRegistryService, @IBuiltinAgentProfileLoader private readonly builtinProfiles: IBuiltinAgentProfileLoader, @IAgentScopeContext private readonly scopeContext: IAgentScopeContext, @IAgentStateService private readonly states: IAgentStateService, @IPluginService private readonly plugins: IPluginService, @IAgentIdentity private readonly identity: IAgentIdentity, @IAgentAgentsMdReminderService private readonly agentsMdReminder: IAgentAgentsMdReminderService, ) { super(); this.states.contributeState(profileKey); this.states.contributeState(profileActiveToolsKey); this.states.contributeState(profileActiveToolNamesOverlayKey); this.states.contributeState(profileAgentsMdWarningKey); this.states.contributeState(profileEmittedThinkingEffortWarningsKey); this.states.contributeState(profileEmittedToolPatternWarningsKey); this.states.contributeState(profileEmittedPluginBudgetWarningsKey); this.configure({}); this._register( this.dispatcher.hooks.onDidRestore.register('profile', async (_ctx, next) => { this.syncTelemetryModelContext(this.modelAlias); await next(); }), ); this._register( this.config.onDidSectionChange(({ domain }) => { if (domain === TOOLS_SECTION) { this.publishToolPatternWarnings(); } }), ); } private get activeToolNamesOverlay(): readonly string[] | undefined { return this.states.get(profileActiveToolNamesOverlayKey); } private set activeToolNamesOverlay(value: readonly string[] | undefined) { this.states.set(profileActiveToolNamesOverlayKey, value); } private get agentsMdWarning(): string | undefined { return this.states.get(profileAgentsMdWarningKey); } private set agentsMdWarning(value: string | undefined) { this.states.set(profileAgentsMdWarningKey, value); } private get emittedThinkingEffortWarnings(): Set { return this.states.get(profileEmittedThinkingEffortWarningsKey); } private get emittedToolPatternWarnings(): Set { return this.states.get(profileEmittedToolPatternWarningsKey); } private get emittedPluginBudgetWarnings(): Set { return this.states.get(profileEmittedPluginBudgetWarningsKey); } configure(options: ProfileServiceOptions): void { this.optionsValue = { emitStatusUpdated: options.emitStatusUpdated ?? this.optionsValue.emitStatusUpdated, }; } update(changed: ProfileUpdateData): void { const { activeToolNames, ...configChanged } = changed; if ( changed.profileName !== undefined && this.activeProfile?.name !== changed.profileName ) { this.activeProfile = undefined; } if (Object.keys(configChanged).length > 0) { void this.dispatcher.dispatch(new ConfigUpdate(this.resolveConfigPayload(configChanged))); this.afterConfigDispatch(configChanged); } if (activeToolNames !== undefined) { this.setActiveTools(activeToolNames); } } applyBindingSnapshot(snapshot: ProfileBindingSnapshot): void { this.activeProfile = undefined; this.activeToolNamesOverlay = undefined; const agentsMdPaths = snapshot.agentsMdPaths ?? extractAgentsMdPathsFromSystemPrompt(snapshot.systemPrompt); void this.dispatcher.dispatch( new ProfileBind({ agentId: this.scopeContext.agentId, modelAlias: snapshot.modelAlias, profileName: snapshot.profileName, thinkingEffort: snapshot.thinkingLevel, systemPrompt: snapshot.systemPrompt, environmentDisclosure: snapshot.environmentDisclosure, renderGeneration: snapshot.renderGeneration, agentsMdPaths, activeToolNames: snapshot.activeToolNames, disallowedTools: snapshot.disallowedTools ?? [], subagents: snapshot.subagents, }), ); this.afterConfigDispatch({ modelAlias: snapshot.modelAlias, profileName: snapshot.profileName, thinkingLevel: snapshot.thinkingLevel, systemPrompt: snapshot.systemPrompt, environmentDisclosure: snapshot.environmentDisclosure, agentsMdPaths, disallowedTools: snapshot.disallowedTools ?? [], }); this.agentsMdReminder.seedInjected(agentsMdPaths, this.sessionContext.cwd); } async bind(input: BindAgentInput): Promise { await this.catalog.ready; await this.identity.resolved(); this.assertBindable(input.profile); const profile = this.catalog.get(input.profile); if (profile === undefined) { const available = this.catalog .list() .map((p) => p.name) .join(', '); throw new ProfileError( ProfileErrors.codes.PROFILE_UNKNOWN, `Unknown agent profile: "${input.profile}". Available profiles: ${available}`, { profile: input.profile, available }, ); } const alias = input.model ?? this.config.get('defaultModel'); if (alias === undefined || alias === '') { throw new ProfileError( ProfileErrors.codes.MODEL_NOT_CONFIGURED, `model is required to bind profile "${input.profile}" (no default model configured)`, ); } const model = this.modelCatalog.get(alias); if (input.strictThinking === true && input.thinking !== undefined) { this.assertThinkingEffortSupported(input.thinking, model, alias); } await this.sessionToolPolicy.ready; const context = await this.buildSystemPromptContext(profile); this.assertBindable(profile.name); const currentProfileName = this.profileName; const rendered = renderAgentProfilePrompt(profile, context); this.activeProfile = profile; this.cacheAgentsMdWarning(context); const thinkingLevel = this.resolveThinkingEffort( input.thinking ?? (currentProfileName !== undefined ? this.thinkingLevel : undefined), model, ); this.activeToolNamesOverlay = undefined; await this.dispatcher.dispatch(new ProfileBind({ agentId: this.scopeContext.agentId, modelAlias: alias, profileName: profile.name, thinkingEffort: thinkingLevel, systemPrompt: rendered.text, environmentDisclosure: rendered.environment, agentsMdPaths: context.agentsMdPaths ?? [], activeToolNames: profile.tools, disallowedTools: profile.disallowedTools ?? [], subagents: profile.subagents, })); this.afterConfigDispatch({ modelAlias: alias, profileName: profile.name, thinkingLevel, systemPrompt: rendered.text, disallowedTools: profile.disallowedTools ?? [], }); this.seedAgentsMdReminder(context); this.publishAgentsMdWarning(); this.publishToolPatternWarnings(profile); } async setModel(alias: string): Promise { const model = this.modelCatalog.get(alias); if (this.profileName === undefined) { await this.bind({ profile: DEFAULT_AGENT_PROFILE_NAME, model: alias }); this.telemetry.track2('model_switch', { model: alias }); } else if (this.modelAlias !== alias) { this.update({ modelAlias: alias }); this.telemetry.track2('model_switch', { model: alias }); } return { model: alias, providerName: model.providerName, }; } setThinking(level: string): void { const previousEffort = this.thinkingLevel; this.assertThinkingEffortSupported(level, this.tryResolveRawModel(), this.modelAlias ?? ''); const normalized = normalizeRequestedThinkingEffort(level); this.update({ thinkingLevel: normalized ?? level }); const effort = this.thinkingLevel; if (effort !== previousEffort) { this.telemetry.track2('thinking_toggle', { enabled: effort !== 'off', effort, from: previousEffort, }); } } private assertThinkingEffortSupported( requested: string, model: Model | undefined, modelAlias: string, ): void { const normalized = normalizeRequestedThinkingEffort(requested); if (normalized === undefined || this.supportsThinkingEffort(normalized, model)) return; const efforts = model?.supportEfforts ?? []; const supported = efforts.length === 0 ? 'off' : ['off', ...efforts].join(', '); throw new ProfileError( ProfileErrors.codes.MODEL_CONFIG_INVALID, `Thinking effort "${requested}" is not supported by model "${modelAlias}". Supported efforts: ${supported}.`, ); } getModel(): string { return this.modelAlias ?? ''; } useProfile(profile: ResolvedAgentProfile, context: SystemPromptContext): void { this.activeProfile = profile; const rendered = renderAgentProfilePrompt(profile, context); this.update({ profileName: profile.name, systemPrompt: rendered.text, environmentDisclosure: rendered.environment, agentsMdPaths: context.agentsMdPaths ?? [], disallowedTools: profile.disallowedTools ?? [], }); this.setActiveTools(profile.tools); } async applyProfile(profile: ResolvedAgentProfile, options?: ApplyProfileOptions): Promise { const context = await this.buildSystemPromptContext(profile, options); this.useProfile(profile, context); this.seedAgentsMdReminder(context); this.cacheAgentsMdWarning(context); this.publishAgentsMdWarning(); this.publishToolPatternWarnings(profile); } private seedAgentsMdReminder(context: SystemPromptContext): void { this.agentsMdReminder.seedInjected( context.agentsMdPaths ?? [], context.cwd ?? this.sessionContext.cwd, ); } getAgentsMdWarning(): string | undefined { return this.agentsMdWarning; } data(): ProfileData { const model = this.tryResolveRawModel(); return { modelAlias: this.modelAlias, modelCapabilities: model?.capabilities ?? UNKNOWN_CAPABILITY, profileName: this.profileName, thinkingLevel: this.thinkingLevel, systemPrompt: this.systemPrompt, agentsMdPaths: this.profileState.agentsMdPaths, activeToolNames: this.activeToolNames === undefined ? undefined : [...this.activeToolNames], disallowedTools: [...(this.profileState.disallowedTools ?? [])], subagents: this.profileState.subagents === undefined ? undefined : [...this.profileState.subagents], environmentDisclosure: this.profileState.environmentDisclosure, renderGeneration: this.profileState.renderGeneration, }; } getEffectiveThinkingLevel(): ThinkingEffort { return this.resolveThinkingState(this.tryResolveRawModel()).effective; } resolveModelContext(): ProfileModelContext { const modelAlias = this.model; const model = this.modelCatalog.get(modelAlias); const loopControl = this.config.get('loopControl'); return { modelAlias, modelCapabilities: model.capabilities, maxOutputSize: model.maxOutputSize, alwaysThinking: model.alwaysThinking || undefined, thinkingLevel: this.resolveThinkingState(model).effective, reservedContextSize: loopControl?.reservedContextSize, compactionTriggerRatio: loopControl?.compactionTriggerRatio, compactionMaxAttempts: loopControl?.compactionMaxAttempts, }; } resolveRequestParams(): ModelRequestParams { const model = this.tryResolveRawModel(); const thinking = this.resolveThinkingState(model); const thinkingConfig = this.config.get(THINKING_SECTION); const overrides = this.config.get('modelOverrides'); const sampling: SamplingOptions = { temperature: overrides?.temperature, topP: overrides?.topP, }; return { cacheKey: this.sessionContext.sessionId, sampling: sampling.temperature === undefined && sampling.topP === undefined ? undefined : sampling, thinkingEffort: thinking.effective, thinkingKeep: resolveThinkingKeep( overrides?.thinkingKeep, thinkingConfig?.keep, thinking.effective, ), }; } getModelCapabilities(): ModelCapability { return this.tryResolveRawModel()?.capabilities ?? UNKNOWN_CAPABILITY; } getModelProviderType(alias?: string): string | undefined { const effective = alias ?? this.modelAlias ?? this.config.get('defaultModel'); return this.resolveModelForThinking(effective)?.providerType; } getMaxOutputSize(): number | undefined { return this.tryResolveRawModel()?.maxOutputSize; } hasModel(): boolean { return this.modelAlias !== undefined; } isRunnable(): boolean { return this.profileName !== undefined && this.hasModel(); } hasProvider(): boolean { return this.tryResolveRawModel() !== undefined; } getSystemPrompt(): string { return this.systemPrompt; } getActiveToolNames(): readonly string[] | undefined { return this.activeToolNames; } addActiveTool(name: string): void { const activeToolNames = this.activeToolNames; if (activeToolNames === undefined || activeToolNames.includes(name)) return; this.activeToolNamesOverlay = [...activeToolNames, name]; } removeActiveTool(name: string): void { const activeToolNames = this.activeToolNames; if (activeToolNames === undefined || !activeToolNames.includes(name)) return; this.activeToolNamesOverlay = activeToolNames.filter((candidate) => candidate !== name); } private resolveConfigPayload( changed: Omit, ): ConfigUpdatePayload { const payload: ConfigUpdatePayload = { agentId: this.scopeContext.agentId }; if (changed.modelAlias !== undefined) payload.modelAlias = changed.modelAlias; if (changed.profileName !== undefined) payload.profileName = changed.profileName; if (changed.thinkingLevel !== undefined || changed.modelAlias !== undefined) { const model = this.resolveModelForThinking(changed.modelAlias ?? this.modelAlias); const requested = changed.thinkingLevel ?? (this.modelAlias === undefined ? undefined : this.thinkingLevel); payload.thinkingEffort = this.resolveThinkingEffort(requested, model); } if (changed.systemPrompt !== undefined) { payload.systemPrompt = changed.systemPrompt; if (changed.environmentDisclosure !== undefined) { payload.environmentDisclosure = changed.environmentDisclosure; } } if (changed.agentsMdPaths !== undefined) { payload.agentsMdPaths = [...changed.agentsMdPaths]; } if (changed.disallowedTools !== undefined) { payload.disallowedTools = [...changed.disallowedTools]; } return payload; } private afterConfigDispatch(changed: Omit): void { if (changed.modelAlias !== undefined) { this.syncTelemetryModelContext(changed.modelAlias); } if (changed.modelAlias !== undefined || changed.thinkingLevel !== undefined) { this.warnAboutAnthropicThinkingEffort(); } this.emitStatusUpdated( changed.modelAlias !== undefined || changed.thinkingLevel !== undefined, ); } private syncTelemetryModelContext(modelAlias: string | undefined): void { if (modelAlias === undefined) { return; } const model = this.tryResolveRawModel(); this.telemetry.setContext({ model: modelAlias, provider_type: model?.providerType ?? model?.protocol, protocol: model?.protocol, }); } private warnAboutAnthropicThinkingEffort(): void { try { const model = this.tryResolveRawModel(); if (model?.protocol !== 'anthropic') return; const effort = this.getEffectiveThinkingLevel(); if (effort === 'on' || effort === 'off') return; let code: string; let message: string; let knownEfforts = ''; const efforts = model.supportEfforts?.filter((value) => value.length > 0); if (efforts === undefined || efforts.length === 0 || efforts.includes(effort)) return; knownEfforts = efforts.join(','); code = 'anthropic-thinking-effort-not-listed'; message = `Thinking effort "${effort}" is not listed for model "${model.name}" (known: ${efforts.join(', ')}). The configured value will be sent unchanged to the Anthropic-compatible backend.`; const key = [code, model.id, model.name, effort, knownEfforts].join('\u0000'); if (this.emittedThinkingEffortWarnings.has(key)) return; this.emittedThinkingEffortWarnings.add(key); void this.dispatcher.dispatch(new WarningIssued({ agentId: this.scopeContext.agentId, code, message })); } catch { } } private setActiveTools(names: readonly string[] | undefined): void { this.activeToolNamesOverlay = undefined; if (names === undefined) { void this.dispatcher.dispatch(new ToolsResetActiveTools({ agentId: this.scopeContext.agentId })); return; } void this.dispatcher.dispatch( new ToolsSetActiveTools({ agentId: this.scopeContext.agentId, names: [...names] }), ); } private emitStatusUpdated(includeThinkingEffort = false): void { const custom = this.optionsValue.emitStatusUpdated; if (custom !== undefined) { custom(); return; } const modelAlias = this.modelAlias; if (modelAlias === undefined) return; const capabilities = this.tryResolveRawModel()?.capabilities; const maxContextTokens = capabilities?.max_input_tokens ?? capabilities?.max_context_tokens; void this.dispatcher.dispatch( new AgentStatusUpdated({ agentId: this.scopeContext.agentId, model: modelAlias, thinkingEffort: includeThinkingEffort ? this.getEffectiveThinkingLevel() : undefined, maxContextTokens: maxContextTokens !== undefined && maxContextTokens > 0 ? maxContextTokens : undefined, }), ); } republishStatus(): void { this.emitStatusUpdated(true); } private get profileState(): ProfileModelState { return this.states.get(profileKey); } private get model(): string { const modelAlias = this.modelAlias; if (modelAlias === undefined) { throw new Error2(ErrorCodes.MODEL_NOT_CONFIGURED, 'Model not set'); } return modelAlias; } private get modelAlias(): string | undefined { return this.profileState.modelAlias; } private get profileName(): string | undefined { return this.profileState.profileName; } private get systemPrompt(): string { return this.profileState.systemPrompt; } private get thinkingLevel(): ThinkingEffort { const stored = this.profileState.thinkingLevel; if (stored === 'off' && this.alwaysThinkingModel) { return this.resolveThinkingEffort(stored, this.tryResolveRawModel()); } return stored; } private resolveThinkingState(model: Model | undefined): { readonly effective: ThinkingEffort; readonly forced: ThinkingEffort | undefined; } { const base = this.thinkingLevel; const forced = resolveForcedThinkingEffort( this.config.get(THINKING_SECTION)?.forcedEffort, base, drivesThinkingThroughTraits(model?.providerType), ); return { effective: forced ?? base, forced }; } private strictThinkingValidation(model: Model | undefined): boolean { if (model === undefined) return false; return requiresStrictThinkingValidation( this.protocolAdapters, model.protocol, model.providerType, ); } private resolveThinkingEffort( requested: string | undefined, model: Model | undefined, ): ThinkingEffort { return resolveThinkingEffortForModel( requested, this.config.get(THINKING_SECTION), model, this.strictThinkingValidation(model), ); } private supportsThinkingEffort(effort: ThinkingEffort, model: Model | undefined): boolean { return modelSupportsThinkingEffort(effort, model, this.strictThinkingValidation(model)); } private get alwaysThinkingModel(): boolean { return this.tryResolveRawModel()?.alwaysThinking === true; } private tryResolveRawModel(): Model | undefined { const alias = this.modelAlias; return this.resolveModelForThinking(alias); } private resolveModelForThinking(alias: string | undefined): Model | undefined { if (alias === undefined) return undefined; try { return this.modelCatalog.get(alias); } catch { return undefined; } } private assertBindable(requested: string): void { const current = this.profileName; if (current !== undefined && current !== requested) { throw new ProfileError( ProfileErrors.codes.PROFILE_ALREADY_BOUND, `agent is already bound to profile "${current}"; cannot switch to "${requested}" in this session`, { current, requested }, ); } } private cacheAgentsMdWarning(context: Pick): void { this.agentsMdWarning = context.agentsMdWarning; } private publishAgentsMdWarning(): void { const warning = this.agentsMdWarning; if (warning === undefined) return; void this.dispatcher.dispatch( new WarningIssued({ agentId: this.scopeContext.agentId, message: warning, code: 'agents-md-oversized', }), ); } private publishToolPatternWarnings(profile?: ResolvedAgentProfile): void { const known = new Set(); for (const contribution of getAgentToolContributions()) known.add(contribution.options.name); for (const ref of this.toolRegistry.listReferences()) known.add(ref.name); for (const builtin of this.builtinProfiles.list()) { for (const name of literalToolNames([ ...(builtin.tools ?? []), ...(builtin.disallowedTools ?? []), ])) { known.add(name); } } const checks: { context: string; field: string; patterns: readonly string[] | undefined; }[] = []; if (profile !== undefined) { checks.push( { context: `profile "${profile.name}"`, field: 'tools', patterns: profile.tools }, { context: `profile "${profile.name}"`, field: 'disallowedTools', patterns: profile.disallowedTools, }, ); } const global = this.config.get(TOOLS_SECTION); checks.push( { context: 'the global [tools] config', field: 'enabled', patterns: global?.enabled }, { context: 'the global [tools] config', field: 'disabled', patterns: global?.disabled }, ); for (const { context, field, patterns } of checks) { if (patterns === undefined) continue; for (const issue of findInactiveToolPatterns(patterns, (name) => known.has(name))) { const key = `${context}|${field}|${issue.pattern}`; if (this.emittedToolPatternWarnings.has(key)) continue; this.emittedToolPatternWarnings.add(key); void this.dispatcher.dispatch( new WarningIssued({ agentId: this.scopeContext.agentId, code: 'tool-pattern-no-match', message: describeInactiveToolPattern(context, field, issue), }), ); } } } private async buildSystemPromptContext( profile: ResolvedAgentProfile, options?: ApplyProfileOptions, ): Promise { await this.notify.ready; const preloadedAgentsMd = await this.workspaceInstructionsSnapshot(); const fsAvailable = this.runtime.isAvailable(['fs']); const lease = this.runtime.acquire(fsAvailable ? ['fs'] : []); const env = lease.runtime.environment; const view = new RuntimeWorkspaceView(lease.runtime, { workDir: this.sessionContext.cwd, additionalDirs: options?.additionalDirs ?? this.workspace.additionalDirs, }); let base: SystemPromptContext; try { base = !fsAvailable ? {} : await prepareSystemPromptContext( { fs: lease.runtime.fs!, homeDir: env.homeDir }, view.workDir, this.bootstrap.homeDir, { additionalDirs: view.additionalDirs, preloadedAgentsMd, }, ); } finally { lease.dispose(); } const skills = await this.resolveSkillListing(); const pluginSections = await this.resolvePluginSections(); return { ...base, cwd: view.workDir, osKind: env.osKind, shellName: env.shellName, shellPath: env.shellPath, skills, pluginSections, skillActive: this.isToolActiveForProfile(profile, 'Skill'), productName: (await this.identity.resolved()).displayName, replyStyleGuide: this.bootstrap.args.replyStyleGuide, notifyUserActive: this.notify.enabled && this.isToolActiveForProfile(profile, NOTIFY_USER_TOOL_NAME), }; } private async workspaceInstructionsSnapshot(): Promise { await this.instructions.ready; return { content: this.instructions.agentsMd ?? '', warning: this.instructions.agentsMdWarning, paths: this.instructions.agentsMdPaths ?? [], }; } private isToolActiveForProfile( profile: ResolvedAgentProfile, 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, ); } private async resolveSkillListing(): Promise { if (this.frozenSkillListing !== undefined) return this.frozenSkillListing; try { await this.skillCatalog.ready; const listing = this.skillCatalog.catalog.getModelSkillListing(); this.frozenSkillListing = listing; return listing; } catch { return ''; } } private async resolvePluginSections(): Promise { if (this.frozenPluginSections !== undefined) return this.frozenPluginSections; const sections = await this.plugins.enabledSystemPrompts(); const parts: string[] = []; const skipped: string[] = []; let totalBytes = 0; for (const section of sections) { const block = `\n${section.content}`; const bytes = Buffer.byteLength(block, 'utf8'); if (totalBytes + bytes > PLUGIN_SECTIONS_MAX_BYTES) { skipped.push(section.pluginId); continue; } totalBytes += bytes; parts.push(block); } if (skipped.length > 0) { const newlySkipped = skipped.filter((id) => !this.emittedPluginBudgetWarnings.has(id)); if (newlySkipped.length > 0) { for (const id of newlySkipped) this.emittedPluginBudgetWarnings.add(id); void this.dispatcher.dispatch( new WarningIssued({ agentId: this.scopeContext.agentId, message: `Plugin system-prompt contributions from ${newlySkipped.map((id) => `"${id}"`).join(', ')} ` + `were skipped: the aggregate ${PLUGIN_SECTIONS_MAX_BYTES / 1024} KB budget is exhausted.`, code: 'plugin-sections-oversized', }), ); } } const resolved = parts.join('\n\n'); if (this.plugins.hasLoadedSnapshot()) this.frozenPluginSections = resolved; return resolved; } } registerScopedService( LifecycleScope.Agent, IAgentProfileService, AgentProfileService, ScopeActivation.OnScopeCreated, 'profile', );