acp-probe
v0.0.1
Published
Probe any ACP-compatible agent for its capabilities (models, modes, configOptions, prompt capabilities, auth methods, MCP transports). No real prompt required; no token cost.
Maintainers
Readme
acp-probe
Probe any Agent Client Protocol agent for its capabilities — models, modes, configOptions, prompt capabilities, auth methods, MCP transports — without sending a real prompt.
[!WARNING] Alpha software. This package is in active development. The public API may change between minor versions without notice until
1.0.0. Pin exact versions; expect rough edges.
What it does
Given an ACP-compatible agent (built-in like claude / codex /
gemini or any custom adapter you can spawn), acp-probe performs
the ACP initialize + session/new handshake, then optionally
pings session/set_config_option to test whether the agent
implements that method — agents like gemini-cli that don't
implement it respond with the standard JSON-RPC "method not found"
error, which the probe catches to flip supportsConfigOption: false
in the result. Finally the agent is torn down. The result is a
typed, schema-stable AgentProbeResult you can feed into a settings
UI, picker, or downstream AcpRuntimeOptions call.
No LLM tokens are consumed. session/new is free; we never call
session/prompt.
Install
bun add acp-probe
# or
npm install acp-probeThe only hard dependency is
@agentclientprotocol/sdk.
acpx is an optional peer
— installed, it lets you use the built-in { agent: 'claude' }
shorthand; absent, you pass { command } or { argv } yourself.
Quickstart
import { probeAgent } from 'acp-probe'
// Custom ACP command — the canonical entry point. Zero acpx
// involvement; works with any agent you can spawn.
const result = await probeAgent({
command: 'my-acp-agent --stdio',
})
console.log(result.models) // ProbedModel[]
console.log(result.modes) // ProbedMode[]
console.log(result.configOptions) // ProbedConfigOption[]
console.log(result.reasoning) // { configId, values, defaultValue } | null
console.log(result.capabilities.promptCapabilities)
// => { image: boolean, audio: boolean, embeddedContext: boolean }
// Built-in shorthand — requires acpx to be installed.
const claude = await probeAgent({ agent: 'claude' })Result shape
interface AgentProbeResult {
agent: {
id: string | null
command: string
argv: readonly string[]
probedAt: string // ISO timestamp
durationMs: number
}
protocolVersion: number
agentInfo: { name; title?; version? } | null
capabilities: {
loadSession: boolean
promptCapabilities: { image; audio; embeddedContext }
mcpCapabilities: { http; sse; meta? }
sessionCapabilities: { close; list; resume; fork; additionalDirectories }
experimental?: { auth; nes; providers; positionEncoding }
}
authMethods: AuthMethod[]
models: Array<{ id; name?; description? }>
modes: Array<{ id; name?; description? }>
configOptions: ProbedConfigOption[]
/** Derived pointer to the configOption with category='thought_level'. */
reasoning: { configId; values; defaultValue? } | null
/**
* True iff the agent implements `session/set_config_option`. False
* when the agent responds with JSON-RPC "method not found" to a
* no-op set call — that's how gemini-cli (and similar adapters that
* skip configOptions support) signal the method is unimplemented.
*/
supportsConfigOption: boolean
error?: ProbeError
/** Verbatim init + session/new responses for callers that need _meta. */
raw: { initialize; newSession }
}Errors
Two distinct error surfaces:
AgentResolveError(thrown sync): caller passed{ agent: <id> }but acpx isn't installed / doesn't recognise the id. IncludesresolveCause: 'acpx_not_installed' | 'acpx_incompatible' | 'unknown_agent'.AgentProbeResult.error(returned, not thrown): probe ran but couldn't complete cleanly. Includescode: 'spawn_failed' | 'initialize_timeout' | 'session_new_timeout' | 'auth_required' | 'protocol_mismatch' | 'agent_crashed' | 'unknown'plusstderrfor debugging.
Options
| option | type | default | notes |
|---|---|---|---|
| agent | string | — | Built-in agent id (requires acpx). |
| command | string | — | Raw command, shell-split. |
| argv | readonly string[] | — | Pre-split argv (takes precedence over command). |
| cwd | string | process.cwd() | Working dir for the spawned agent. |
| env | Record<string, string> | {} | Merged into the spawned process's env. |
| authPolicy | 'skip' \| 'fail' | 'skip' | 'skip': record authMethods and continue. 'fail': don't call session/new. |
| timeoutMs | number | 30_000 | Hard cap on the full probe lifecycle. |
Exactly one of agent, command, or argv is required.
Captured fixtures (2026-05-14)
The repo ships recorded initialize + session/new responses for
claude / codex / gemini under test/fixtures/. They power the unit
test suite and document the per-agent capability matrix at the time
of capture.
End-to-end probes against real agents
An opt-in suite under test/e2e/ runs the
probe against the locally-installed claude / codex / gemini
CLIs to catch upstream adapter drift the fixture tests can't see.
Each agent is probed twice — once via { agent: <id> } (acpx
resolution) and once via { command: <hardcoded> } (the no-acpx
path) — and the two results are asserted to be structurally
identical.
# Run all three agents
bun run test:e2e
# Or pick one
PROBE_E2E=claude bun test test/e2eNot run in CI. Zero LLM tokens consumed. See the suite's README for per-agent setup notes.
Relationship to acpx
acp-probe is upstream of acpx in the data flow: probe → capabilities
→ feed AcpRuntimeOptions. The probe doesn't depend on acpx at
runtime unless you use the { agent: <id> } shorthand, in which case
it lazy-imports acpx/runtime to resolve via acpx's built-in agent
registry. Custom-agent users never touch acpx.
License
MIT — see LICENSE.
