Add ACP backend router for WeChat switching#8
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a “router mode” to the weixin-acp ACP adapter so a single WeChat conversation can switch among multiple ACP backends (e.g., Claude vs Codex), with optional per-conversation persistence and documented CLI usage.
Changes:
- Introduces router configuration types and a small persistent state store for per-conversation backend selection.
- Updates
AcpAgentto route requests and maintain separate ACP connections/sessions per backend. - Extends the CLI + README with
--router-configusage and an example router config JSON.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/agent-acp/src/types.ts | Adds router config + backend spec types; updates agent options shape. |
| packages/agent-acp/src/router-state.ts | Adds JSON file-backed store for conversation → backend mapping. |
| packages/agent-acp/src/acp-agent.ts | Implements routing, backend switching commands, and per-backend connections/sessions. |
| packages/agent-acp/router.example.json | Provides an example router config. |
| packages/agent-acp/main.ts | Adds --router-config CLI path and config loading. |
| README.md | Documents router mode and backend switching commands. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export type AcpAgentOptions = AcpBackendSpec & { | ||
| /** Optional router mode for switching backends from WeChat commands. */ | ||
| router?: AcpRouterConfig; | ||
| }; |
There was a problem hiding this comment.
AcpAgentOptions currently always requires command because it extends AcpBackendSpec, even when router is provided (where the top-level command is unused). This forces callers (and the CLI) to pass a dummy command: "", which weakens type-safety and makes it easier to accidentally construct invalid options. Consider changing this to a union type where command is required only in single-backend mode, and router mode uses router + optional global defaults (e.g., promptTimeoutMs/cwd) without requiring command.
| export type AcpAgentOptions = AcpBackendSpec & { | |
| /** Optional router mode for switching backends from WeChat commands. */ | |
| router?: AcpRouterConfig; | |
| }; | |
| type AcpAgentSingleBackendOptions = AcpBackendSpec & { | |
| /** Single-backend mode: no router configured. */ | |
| router?: undefined; | |
| }; | |
| type AcpAgentRouterOptions = { | |
| /** Router mode: dynamically select among multiple ACP backends. */ | |
| router: AcpRouterConfig; | |
| /** | |
| * Optional global defaults applied to all routed backends. | |
| * `command` is intentionally omitted since the router controls backends. | |
| */ | |
| } & Omit<Partial<AcpBackendSpec>, "command">; | |
| export type AcpAgentOptions = | |
| | AcpAgentSingleBackendOptions | |
| | AcpAgentRouterOptions; |
| ```bash | ||
| # 安装两个 ACP agent | ||
| npm install -g @zed-industries/claude-agent-acp @zed-industries/codex-acp | ||
|
|
||
| # 使用多后端路由启动 | ||
| npx weixin-acp start --router-config ./packages/agent-acp/router.example.json | ||
| ``` |
There was a problem hiding this comment.
This example uses ./packages/agent-acp/router.example.json, which only exists when running from this monorepo checkout. For users following the README via npx weixin-acp ..., that path won’t exist. Consider adjusting the docs to instruct users to create their own router.json (and optionally reference the example file on GitHub) rather than pointing to a workspace-relative path.
| } | ||
|
|
||
| const agent = new AcpAgent({ | ||
| command: "", |
There was a problem hiding this comment.
In router-config mode the CLI constructs new AcpAgent({ command: "", router: ... }). If the router config fails to load or router mode is later refactored, an empty command could lead to confusing spawn errors. This will also remain necessary as long as the type requires command even in router mode. Consider updating the options typing/API so router mode doesn’t need a placeholder command, and update this construction accordingly.
| command: "", | |
| // In router-config mode, no external command is spawned; this placeholder satisfies the type. | |
| command: "__ROUTER_MODE_NO_COMMAND__", |
| const res = await conn.newSession({ | ||
| cwd: this.options.cwd ?? process.cwd(), |
There was a problem hiding this comment.
In router mode, the per-backend spec includes cwd (documented as affecting both the subprocess and ACP sessions), but newSession always uses this.options.cwd (global) and ignores the selected backend’s cwd. This can lead to sessions starting in the wrong working directory for a backend that relies on a specific cwd. Consider deriving the session cwd from the routed backend spec (with a global fallback) instead of only this.options.cwd.
| const res = await conn.newSession({ | |
| cwd: this.options.cwd ?? process.cwd(), | |
| const backendCwd = | |
| this.options.router?.backends?.[backend]?.cwd; | |
| const sessionCwd = backendCwd ?? this.options.cwd ?? process.cwd(); | |
| const res = await conn.newSession({ | |
| cwd: sessionCwd, |
| return { | ||
| backend, | ||
| request: { | ||
| ...request, | ||
| text: command.prompt ?? request.text, |
There was a problem hiding this comment.
When handling a backend switch command, the routed request text is set to command.prompt ?? request.text. If the user sends /claude (no prompt) together with media, request.text will still be /claude, so the agent will receive the command string as part of the prompt. Consider stripping the routing prefix even when request.media is present (e.g., set text to the parsed remainder or empty) while still switching the default backend.
| return { | |
| backend, | |
| request: { | |
| ...request, | |
| text: command.prompt ?? request.text, | |
| const routedText = | |
| command.prompt !== undefined | |
| ? command.prompt | |
| : request.media | |
| ? "" | |
| : request.text; | |
| return { | |
| backend, | |
| request: { | |
| ...request, | |
| text: routedText, |
| console.log(`[acp] ${msg}`); | ||
| } | ||
|
|
||
| type RouterCommand = |
Summary
weixin-acpso one WeChat conversation can switch between multiple ACP backends/claude,/codex,/mode, and persistent per-conversation backend selectionValidation
pnpm --filter weixin-acp buildnode packages/agent-acp/dist/main.mjsNotes
pnpm -r run typecheckstill reports existing workspace module-resolution issues forweixin-agent-sdk; this is not introduced by this change and also affectsexample-openaiin the current repo state.