amp-acp 0.1.0 → 0.2.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/README.md +20 -26
- package/package.json +3 -3
- package/src/index.js +0 -0
- package/src/server.js +55 -63
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# ACP adapter for
|
|
1
|
+
# ACP adapter for AmpCode
|
|
2
2
|
|
|
3
|
-
Use [Amp](https://ampcode.com) from ACP-compatible clients such as [Zed](https://zed.dev).
|
|
3
|
+
Use [Amp](https://ampcode.com) from [ACP](https://agentclientprotocol.com/)-compatible clients such as [Zed](https://zed.dev).
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
@@ -9,37 +9,31 @@ Use [Amp](https://ampcode.com) from ACP-compatible clients such as [Zed](https:/
|
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Add to your Zed `settings.json` (open with `cmd+,` or `ctrl+,`):
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"agent_servers": {
|
|
17
|
+
"Amp": {
|
|
18
|
+
"command": "npx",
|
|
19
|
+
"args": ["-y", "amp-acp"],
|
|
20
|
+
"env": {
|
|
21
|
+
"AMP_EXECUTABLE": "path of AMP bin",
|
|
22
|
+
"AMP_PREFER_SYSTEM_PATH": "1"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
14
27
|
```
|
|
15
28
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm install
|
|
20
|
-
npm start
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Usage
|
|
24
|
-
|
|
25
|
-
1. Start the adapter:
|
|
26
|
-
```bash
|
|
27
|
-
amp-acp
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
2. In Zed:
|
|
31
|
-
- Open Settings → External Agents (ACP)
|
|
32
|
-
- Connect to the adapter
|
|
33
|
-
- Start a new Amp thread
|
|
29
|
+
Replace `"path of AMP bin"` with your Amp CLI path (e.g., `/usr/local/bin/amp`).
|
|
34
30
|
|
|
35
31
|
## How it Works
|
|
36
32
|
|
|
37
|
-
- Streams Amp's
|
|
33
|
+
- Streams Amp's JSON over ACP
|
|
38
34
|
- Renders Amp messages and interactions in Zed
|
|
39
35
|
- Tool permissions are handled by Amp (no additional configuration needed)
|
|
40
36
|
|
|
41
37
|
## Troubleshooting
|
|
42
38
|
|
|
43
|
-
**Connection fails**: Ensure `amp login` was successful and the CLI is in your
|
|
44
|
-
|
|
45
|
-
**Adapter not found**: Check that `amp-acp` is running before connecting from Zed.
|
|
39
|
+
**Connection fails**: Ensure `amp login` was successful and the CLI is in your `AMP_EXECUTABLE`.
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amp-acp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"amp-acp": "
|
|
7
|
+
"amp-acp": "src/index.js"
|
|
8
8
|
},
|
|
9
9
|
"description": "ACP adapter that bridges Amp CLI to Agent Client Protocol (Zed external agent)",
|
|
10
10
|
"license": "Apache-2.0",
|
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@agentclientprotocol/sdk": "0.4.8"
|
|
18
18
|
}
|
|
19
|
-
}
|
|
19
|
+
}
|
package/src/index.js
CHANGED
|
File without changes
|
package/src/server.js
CHANGED
|
@@ -56,94 +56,86 @@ export class AmpAcpAgent {
|
|
|
56
56
|
s.cancelled = false;
|
|
57
57
|
s.active = true;
|
|
58
58
|
|
|
59
|
-
// Start a fresh Amp process per turn
|
|
60
|
-
|
|
59
|
+
// Start a fresh Amp process per turn. Amp does not expose the Claude Code JSON stream flags;
|
|
60
|
+
// we pipe plain text and stream stdout lines back to ACP.
|
|
61
|
+
const ampCmd = process.env.AMP_EXECUTABLE || 'amp';
|
|
62
|
+
const spawnEnv = { ...process.env };
|
|
63
|
+
if (process.env.AMP_PREFER_SYSTEM_PATH === '1' && spawnEnv.PATH) {
|
|
64
|
+
// Drop npx/npm-local node_modules/.bin segments so we pick the system 'amp'
|
|
65
|
+
const parts = spawnEnv.PATH.split(':').filter((p) => !/\bnode_modules\/\.bin\b|\/_npx\//.test(p));
|
|
66
|
+
spawnEnv.PATH = parts.join(':');
|
|
67
|
+
}
|
|
68
|
+
const proc = spawn(ampCmd, ['--no-notifications'], {
|
|
61
69
|
cwd: params.cwd || process.cwd(),
|
|
62
70
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
63
|
-
env:
|
|
71
|
+
env: spawnEnv,
|
|
64
72
|
});
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
|
|
74
|
+
const rlOut = readline.createInterface({ input: proc.stdout });
|
|
75
|
+
const rlErr = readline.createInterface({ input: proc.stderr });
|
|
76
|
+
|
|
77
|
+
s.proc = proc;
|
|
78
|
+
s.rl = rlOut;
|
|
79
|
+
s.queue = null;
|
|
80
|
+
|
|
81
|
+
let hadOutput = false;
|
|
82
|
+
|
|
83
|
+
rlOut.on('line', async (line) => {
|
|
84
|
+
hadOutput = true;
|
|
85
|
+
if (!line) return;
|
|
86
|
+
try {
|
|
87
|
+
await this.client.sessionUpdate({
|
|
88
|
+
sessionId: params.sessionId,
|
|
89
|
+
update: {
|
|
90
|
+
sessionUpdate: 'agent_message_chunk',
|
|
91
|
+
content: { type: 'text', text: line },
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error('[acp] sessionUpdate failed', e);
|
|
96
|
+
}
|
|
71
97
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.error(`[amp] ${
|
|
98
|
+
|
|
99
|
+
rlErr.on('line', (line) => {
|
|
100
|
+
console.error(`[amp] ${line}`);
|
|
75
101
|
});
|
|
76
|
-
s.proc = proc;
|
|
77
|
-
s.rl = rl;
|
|
78
|
-
s.queue = queue;
|
|
79
|
-
// Don't wait for init; amp will emit it before assistant/user events
|
|
80
102
|
|
|
81
|
-
// Build
|
|
82
|
-
|
|
103
|
+
// Build plain-text input for Amp from ACP prompt chunks
|
|
104
|
+
let textInput = '';
|
|
83
105
|
for (const chunk of params.prompt) {
|
|
84
106
|
switch (chunk.type) {
|
|
85
107
|
case 'text':
|
|
86
|
-
|
|
108
|
+
textInput += chunk.text;
|
|
87
109
|
break;
|
|
88
110
|
case 'resource_link':
|
|
89
|
-
|
|
111
|
+
textInput += `\n${chunk.uri}\n`;
|
|
90
112
|
break;
|
|
91
113
|
case 'resource':
|
|
92
114
|
if ('text' in chunk.resource) {
|
|
93
|
-
|
|
94
|
-
content.push({ type: 'text', text: `\n<context ref="${chunk.resource.uri}">\n${chunk.resource.text}\n</context>` });
|
|
115
|
+
textInput += `\n<context ref="${chunk.resource.uri}">\n${chunk.resource.text}\n</context>\n`;
|
|
95
116
|
}
|
|
96
117
|
break;
|
|
97
118
|
case 'image':
|
|
98
|
-
|
|
99
|
-
content.push({ type: 'image', source: { type: 'base64', data: chunk.data, media_type: chunk.mimeType } });
|
|
100
|
-
} else if (chunk.uri && chunk.uri.startsWith('http')) {
|
|
101
|
-
content.push({ type: 'image', source: { type: 'url', url: chunk.uri } });
|
|
102
|
-
}
|
|
119
|
+
// Images not supported by Amp CLI via stdin; ignore for now
|
|
103
120
|
break;
|
|
104
121
|
default:
|
|
105
122
|
break;
|
|
106
123
|
}
|
|
107
124
|
}
|
|
125
|
+
if (!textInput.endsWith('\n')) textInput += '\n';
|
|
108
126
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
message: { role: 'user', content },
|
|
112
|
-
parent_tool_use_id: null,
|
|
113
|
-
session_id: params.sessionId,
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
s.proc.stdin.write(JSON.stringify(userMsg) + '\n');
|
|
127
|
+
proc.stdin.write(textInput);
|
|
128
|
+
proc.stdin.end();
|
|
117
129
|
|
|
118
130
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
break;
|
|
128
|
-
case 'assistant': {
|
|
129
|
-
const notifs = toAcpNotifications(msg, params.sessionId);
|
|
130
|
-
for (const n of notifs) await this.client.sessionUpdate(n);
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
case 'user': {
|
|
134
|
-
// Skip echoing user messages to avoid duplicates in the client UI
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
case 'result': {
|
|
138
|
-
if (msg.subtype === 'success') return { stopReason: 'end_turn' };
|
|
139
|
-
if (msg.subtype === 'error_max_turns') return { stopReason: 'max_turn_requests' };
|
|
140
|
-
return { stopReason: 'refusal' };
|
|
141
|
-
}
|
|
142
|
-
default:
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
throw new Error('Session did not end in result');
|
|
131
|
+
await new Promise((resolve) => {
|
|
132
|
+
proc.on('close', () => {
|
|
133
|
+
try { rlOut.close(); } catch {}
|
|
134
|
+
try { rlErr.close(); } catch {}
|
|
135
|
+
resolve();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
return { stopReason: s.cancelled ? 'cancelled' : (hadOutput ? 'end_turn' : 'refusal') };
|
|
147
139
|
} finally {
|
|
148
140
|
s.active = false;
|
|
149
141
|
s.cancelled = false;
|