| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- /**
- * The globally named `send_message` and `interrupt_agent` tools: thin
- * model-facing adapters over `ctx.subagents.sendMessage()` and
- * `ctx.subagents.interrupt()`. They perform no lifecycle routing of their own —
- * residency, cold resume, and interrupt authorization belong to the subagent
- * service — and they live apart from the provider-bound
- * `@deepseek-ai/dsh-tool-subagent` instances so multiple delegation tools share
- * one control API.
- * @module @deepseek-ai/dsh-tool-subagent-control
- */
- import type { Context } from '@deepseek-ai/cordis'
- import { brandString } from '@deepseek-ai/dsh-brand'
- import { defineTool } from '@deepseek-ai/dsh-tools'
- import type { ContentBlock } from '@deepseek-ai/dsh-llm'
- import type { SessionId } from '@deepseek-ai/dsh-session'
- import type {} from '@deepseek-ai/dsh-subagent'
- import { markAdjacentAgentSendMessageTool } from '@deepseek-ai/dsh-subagent/internal'
- export const name = 'tool-subagent-control'
- export const inject = ['tools', 'subagents']
- /**
- * Register the `send_message` and `interrupt_agent` tools.
- * @param ctx - context carrying the tool registry and subagent service.
- */
- export function apply(ctx: Context): void {
- ctx.tools.register(markAdjacentAgentSendMessageTool(defineTool({
- name: 'send_message',
- description:
- 'Send a message to a direct continuable child by its agent id. If you are a resident continuable child, '
- + 'you may also target your direct parent. If the target is still working, the message steers its nearest step; '
- + 'if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation '
- + 'that the message was delivered. A failure means the message was NOT delivered.',
- parameters: {
- agent_id: {
- type: 'string',
- required: true,
- description: 'The agent id of your direct continuable child, or your direct parent when you are a resident continuable child.',
- },
- message: {
- type: 'string',
- required: true,
- description: 'The message to deliver to the agent.',
- },
- },
- output: {
- schema: {
- type: 'object',
- additionalProperties: false,
- properties: {
- messageId: { type: 'string', required: true },
- },
- },
- render: (args, _value) => [{
- type: 'text',
- text: `message delivered to agent ${args.agent_id}`,
- }],
- },
- async execute(args, exec) {
- const sender = exec.agent
- if (!sender) {
- throw new Error('send_message requires a calling agent (exec.agent was undefined)')
- }
- const message: ContentBlock[] = [{ type: 'text', text: args.message }]
- const messageId = await ctx.subagents.sendMessage(
- sender,
- brandString<SessionId>(args.agent_id),
- message,
- { signal: exec.signal },
- )
- return { messageId }
- },
- })))
- ctx.tools.register(defineTool({
- name: 'interrupt_agent',
- description:
- 'Request cancellation of a background agent\'s current turn by its agent id. The target may be your '
- + 'direct child or a deeper agent created under you. Only the current turn stops: messages already '
- + 'queued for the agent stay parked until a later send_message, agents it started keep running, and '
- + 'the agent itself stays available for follow-ups. This call returns as soon as the stop request is '
- + 'accepted, so the target may keep running briefly; interrupting an agent that already finished is '
- + 'an accepted no-op.',
- parameters: {
- agent_id: {
- type: 'string',
- required: true,
- description: 'The agent id of the running agent to interrupt.',
- },
- },
- output: {
- schema: {
- type: 'object',
- additionalProperties: false,
- properties: {
- accepted: { type: 'boolean', required: true },
- },
- },
- render: (args, _value) => [{
- type: 'text',
- text: `interrupt requested for agent ${args.agent_id}`,
- }],
- },
- execute(args, exec) {
- const caller = exec.agent
- if (!caller) {
- // Ancestor authority requires an exact live calling agent.
- throw new Error('interrupt_agent requires a calling agent (exec.agent was undefined)')
- }
- // The service authorizes the exact live caller against the target's
- // recorded lineage; the tool adds no authority of its own.
- ctx.subagents.interrupt(brandString<SessionId>(args.agent_id), { kind: 'ancestor', agent: caller })
- return Promise.resolve({ accepted: true })
- },
- }))
- }
|