@pi-in-go/pigpen-acp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CREDITS.md +23 -0
- package/LICENSE +22 -0
- package/README.md +110 -0
- package/extensions/acp/cmd/pig-acp/e2e_llm_test.go +168 -0
- package/extensions/acp/cmd/pig-acp/e2e_test.go +541 -0
- package/extensions/acp/cmd/pig-acp/go.mod +6 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent.go +784 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_builtin_test.go +536 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_commands_test.go +199 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_config_test.go +530 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_helpers_test.go +215 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_load_test.go +333 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_prompt.go +289 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_session_test.go +429 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/auth.go +61 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/bench_test.go +47 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/helpers_test.go +421 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/main_test.go +28 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/mutation_extra_test.go +142 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/paths.go +77 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/pisessions.go +317 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/protocol.go +188 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/server.go +218 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/server_test.go +386 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session.go +1068 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session_diff_test.go +183 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session_events_test.go +443 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session_extra_test.go +547 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session_usage_test.go +198 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/settings.go +79 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/slash.go +278 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/slash_test.go +128 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/startup.go +156 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/store.go +118 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/store_paths_test.go +466 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/translate.go +352 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/translate_test.go +105 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/twin_test.go +28 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/types.go +202 -0
- package/extensions/acp/cmd/pig-acp/internal/jsonrpc/bench_test.go +24 -0
- package/extensions/acp/cmd/pig-acp/internal/jsonrpc/jsonrpc.go +302 -0
- package/extensions/acp/cmd/pig-acp/internal/jsonrpc/jsonrpc_test.go +320 -0
- package/extensions/acp/cmd/pig-acp/internal/jsonrpc/twin_test.go +28 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/kill_unix.go +19 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/kill_windows.go +11 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/process.go +530 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/process_test.go +355 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/twin_test.go +28 -0
- package/extensions/acp/cmd/pig-acp/main.go +177 -0
- package/extensions/acp/cmd/pig-acp/main_test.go +213 -0
- package/extensions/acp/cmd/pig-acp/sigpipe_unix.go +10 -0
- package/extensions/acp/cmd/pig-acp/sigpipe_windows.go +5 -0
- package/extensions/acp/cmd/pig-acp/testdata/ask/extension.go +28 -0
- package/extensions/acp/cmd/pig-acp/testdata/ask/go.mod +7 -0
- package/extensions/acp/cmd/pig-acp/testdata/ask/go.sum +2 -0
- package/extensions/acp/cmd/pig-acp/testdata/gate/extension.go +37 -0
- package/extensions/acp/cmd/pig-acp/testdata/gate/go.mod +7 -0
- package/extensions/acp/cmd/pig-acp/testdata/gate/go.sum +2 -0
- package/extensions/acp/cmd/pig-acp/twin_test.go +28 -0
- package/extensions/acp/extension.go +63 -0
- package/extensions/acp/extension_test.go +111 -0
- package/extensions/acp/fakehost_test.go +548 -0
- package/extensions/acp/go.mod +6 -0
- package/extensions/acp/go.sum +2 -0
- package/package.json +41 -0
- package/port/PORT.md +147 -0
- package/port/interop/package-lock.json +89 -0
- package/port/interop/package.json +13 -0
- package/port/interop/run.mjs +229 -0
- package/port/mutate.mjs +110 -0
- package/port/mutation-results.txt +107 -0
- package/port/mutations.json +748 -0
- package/port/oracle/pi-acp/LICENSE +21 -0
- package/port/oracle/pi-acp/README.md +209 -0
- package/port/oracle/pi-acp/package.json +65 -0
- package/port/oracle/pi-acp/src/acp/agent.ts +1661 -0
- package/port/oracle/pi-acp/src/acp/auth-required.ts +37 -0
- package/port/oracle/pi-acp/src/acp/auth.ts +59 -0
- package/port/oracle/pi-acp/src/acp/paths.ts +15 -0
- package/port/oracle/pi-acp/src/acp/pi-commands.ts +59 -0
- package/port/oracle/pi-acp/src/acp/pi-sessions.ts +333 -0
- package/port/oracle/pi-acp/src/acp/pi-settings.ts +75 -0
- package/port/oracle/pi-acp/src/acp/session-store.ts +68 -0
- package/port/oracle/pi-acp/src/acp/session.ts +1078 -0
- package/port/oracle/pi-acp/src/acp/slash-commands.ts +197 -0
- package/port/oracle/pi-acp/src/acp/translate/bash.ts +100 -0
- package/port/oracle/pi-acp/src/acp/translate/pi-messages.ts +17 -0
- package/port/oracle/pi-acp/src/acp/translate/pi-tools.ts +51 -0
- package/port/oracle/pi-acp/src/acp/translate/prompt.ts +71 -0
- package/port/oracle/pi-acp/src/index.ts +82 -0
- package/port/oracle/pi-acp/src/pi-rpc/command.ts +16 -0
- package/port/oracle/pi-acp/src/pi-rpc/process.ts +432 -0
- package/port/oracle/pi-acp/test/component/agent-steering-followup-modes.test.ts +143 -0
- package/port/oracle/pi-acp/test/component/session-diff.test.ts +214 -0
- package/port/oracle/pi-acp/test/component/session-events.test.ts +1102 -0
- package/port/oracle/pi-acp/test/component/session-list-and-load.test.ts +129 -0
- package/port/oracle/pi-acp/test/component/session-list-custom-session-dir.test.ts +52 -0
- package/port/oracle/pi-acp/test/component/session-list-scoped.test.ts +76 -0
- package/port/oracle/pi-acp/test/component/session-load-toolresult.test.ts +68 -0
- package/port/oracle/pi-acp/test/component/session-queue-cancel.test.ts +43 -0
- package/port/oracle/pi-acp/test/component/session-slash-commands.test.ts +37 -0
- package/port/oracle/pi-acp/test/component/session-thinking-modes.test.ts +22 -0
- package/port/oracle/pi-acp/test/component/session-title-long-session.test.ts +56 -0
- package/port/oracle/pi-acp/test/component/session-updatedAt-message-only.test.ts +57 -0
- package/port/oracle/pi-acp/test/helpers/fakes.ts +87 -0
- package/port/oracle/pi-acp/test/unit/auth-methods-terminal-auth-meta.test.ts +22 -0
- package/port/oracle/pi-acp/test/unit/builtin-commands.test.ts +60 -0
- package/port/oracle/pi-acp/test/unit/context-usage.test.ts +204 -0
- package/port/oracle/pi-acp/test/unit/merge-commands.test.ts +19 -0
- package/port/oracle/pi-acp/test/unit/model-thinking-levels.test.ts +268 -0
- package/port/oracle/pi-acp/test/unit/new-session-auth-required-when-no-models.test.ts +51 -0
- package/port/oracle/pi-acp/test/unit/new-session-pi-not-found.test.ts +26 -0
- package/port/oracle/pi-acp/test/unit/new-session-runtime-startup-errors.test.ts +148 -0
- package/port/oracle/pi-acp/test/unit/pi-command.test.ts +44 -0
- package/port/oracle/pi-acp/test/unit/pi-commands.test.ts +32 -0
- package/port/oracle/pi-acp/test/unit/pi-enable-embed-context-flag.test.ts +35 -0
- package/port/oracle/pi-acp/test/unit/pi-messages.test.ts +29 -0
- package/port/oracle/pi-acp/test/unit/pi-rpc-request-timeout.test.ts +137 -0
- package/port/oracle/pi-acp/test/unit/pi-rpc-session-path.test.ts +93 -0
- package/port/oracle/pi-acp/test/unit/pi-tools.test.ts +40 -0
- package/port/oracle/pi-acp/test/unit/prompt-to-pi-message.test.ts +74 -0
- package/port/oracle/pi-acp/test/unit/session-config-options.test.ts +217 -0
- package/port/oracle/pi-acp/test/unit/session-delete.test.ts +170 -0
- package/port/oracle/pi-acp/test/unit/session-restore.test.ts +239 -0
- package/port/oracle/pi-acp/test/unit/slash-commands.test.ts +36 -0
- package/port/oracle/pi-acp/test/unit/startup-info-env.test.ts +84 -0
- package/port/oracle/pi-acp/test/unit/startup-info-load-session.test.ts +53 -0
- package/port/oracle/pi-acp/test/unit/startup-info-project-packages.test.ts +74 -0
- package/port/oracle/pi-acp/test/unit/stdout-destroyed-does-not-crash.test.ts +32 -0
- package/port/oracle/pi-acp/test/unit/thinking-level-rpc.test.ts +43 -0
- package/port/schema/LICENSE +191 -0
- package/port/schema/schema.json +8015 -0
- package/port/upstream-tests.json +201 -0
- package/provenance.json +28 -0
package/CREDITS.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Credits
|
|
2
|
+
|
|
3
|
+
`extensions/acp/cmd/pig-acp` is a Go port of **pi-acp**, https://github.com/svkozak/pi-acp, by
|
|
4
|
+
**Sergii Kozak** (MIT, Copyright (c) 2025 Sergii Kozak): the Agent Client Protocol adapter for the Pi
|
|
5
|
+
coding agent, which the port adapts to PiG.
|
|
6
|
+
|
|
7
|
+
- Original: `src/` and `test/` of pi-acp 0.0.34, commit `b0581c9c1d675e634234674484247008b03d69b4`.
|
|
8
|
+
- The unmodified original, with its README, `package.json` and license, is kept at
|
|
9
|
+
[`port/oracle/pi-acp`](port/oracle/pi-acp) as the oracle the Go tests were ported from. Its license is at
|
|
10
|
+
[`port/oracle/pi-acp/LICENSE`](port/oracle/pi-acp/LICENSE).
|
|
11
|
+
- What was modified: nothing of the original is modified in place. The port is a separate Go implementation with the
|
|
12
|
+
same structure (`agent`, `session`, `translate`, `pi-rpc`), PiG's directories and executable in place of Pi's,
|
|
13
|
+
the adapter's own JSON-RPC transport in place of the ACP SDK, and the additions listed in
|
|
14
|
+
[`port/PORT.md`](port/PORT.md). The ported tests keep the original test names.
|
|
15
|
+
|
|
16
|
+
The protocol is the **Agent Client Protocol**, https://agentclientprotocol.com. The JSON schema in
|
|
17
|
+
[`port/schema/schema.json`](port/schema/schema.json) is `schema/schema.json` of
|
|
18
|
+
`@agentclientprotocol/sdk` 0.26.0 by **Zed Industries** (Apache-2.0, https://github.com/agentclientprotocol/typescript-sdk,
|
|
19
|
+
tag `v0.26.0`, commit `73bc30649b650de320340c782733bf69a545bd28`), unmodified, with its license at
|
|
20
|
+
[`port/schema/LICENSE`](port/schema/LICENSE). It is used only to check the adapter's messages in tests.
|
|
21
|
+
|
|
22
|
+
The Go code, the scenarios and the tests were written for this Package by Michael Kinsy and are MIT
|
|
23
|
+
licensed like the rest of Pigpen.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sergii Kozak
|
|
4
|
+
Copyright (c) 2026 Michael Kinsy (the Go port)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# acp: Agent Client Protocol for PiG
|
|
2
|
+
|
|
3
|
+
Lets an editor that speaks the [Agent Client Protocol](https://agentclientprotocol.com) (ACP), such as
|
|
4
|
+
[Zed](https://zed.dev), drive a PiG agent: sessions, streamed answers, tool calls with diffs and terminal output,
|
|
5
|
+
cancel, model and thinking-level selection, context usage, and permission requests for extension dialogs.
|
|
6
|
+
|
|
7
|
+
It is a Go port of [pi-acp](https://github.com/svkozak/pi-acp) by Sergii Kozak (MIT). See [CREDITS.md](CREDITS.md)
|
|
8
|
+
and the port record, [port/PORT.md](port/PORT.md). **Pinned protocol: ACP version 1, schema of
|
|
9
|
+
`@agentclientprotocol/sdk` 0.26.0.** Built against PiG 0.4.0 (Pi 1.0.0); the numbers under Evidence were first recorded on PiG 0.3.0, and the end-to-end scenarios, the ACP reference client run and the Go tests pass again on 0.4.0.
|
|
10
|
+
|
|
11
|
+
The Package has two parts:
|
|
12
|
+
|
|
13
|
+
| Part | What it is |
|
|
14
|
+
|---|---|
|
|
15
|
+
| [`extensions/acp/cmd/pig-acp`](extensions/acp/cmd/pig-acp) | **The protocol entrypoint**, a separate executable with no dependency (not even the PiG SDK). The editor starts it and speaks ACP on its stdin and stdout; it starts `pig --mode rpc` for each session. It is not the TUI and shares nothing with it. |
|
|
16
|
+
| [`extensions/acp`](extensions/acp) | A small Go extension on the public SDK. `/acp` prints the editor configuration for the running agent and the limits below. |
|
|
17
|
+
|
|
18
|
+
An extension cannot speak ACP itself: a fused extension must not write protocol bytes to the host's stdout. So
|
|
19
|
+
**installing this Package, or building the `acp` Piglet, does not make the Piglet Binary an ACP server.** The editor
|
|
20
|
+
runs `pig-acp`, and `pig-acp --pig <your pig or Piglet Binary>` runs the agent. There is no one-command Binary yet
|
|
21
|
+
(see [RELEASE-BLOCKERS.md](../../RELEASE-BLOCKERS.md)).
|
|
22
|
+
|
|
23
|
+
## Use
|
|
24
|
+
|
|
25
|
+
Build the companion (Go 1.26 or later) and put it on your `PATH`:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
cd components/acp/extensions/acp/cmd/pig-acp
|
|
29
|
+
go build -trimpath -o pig-acp .
|
|
30
|
+
pig-acp --version # pig-acp 0.1.0 (ACP protocol version 1)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Zed, `settings.json` (`/acp` prints this with your paths filled in):
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"agent_servers": {
|
|
38
|
+
"PiG": { "type": "custom", "command": "pig-acp", "args": ["--pig", "/path/to/pig"], "env": {} }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Other ACP clients run `pig-acp --pig <pig>` and speak newline-delimited JSON-RPC on stdio. `--pig` also takes a built Piglet
|
|
44
|
+
Binary (`--pig ./acp`). `--piglet <name-or-file>` starts pig with `--piglet ...`, and `--pig-arg <arg>` (repeatable) passes any
|
|
45
|
+
other argument. Without `--pig`, `PIG_ACP_PIG_COMMAND` (or `PI_ACP_PI_COMMAND`) is used, then `pig`.
|
|
46
|
+
|
|
47
|
+
Signing in: if pig has no model or key, `session/new` fails with the ACP "authentication required" error (-32000) and one
|
|
48
|
+
terminal auth method, `pig-acp --terminal-login`, which starts pig interactively in the editor's terminal.
|
|
49
|
+
|
|
50
|
+
| Flag or variable | Meaning |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `--pig <command>` | the pig executable or Piglet Binary to drive |
|
|
53
|
+
| `--piglet <value>`, `--pig-arg <arg>` | extra pig arguments |
|
|
54
|
+
| `--terminal-login` | start pig interactively (sign in, configure keys) and exit with its status |
|
|
55
|
+
| `--version`, `--help` | print and exit |
|
|
56
|
+
| `PIG_ACP_PIG_COMMAND` | the pig executable when `--pig` is not given |
|
|
57
|
+
| `PIG_ACP_ENABLE_EMBEDDED_CONTEXT=true` | advertise embedded-context prompts (off by default) |
|
|
58
|
+
| `PIG_HOME`, `PIG_CODING_AGENT_DIR`, `PIG_CODING_AGENT_SESSION_DIR`, `PIG_USE_PI_DIRS=1` | pig's own directories are used as pig uses them (a leading `~` is expanded) |
|
|
59
|
+
|
|
60
|
+
The session map (ACP session id to pig session file) is `<PIG_HOME>/pig-acp/session-map.json`. Sessions are pig's own session
|
|
61
|
+
files, so they show up in `pig` too. `pig-acp` reads prompt templates from `<agent dir>/prompts` and `<cwd>/.pig/prompts` and
|
|
62
|
+
settings from `<agent dir>/settings.json` and `<cwd>/.pig/settings.json`. It never reads `auth.json` for itself; like pi-acp, it
|
|
63
|
+
reads a file that pig's `edit` or `write` tool changes, before and after, to send the diff to the editor.
|
|
64
|
+
|
|
65
|
+
## What is implemented
|
|
66
|
+
|
|
67
|
+
Advertised in `initialize`, and only these: `loadSession`, prompt `image` (embedded context only by the variable above; no
|
|
68
|
+
audio), `sessionCapabilities` `list` and `delete`, MCP `http` and `sse` **false**.
|
|
69
|
+
|
|
70
|
+
- `session/new`, `session/load` (history replayed, including tool results), `session/list` (paged by 50, scoped to the last
|
|
71
|
+
cwd), `session/delete` (idempotent), `session/prompt`, `session/cancel`, `session/set_mode` (thinking level),
|
|
72
|
+
`session/set_config_option` (model, thinking level), `session/set_model`, `authenticate`.
|
|
73
|
+
- Streaming: `agent_message_chunk`, `agent_thought_chunk`, `tool_call` and `tool_call_update` with locations, structured
|
|
74
|
+
diffs for edit and write, `execute` calls with terminal output for bash, `usage_update`, `available_commands_update`,
|
|
75
|
+
`config_option_update`, `current_mode_update`, `session_info_update`.
|
|
76
|
+
- Adapter-side slash commands that never reach the model: `/compact`, `/autocompact`, `/session`, `/name`, `/steering`,
|
|
77
|
+
`/follow-up`, `/changelog`, `/export`. Prompt template files expand as `/name args`.
|
|
78
|
+
- **Permissions.** An extension `select` or `confirm` dialog becomes `session/request_permission`; the editor's answer is
|
|
79
|
+
sent back to the extension. A client that fails or cancels means "cancelled". Nothing is ever executed locally in place of an
|
|
80
|
+
editor's answer. `input` and `editor` dialogs are cancelled with a visible notice; `notify` becomes a chunk with its level.
|
|
81
|
+
- One live pig child per ACP connection: a new session or load closes the previous child. Closing stdin (or SIGINT/SIGTERM)
|
|
82
|
+
stops every child and exits 0. Nothing but protocol messages is written to stdout.
|
|
83
|
+
|
|
84
|
+
## Not supported (by design, as upstream)
|
|
85
|
+
|
|
86
|
+
- File system and terminal delegation: `pig-acp` never calls `fs/*` or `terminal/*` on the editor; pig reads and writes files itself.
|
|
87
|
+
- MCP servers passed by the editor are accepted and stored, never started (PiG's own MCP support is configured in PiG).
|
|
88
|
+
- Extension slash commands (`/name` of an extension is not advertised, and as a prompt it runs without ending a turn: drive
|
|
89
|
+
extensions through tools). `session/fork`, `session/resume`, `session/close` are Method not found.
|
|
90
|
+
- The npm "New version available" notice: PiG has no npm package (`pig update` uses a signed manifest).
|
|
91
|
+
|
|
92
|
+
## Evidence
|
|
93
|
+
|
|
94
|
+
Everything below was first run on PiG 0.3.0 and re-run on 0.4.0 (the 11 end-to-end scenarios, the reference client's 30 checked messages, the Go tests) with a temporary `HOME`, `PIG_HOME` and agent directories.
|
|
95
|
+
|
|
96
|
+
- **Twins of the upstream tests:** 124 exact twins with their upstream titles plus 1 named skip, checked by
|
|
97
|
+
`pigeq twins check` against the upstream ledger (`port/upstream-tests.json`). Two more skipped tests name the remaining gaps (the npm update notice, the compaction event names); see `port/PORT.md`.
|
|
98
|
+
- **Unit and protocol tests:** `go test -race ./...` in both modules (a scripted ACP client over pipes against `Serve`).
|
|
99
|
+
- **End to end:** 11 scenarios (streaming, bash, diff, cancel, load/list/delete across two adapter runs, config options, a real
|
|
100
|
+
permission round trip through a Go extension tool, no-model auth error, missing pig, MCP accepted, exit on stdin close) pass against
|
|
101
|
+
a real `pig` **and against the built `acp` Piglet Binary** (`PIG_ACP_E2E_PIG=<binary> go test -run E2E`).
|
|
102
|
+
- **Reference client:** `components/acp/port/interop` runs the ACP SDK's `ClientSideConnection` 0.26.0 against `pig-acp` and the
|
|
103
|
+
Piglet Binary and validates every message against the pinned schema: `npm ci --ignore-scripts` in `port/interop`, then
|
|
104
|
+
`PIG_ACP=<pig-acp> PIG=<pig or Binary> PIGEQ=<pigeq> npm run test:acp-interop`. The run needs the `pigeq` built from
|
|
105
|
+
this repository (`PIG_BIN=<pig> npm run build:pigeq` writes `dist/bin/pigeq`), which has the `llm` command; so does the twin
|
|
106
|
+
ledger check (`pigeq twins check`).
|
|
107
|
+
- **Mutations:** 105 deliberate defects (`port/mutations.json`, `node components/acp/port/mutate.mjs`), 104 killed and 1 documented
|
|
108
|
+
equivalent; results in `port/mutation-results.txt`.
|
|
109
|
+
|
|
110
|
+
**Not verified:** Zed and JetBrains themselves (no editor could be run here). The reference client is the interop evidence.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
// A scripted OpenAI-compatible chat-completions server, so the end-to-end tests drive a real
|
|
4
|
+
// `pig --mode rpc` without credentials or a network (the same idea as the equivalence harness).
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"fmt"
|
|
9
|
+
"net"
|
|
10
|
+
"net/http"
|
|
11
|
+
"strings"
|
|
12
|
+
"sync"
|
|
13
|
+
"testing"
|
|
14
|
+
"time"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
type llmCall struct {
|
|
18
|
+
Name string
|
|
19
|
+
Args map[string]any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// llmTurn is one scripted model reply.
|
|
23
|
+
type llmTurn struct {
|
|
24
|
+
Text string
|
|
25
|
+
Calls []llmCall
|
|
26
|
+
// Chunks streams Text in this many pieces (default 1).
|
|
27
|
+
Chunks int
|
|
28
|
+
// Hold blocks the reply after its first chunk until the channel closes or the client disconnects.
|
|
29
|
+
Hold chan struct{}
|
|
30
|
+
// Started is closed when the first chunk of this turn has been sent.
|
|
31
|
+
Started chan struct{}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type fakeLLM struct {
|
|
35
|
+
t *testing.T
|
|
36
|
+
ln net.Listener
|
|
37
|
+
srv *http.Server
|
|
38
|
+
mu sync.Mutex
|
|
39
|
+
turns []llmTurn
|
|
40
|
+
next int
|
|
41
|
+
// requests records the last user text of each request.
|
|
42
|
+
requests []string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func startFakeLLM(t *testing.T, turns ...llmTurn) *fakeLLM {
|
|
46
|
+
t.Helper()
|
|
47
|
+
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
48
|
+
if err != nil {
|
|
49
|
+
t.Fatal(err)
|
|
50
|
+
}
|
|
51
|
+
f := &fakeLLM{t: t, ln: ln, turns: turns}
|
|
52
|
+
mux := http.NewServeMux()
|
|
53
|
+
mux.HandleFunc("/v1/chat/completions", f.handle)
|
|
54
|
+
f.srv = &http.Server{Handler: mux}
|
|
55
|
+
go func() { _ = f.srv.Serve(ln) }()
|
|
56
|
+
t.Cleanup(func() { _ = f.srv.Close() })
|
|
57
|
+
return f
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func (f *fakeLLM) baseURL() string { return "http://" + f.ln.Addr().String() + "/v1" }
|
|
61
|
+
|
|
62
|
+
func (f *fakeLLM) seen() []string {
|
|
63
|
+
f.mu.Lock()
|
|
64
|
+
defer f.mu.Unlock()
|
|
65
|
+
return append([]string(nil), f.requests...)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func (f *fakeLLM) handle(w http.ResponseWriter, r *http.Request) {
|
|
69
|
+
var req struct {
|
|
70
|
+
Messages []struct {
|
|
71
|
+
Role string `json:"role"`
|
|
72
|
+
Content json.RawMessage `json:"content"`
|
|
73
|
+
} `json:"messages"`
|
|
74
|
+
}
|
|
75
|
+
_ = json.NewDecoder(r.Body).Decode(&req)
|
|
76
|
+
last := ""
|
|
77
|
+
for _, m := range req.Messages {
|
|
78
|
+
if m.Role == "user" {
|
|
79
|
+
var s string
|
|
80
|
+
if json.Unmarshal(m.Content, &s) == nil {
|
|
81
|
+
last = s
|
|
82
|
+
} else {
|
|
83
|
+
var parts []struct {
|
|
84
|
+
Text string `json:"text"`
|
|
85
|
+
}
|
|
86
|
+
_ = json.Unmarshal(m.Content, &parts)
|
|
87
|
+
if len(parts) > 0 {
|
|
88
|
+
last = parts[0].Text
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
f.mu.Lock()
|
|
94
|
+
f.requests = append(f.requests, last)
|
|
95
|
+
i := f.next
|
|
96
|
+
f.next++
|
|
97
|
+
f.mu.Unlock()
|
|
98
|
+
if i >= len(f.turns) {
|
|
99
|
+
http.Error(w, "llm script exhausted", http.StatusInternalServerError)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
turn := f.turns[i]
|
|
103
|
+
w.Header().Set("Content-Type", "text/event-stream")
|
|
104
|
+
flusher, _ := w.(http.Flusher)
|
|
105
|
+
send := func(delta map[string]any, finish any) {
|
|
106
|
+
b, _ := json.Marshal(map[string]any{"id": fmt.Sprintf("acp-%d", i), "object": "chat.completion.chunk", "created": 1, "model": "acp-1",
|
|
107
|
+
"choices": []any{map[string]any{"index": 0, "delta": delta, "finish_reason": finish}}})
|
|
108
|
+
fmt.Fprintf(w, "data: %s\n\n", b)
|
|
109
|
+
flusher.Flush()
|
|
110
|
+
}
|
|
111
|
+
send(map[string]any{"role": "assistant", "content": ""}, nil)
|
|
112
|
+
chunks := turn.Chunks
|
|
113
|
+
if chunks < 1 {
|
|
114
|
+
chunks = 1
|
|
115
|
+
}
|
|
116
|
+
if turn.Text != "" {
|
|
117
|
+
pieces := splitText(turn.Text, chunks)
|
|
118
|
+
for n, p := range pieces {
|
|
119
|
+
send(map[string]any{"content": p}, nil)
|
|
120
|
+
if n == 0 && turn.Started != nil {
|
|
121
|
+
close(turn.Started)
|
|
122
|
+
}
|
|
123
|
+
if n == 0 && turn.Hold != nil {
|
|
124
|
+
select {
|
|
125
|
+
case <-turn.Hold:
|
|
126
|
+
case <-r.Context().Done():
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} else if turn.Started != nil {
|
|
132
|
+
close(turn.Started)
|
|
133
|
+
}
|
|
134
|
+
finish := "stop"
|
|
135
|
+
for j, c := range turn.Calls {
|
|
136
|
+
args, _ := json.Marshal(c.Args)
|
|
137
|
+
send(map[string]any{"tool_calls": []any{map[string]any{"index": j, "id": fmt.Sprintf("call_%d_%d", i, j), "type": "function",
|
|
138
|
+
"function": map[string]any{"name": c.Name, "arguments": string(args)}}}}, nil)
|
|
139
|
+
finish = "tool_calls"
|
|
140
|
+
}
|
|
141
|
+
send(map[string]any{}, finish)
|
|
142
|
+
usage, _ := json.Marshal(map[string]any{"id": fmt.Sprintf("acp-%d", i), "object": "chat.completion.chunk", "created": 1, "model": "acp-1", "choices": []any{},
|
|
143
|
+
"usage": map[string]any{"prompt_tokens": 1234, "completion_tokens": 5, "total_tokens": 1239}})
|
|
144
|
+
fmt.Fprintf(w, "data: %s\n\ndata: [DONE]\n\n", usage)
|
|
145
|
+
flusher.Flush()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
func splitText(s string, n int) []string {
|
|
149
|
+
if n <= 1 || len(s) < n {
|
|
150
|
+
return []string{s}
|
|
151
|
+
}
|
|
152
|
+
words := strings.SplitAfter(s, " ")
|
|
153
|
+
if len(words) < n {
|
|
154
|
+
return []string{s}
|
|
155
|
+
}
|
|
156
|
+
per := (len(words) + n - 1) / n
|
|
157
|
+
var out []string
|
|
158
|
+
for i := 0; i < len(words); i += per {
|
|
159
|
+
end := i + per
|
|
160
|
+
if end > len(words) {
|
|
161
|
+
end = len(words)
|
|
162
|
+
}
|
|
163
|
+
out = append(out, strings.Join(words[i:end], ""))
|
|
164
|
+
}
|
|
165
|
+
return out
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
var _ = time.Second
|