kimi-code / packages /agent-core-v2 /src /agent /profile /profileService.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
68d7816 verified
Raw History Blame Contribute Delete
35 kB
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<readonly string[] | undefined>(
'profile.activeToolNamesOverlay',
() => undefined as readonly string[] | undefined,
);
export const profileAgentsMdWarningKey = defineState<string | undefined>(
'profile.agentsMdWarning',
() => undefined as string | undefined,
);
export const profileEmittedThinkingEffortWarningsKey = defineState<Set<string>>(
'profile.emittedThinkingEffortWarnings',
() => new Set(),
);
export const profileEmittedToolPatternWarningsKey = defineState<Set<string>>(
'profile.emittedToolPatternWarnings',
() => new Set(),
);
export const profileEmittedPluginBudgetWarningsKey = defineState<Set<string>>(
'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<string> {
return this.states.get(profileEmittedThinkingEffortWarningsKey);
}
private get emittedToolPatternWarnings(): Set<string> {
return this.states.get(profileEmittedToolPatternWarningsKey);
}
private get emittedPluginBudgetWarnings(): Set<string> {
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<void> {
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<string>('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<ProfileSetModelResult> {
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<void> {
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>('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<ThinkingConfig>(THINKING_SECTION);
const overrides = this.config.get<ModelOverrides>('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<string>('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<ProfileUpdateData, 'activeToolNames'>,
): 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<ProfileUpdateData, 'activeToolNames'>): 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<ThinkingConfig>(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<ThinkingConfig>(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<SystemPromptContext, 'agentsMdWarning'>): 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<string>();
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<ToolsConfig>(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<SystemPromptContext> {
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<LoadedAgentsMd> {
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<ToolsConfig>(TOOLS_SECTION),
sessionDisabledTools: this.sessionToolPolicy.disabledTools(),
},
name,
source,
);
}
private async resolveSkillListing(): Promise<string> {
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<string> {
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 = `<!-- From: plugin ${section.pluginId} -->\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',
);