kimi-code / packages /agent-core-v2 /src /session /subagent /subagentService.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
68d7816 verified
Raw History Blame Contribute Delete
9.29 kB
import { Service } from '#/_base/di/service';
import type { AgentContext } from '#/agent/agentContext/agentContext';
import { Error2, ErrorCodes } from '#/errors';
import { LifecycleScope } from '#/app/scopes';
import {
type IAgentScopeHandle,
ScopeActivation,
registerScopedService,
} from '#/_base/di/scope';
import { Emitter } from '#/_base/event';
import { applyProfilePromptPrefix } from '#/app/agentProfileCatalog/promptPrefix';
import {
rootDelegationExtras,
subagentAllowlistFor,
subagentTypeNotAllowedMessage,
withoutDelegatingTargets,
} from '#/app/agentProfileCatalog/profile-shared';
import { ISessionAgentProfileCatalog } from '#/session/sessionAgentProfileCatalog/sessionAgentProfileCatalog';
import { IAgentProfileService } from '#/agent/profile/profile';
import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode';
import { IAgentUserToolService } from '#/agent/userTool/userTool';
import { IAgentRuntimeService } from '#/agent/runtimeBinding/agentRuntime';
import type { Runtime } from '#/runtime/runtime';
import { IConfigService } from '#/app/config/config';
import { IModelCatalog, type Model } from '#/llm-adapter/model/catalog';
import { ILogService } from '#/_base/log/log';
import { ISessionContext } from '#/session/sessionContext/sessionContext';
import { RuntimeWorkspaceView } from '#/runtime/runtimeWorkspaceView';
import { createHooks } from '#/hooks';
import { IAgentLifecycleService, MAIN_AGENT_ID } from '#/session/agentLifecycle/agentLifecycle';
import { agentContextOf } from '#/agent/scopeContext/scopeContext';
import { IAgentReminderService } from '#/features/reminder/reminderService';
import {
type AgentRunHandle,
type AgentRunRequest,
type AgentTaskHooks,
type AgentTaskStopHookContext,
ISessionSubagentService,
type RunAgentOptions,
} from './subagent';
import { runAgentTurn } from './runAgentTurn';
import {
resolveSubagentBinding,
resolveSubagentThinking,
wrapSubagentModelError,
} from './configSection';
import {
DEFAULT_PROFILE_NAME,
FORK_CONTEXT_NOTICE,
type SpawnSubagentOptions,
type SpawnedSubagent,
type SubagentSpawnPlan,
type SubagentSpawnPlanInput,
} from './spawn';
export class SessionSubagentService extends Service implements ISessionSubagentService {
declare readonly _serviceBrand: undefined;
readonly hooks = createHooks<AgentTaskHooks, keyof AgentTaskHooks>(['onWillStartAgentTask']);
private readonly onDidStopAgentTaskEmitter = this._register(
new Emitter<AgentTaskStopHookContext>(),
);
get onDidStopAgentTask() {
return this.onDidStopAgentTaskEmitter.event;
}
constructor(
@IAgentLifecycleService private readonly agentLifecycle: IAgentLifecycleService,
@ISessionAgentProfileCatalog private readonly catalog: ISessionAgentProfileCatalog,
@IConfigService private readonly configService: IConfigService,
@IModelCatalog private readonly modelCatalog: IModelCatalog,
@ISessionContext private readonly sessionContext: ISessionContext,
@ILogService private readonly log: ILogService,
) {
super();
}
run(agent: AgentContext, request: AgentRunRequest, opts: RunAgentOptions): Promise<AgentRunHandle> {
const handle = this.agentLifecycle.handleOf(agent.agentId);
if (handle === undefined) {
throw new Error2(ErrorCodes.AGENT_NOT_FOUND, `Agent "${agent.agentId}" does not exist`, {
details: { agentId: agent.agentId },
});
}
return runAgentTurn(handle, request, { signal: opts.signal, onReady: opts.onReady });
}
async planSpawn(input: SubagentSpawnPlanInput): Promise<SubagentSpawnPlan> {
const caller = this.requireCaller(input.callerAgentId);
const fork = input.fork === true;
await this.catalog.ready;
const own = caller.accessor.get(IAgentProfileService).data();
const requested = input.profileName !== undefined && input.profileName.length > 0
? input.profileName
: undefined;
const requestedProfileName =
requested ?? (fork ? (own.profileName ?? DEFAULT_PROFILE_NAME) : DEFAULT_PROFILE_NAME);
const extras =
input.callerAgentId === MAIN_AGENT_ID
? rootDelegationExtras(this.catalog, own, this.catalog.list())
: undefined;
let allowlist = subagentAllowlistFor(this.catalog, own, extras);
if (allowlist !== undefined && own.subagents === undefined) {
allowlist = withoutDelegatingTargets(this.catalog, allowlist);
}
if (!fork && allowlist !== undefined && !allowlist.includes(requestedProfileName)) {
throw new Error2(
ErrorCodes.AGENT_TYPE_NOT_ALLOWED,
subagentTypeNotAllowedMessage(requestedProfileName, allowlist),
{ details: { profileName: requestedProfileName, allowlist } },
);
}
const profile = this.catalog.get(requestedProfileName);
if (!fork && profile === undefined) {
throw new Error2(ErrorCodes.PROFILE_UNKNOWN, `Unknown agent type: "${requestedProfileName}"`, {
details: { profileName: requestedProfileName },
});
}
if (own.modelAlias === undefined) {
throw new Error2(ErrorCodes.MODEL_NOT_CONFIGURED, 'Caller agent has no model bound', {
details: { agentId: input.callerAgentId },
});
}
const binding = fork
? { model: own.modelAlias, thinking: own.thinkingLevel, modelSource: 'inherited' as const }
: resolveSubagentBinding(
this.configService,
{ modelAlias: own.modelAlias, thinkingLevel: own.thinkingLevel },
input.model,
);
let model: Model;
try {
model = this.modelCatalog.get(binding.model);
} catch (error) {
throw wrapSubagentModelError(error, binding.model, own.modelAlias);
}
return {
profileName: profile?.name ?? requestedProfileName,
model: binding.model,
modelSource: binding.modelSource,
thinking: resolveSubagentThinking(this.configService, model, binding.thinking),
fork,
};
}
async spawn(opts: SpawnSubagentOptions): Promise<SpawnedSubagent> {
const caller = this.requireCaller(opts.callerAgentId);
const { plan } = opts;
const lease = plan.fork
? undefined
: caller.accessor.get(IAgentRuntimeService).acquire(['process']);
try {
let created: IAgentScopeHandle;
try {
if (plan.fork) {
const forked = await this.agentLifecycle.fork(agentContextOf(caller), {
labels: opts.labels,
});
created = this.agentLifecycle.handleOf(forked.agentId)!;
created.accessor
.get(IAgentReminderService)
.notify(FORK_CONTEXT_NOTICE, { variant: 'fork_context' });
} else {
const createdContext = await this.agentLifecycle.create({
binding: {
profile: plan.profileName,
model: plan.model,
thinking: plan.thinking,
},
labels: opts.labels,
runtimeId: lease!.runtime.identity.runtimeId,
});
created = this.agentLifecycle.handleOf(createdContext.agentId)!;
}
} catch (error) {
throw wrapSubagentModelError(
error,
plan.model,
caller.accessor.get(IAgentProfileService).data().modelAlias,
);
}
created.accessor
.get(IAgentPermissionModeService)
.setMode(caller.accessor.get(IAgentPermissionModeService).mode);
const createdUserTools = created.accessor.get(IAgentUserToolService);
const callerUserTools = caller.accessor.get(IAgentUserToolService);
if (plan.fork) {
const activeToolNames = created.accessor.get(IAgentProfileService).getActiveToolNames();
createdUserTools.inheritUserTools(callerUserTools, activeToolNames);
} else {
createdUserTools.inheritUserTools(callerUserTools);
}
const promptText = plan.fork
? opts.prompt
: await this.applyPromptPrefix(plan.profileName, opts.prompt, lease!.runtime);
return {
agentId: created.id,
profileName: plan.profileName,
model: plan.model,
modelSource: plan.modelSource,
promptText,
};
} finally {
lease?.dispose();
}
}
notifyAgentTaskStopped(context: AgentTaskStopHookContext): void {
this.onDidStopAgentTaskEmitter.fire(context);
}
private async applyPromptPrefix(
profileName: string,
prompt: string,
runtime: Runtime,
): Promise<string> {
const profile = this.catalog.get(profileName);
if (profile?.promptPrefix === undefined) return prompt;
const view = new RuntimeWorkspaceView(runtime, {
workDir: this.sessionContext.cwd,
});
return applyProfilePromptPrefix(profile, prompt, {
cwd: view.workDir,
process: runtime.process!,
log: this.log,
});
}
private requireCaller(agentId: string): IAgentScopeHandle {
const handle = this.agentLifecycle.handleOf(agentId);
if (handle === undefined) {
throw new Error2(ErrorCodes.AGENT_NOT_FOUND, `Caller agent "${agentId}" does not exist`, {
details: { agentId },
});
}
return handle;
}
}
registerScopedService(
LifecycleScope.Session,
ISessionSubagentService,
SessionSubagentService,
ScopeActivation.OnScopeCreated,
'subagent',
);