Repository navigation
ACP server ignores the configured provider, hardcodes tool kind, and its unknown-session fallback always raises #120
Description
Testing Mini-Agent's ACP server against a third-party ACP client turned up three
defects in mini_agent/acp/. All are reproduced below on main (d76a4f638968),
Python 3.12, agent-client-protocol 0.12.1.
Note: reaching any of this first requires pinning mcp==1.9.4, per #119 -- with the
resolved mcp 2.x the package does not import at all. That one is already tracked,
so it is not repeated here.
1. The ACP server ignores the configured provider, so any OpenAI-wire endpoint 404s
mini_agent/acp/__init__.py:186 constructs the client without provider:
llm = LLMClient(api_key=config.llm.api_key, api_base=config.llm.api_base,
model=config.llm.model, retry_config=...)LLMClient.provider defaults to LLMProvider.ANTHROPIC, and the module never imports
LLMProvider at all. The CLI does derive it (mini_agent/cli.py:559):
provider = LLMProvider.ANTHROPIC if config.llm.provider.lower() == "anthropic" else LLMProvider.OPENAISo provider: "openai" is honoured by mini-agent and silently ignored by
mini-agent-acp: the ACP server posts Anthropic-shaped requests at an
OpenAI-compatible endpoint and every turn fails.
Reproduction -- one config.yaml, two entry points:
api_key: "<key>"
api_base: "https://api.deepseek.com/v1"
model: "deepseek-v4-pro"
provider: "openai"$ mini-agent -w /tmp/demo -t "Read hello.txt and reply with its exact contents."
-> works: 1 tool call, correct answer
$ mini-agent-acp # same config, driven over ACP
-> Error: Retry failed after 4 attempts. Last error: Error code: 404
I verified this is not the /v1 suffix question from #37: the CLI succeeds with
api_base both with and without /v1, and the ACP server fails with both.
Fix (verified locally -- the same prompt then returns the correct answer over ACP):
-from mini_agent.llm import LLMClient
+from mini_agent.llm import LLMClient
+from mini_agent.schema.schema import LLMProvider
llm = LLMClient(api_key=config.llm.api_key, api_base=config.llm.api_base,
model=config.llm.model,
+ provider=(LLMProvider.ANTHROPIC if config.llm.provider.lower() == "anthropic"
+ else LLMProvider.OPENAI),
retry_config=...)2. Every tool call is reported to the client as kind="execute"
mini_agent/acp/__init__.py:152:
await self._send(session_id, start_tool_call(call.id, label, kind="execute", raw_input=args))The kind is a literal, so a file read, an edit, a search and a shell command are all
announced identically. The ACP schema defines ten kinds
(read, edit, delete, move, search, execute, think, fetch,
switch_mode, other), and clients group, label and icon tool calls from this field.
Observed frame for a plain file read -- the title already knows the right answer
while kind does not:
{"sessionUpdate": "tool_call", "kind": "execute",
"title": "read_file(path='hello.txt')", "rawInput": {"path": "hello.txt"}}A client that renders kind (the spec field) therefore shows every read as an
execution. Mapping the tool name onto the closer kind, with other as the fallback,
would fix it.
3. The unknown-session fallback in prompt() always raises
prompt() handles a missing session id like this:
# Auto-create session if not found (compatibility with clients that skip newSession)
new_session = await self.newSession(NewSessionRequest(cwd=None))NewSessionRequest requires cwd: str and mcpServers, so this call fails validation
on every invocation and the recovery path has never run.
Reproduction -- initialize, then session/prompt with an id that was never created:
{"code": -32602, "message": "Invalid params", "data": {"errors": [
{"loc": ["cwd"], "msg": "Input should be a valid string", "input": null},
{"loc": ["mcpServers"], "msg": "Field required"}]}}So a client that loses its session gets a confusing complaint about cwd rather than
"unknown session". Worth noting the intended behaviour is also questionable: had the
call succeeded, cwd=None would fall back to config.agent.workspace_dir rather than
the directory the client asked for, so the agent would silently operate somewhere
else. Returning a clear error for an unknown session seems better than reviving the
fallback.
Smaller things noticed while reading
self._sessionsis never evicted, so anAgent(with its full message history) is
retained per session for the process lifetime.initializeignoresparamsentirely, soclientCapabilitiesare never read (the
client'sfs/terminalcannot be used) and the protocol version is echoed rather
than negotiated.AgentCapabilities(loadSession=False)is hardcoded and noloadSession/
authenticate/setSessionModemethods exist. That is a fair description of what
is implemented; flagging it only because clients useloadSessionto decide whether
a session handle can be reused, so Mini-Agent is treated as single-turn by clients
that gate on it -- even though prompting onesessionIdtwice does hold context
correctly, which I confirmed.README.mdlinks "ACP" tomodelcontextprotocol/protocol; the code implements
Zed's Agent Client Protocol (agent-client-protocol), which is a different spec.pytest,pipandpipxare listed in[project.dependencies]rather than as dev
extras.
Happy to open a PR for 1 and 2 if that is useful.
Activity