Skip to main content

Module 34: Extension Methods

Add your own _-prefixed requests and notifications to ACP, in both directions, typed and raw: builder handlers, annotations, and the senders on all four facades.

What You’ll Learn

  • Why a custom method name must start with _, and what happens if it doesn’t
  • @ExtRequest and @ExtNotification on an annotated agent, the recommended way in
  • The builder-API equivalent, typed (TypeRef) or raw (the plain JSON value)
  • Sending with sendExtRequest/sendExtNotification from a client or an agent
  • What happens when nothing serves a method: -32601 for a request, silence for a notification

The Code

Naming the extension

A shared contract both sides agree on. ACP reserves every name without a leading _ for the protocol itself, so a custom method must start with it:
Registering a handler or sending to a name without the prefix throws IllegalArgumentException before anything leaves the process, so an extension can never collide with or shadow a protocol method. Namespacing the rest of the name with a domain you control (_acptutorial/...) keeps it from colliding with someone else’s extension.

Serving, with annotations

The annotation names the method; discovery fails if it doesn’t start with _. A handler takes at most one params parameter (a record for typed params, Map<String, Object> for raw), plus, like any other annotated handler, the connection’s AcpSyncAgent/AcpAsyncAgent and NegotiatedCapabilities. An @ExtRequest’s return value is the result; returning null answers -32603. An @ExtNotification gets no answer at all, so it must return void, as log above does: building the agent rejects one that returns a value, naming the method.

The same handlers, with the builder API

A handler must not return null: a null result answers -32603; return an empty map when there’s nothing to say.

Calling the other side, from a prompt handler

A prompt handler can also reach the client through the prompt context, without a reference to the agent at all: ctx.client().sendExtRequest(Extensions.SELECTION, new SelectionQuery("src/Main.java"), new TypeRef<Selection>() {}) calls the same way agent.sendExtRequest(...) does above. Useful in a builder prompt handler, which gets a PromptContext/SyncPromptContext either way, whether or not it also takes the agent as a parameter.

Client: serving the agent’s calls, and calling back

What happens without a handler

A request nobody serves fails with -32601 (method not found), the same as any unhandled protocol method. A notification nobody handles is silently ignored, since a notification has no response to fail. Both behaviors match the spec’s extensibility rules, not just this SDK’s convenience.

Source Code

View on GitHub

Running the Example

The demo runs the same six steps against the builder agent and the annotated agent: a typed request, a raw request, a notification with an agent-sent acknowledgement, a method nobody serves, a name without the leading underscore, and a prompt during which the agent calls two of the client’s extension methods. No API key required.

Next Module

Module 35: Cancellation and Timeouts: the two ways to cancel in ACP, and the Java SDK’s prompt timeouts.