Skip to main content

Overview

The prompt method is the core of your agent’s functionality. It handles the entire lifecycle of processing a user’s request, from receiving the initial messages to executing tool calls and returning a final response.
async prompt(
  params: PromptRequest
): Promise<PromptResponse>
params.sessionId
string
required
The session ID to process the prompt in
params.messages
Message[]
required
The user messages with optional context (files, images, etc.)
stopReason
StopReason
required
The reason the prompt turn ended: “end_turn”, “cancelled”, or “error”
See protocol docs: Prompt Turn

Prompt Lifecycle

The prompt method handles the complete lifecycle of a turn:
1

Receive Messages

The client sends user messages with optional context (files, images, etc.)
2

Process with Language Model

Send the messages to your language model and receive a response
3

Stream Updates to Client

Send real-time updates as the model generates content using sessionUpdate
4

Handle Tool Calls

If the model wants to use tools, request permission (if needed) and execute them
5

Return Stop Reason

Return the final stop reason indicating why the turn ended

Sending Session Updates

Use the sessionUpdate method on your AgentSideConnection to send real-time updates during prompt processing:

Message Chunks

Send text content as it’s generated by the language model:
await this.connection.sessionUpdate({
  sessionId: params.sessionId,
  update: {
    sessionUpdate: "agent_message_chunk",
    content: {
      type: "text",
      text: "I'll help you with that task.",
    },
  },
});

Tool Calls

Report when the model wants to execute a tool:
// Initial tool call
await this.connection.sessionUpdate({
  sessionId: params.sessionId,
  update: {
    sessionUpdate: "tool_call",
    toolCallId: "call_1",
    title: "Reading project files",
    kind: "read",
    status: "pending",
    locations: [{ path: "/project/README.md" }],
    rawInput: { path: "/project/README.md" },
  },
});

// Update with results
await this.connection.sessionUpdate({
  sessionId: params.sessionId,
  update: {
    sessionUpdate: "tool_call_update",
    toolCallId: "call_1",
    status: "completed",
    content: [
      {
        type: "content",
        content: {
          type: "text",
          text: "# My Project\n\nThis is a sample project...",
        },
      },
    ],
    rawOutput: { content: "# My Project\n\nThis is a sample project..." },
  },
});

Tool Calls and Permission Requests

When the language model wants to execute a sensitive operation, request permission from the user:
// Send the tool call
await this.connection.sessionUpdate({
  sessionId: params.sessionId,
  update: {
    sessionUpdate: "tool_call",
    toolCallId: "call_2",
    title: "Modifying critical configuration file",
    kind: "edit",
    status: "pending",
    locations: [{ path: "/project/config.json" }],
    rawInput: {
      path: "/project/config.json",
      content: '{"database": {"host": "new-host"}}',
    },
  },
});

// Request permission
const permissionResponse = await this.connection.requestPermission({
  sessionId: params.sessionId,
  toolCall: {
    toolCallId: "call_2",
    title: "Modifying critical configuration file",
    kind: "edit",
    status: "pending",
    locations: [{ path: "/home/user/project/config.json" }],
    rawInput: {
      path: "/home/user/project/config.json",
      content: '{"database": {"host": "new-host"}}',
    },
  },
  options: [
    {
      kind: "allow_once",
      name: "Allow this change",
      optionId: "allow",
    },
    {
      kind: "reject_once",
      name: "Skip this change",
      optionId: "reject",
    },
  ],
});

// Handle the response
if (permissionResponse.outcome.outcome === "cancelled") {
  // User cancelled the entire prompt turn
  return { stopReason: "cancelled" };
}

switch (permissionResponse.outcome.optionId) {
  case "allow":
    // Execute the tool call
    await this.executeToolCall("call_2");
    break;
  case "reject":
    // Skip the tool call
    break;
}
If the client cancels the prompt turn via session/cancel, the requestPermission call MUST respond with RequestPermissionOutcome::Cancelled.

Stop Reasons

Return one of the following stop reasons when the prompt completes:
end_turn
StopReason
The language model completed its turn naturally
cancelled
StopReason
The prompt was cancelled by the client via session/cancel
error
StopReason
An error occurred during processing

Handling Cancellation

Implement proper cancellation handling using AbortController:
interface AgentSession {
  pendingPrompt: AbortController | null;
}

async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
  const session = this.sessions.get(params.sessionId);
  if (!session) {
    throw new Error(`Session ${params.sessionId} not found`);
  }

  // Cancel any existing prompt
  session.pendingPrompt?.abort();
  session.pendingPrompt = new AbortController();

  try {
    // Pass the abort signal to your LLM client
    await this.processWithLLM(params, session.pendingPrompt.signal);
    return { stopReason: "end_turn" };
  } catch (err) {
    if (session.pendingPrompt.signal.aborted) {
      return { stopReason: "cancelled" };
    }
    throw err;
  } finally {
    session.pendingPrompt = null;
  }
}

async cancel(params: acp.CancelNotification): Promise<void> {
  this.sessions.get(params.sessionId)?.pendingPrompt?.abort();
}
Continue accepting tool call updates even after receiving a session/cancel notification, as you may need to send final updates before responding with the cancelled stop reason.

Complete Example

Here’s a complete example from the SDK’s example agent:
async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
  const session = this.sessions.get(params.sessionId);
  if (!session) {
    throw new Error(`Session ${params.sessionId} not found`);
  }

  session.pendingPrompt?.abort();
  session.pendingPrompt = new AbortController();

  try {
    // Send initial text chunk
    await this.connection.sessionUpdate({
      sessionId: params.sessionId,
      update: {
        sessionUpdate: "agent_message_chunk",
        content: {
          type: "text",
          text: "I'll help you with that. Let me start by reading some files.",
        },
      },
    });

    // Send a tool call
    await this.connection.sessionUpdate({
      sessionId: params.sessionId,
      update: {
        sessionUpdate: "tool_call",
        toolCallId: "call_1",
        title: "Reading project files",
        kind: "read",
        status: "pending",
        locations: [{ path: "/project/README.md" }],
        rawInput: { path: "/project/README.md" },
      },
    });

    // Execute and update
    await this.connection.sessionUpdate({
      sessionId: params.sessionId,
      update: {
        sessionUpdate: "tool_call_update",
        toolCallId: "call_1",
        status: "completed",
        content: [
          {
            type: "content",
            content: {
              type: "text",
              text: "# My Project\n\nThis is a sample project...",
            },
          },
        ],
        rawOutput: { content: "# My Project\n\nThis is a sample project..." },
      },
    });

    return { stopReason: "end_turn" };
  } catch (err) {
    if (session.pendingPrompt.signal.aborted) {
      return { stopReason: "cancelled" };
    }
    throw err;
  } finally {
    session.pendingPrompt = null;
  }
}
See the full example agent for more details.

Build docs developers (and LLMs) love