@supen-ai/cli 1.4.1 → 1.4.3
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 +1 -1
- package/daemon/dist/agent-sdk/app-server-stream.js +5 -5
- package/daemon/dist/agent-sdk/app-server-stream.js.map +1 -1
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts +3 -2
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js +72 -49
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js.map +1 -1
- package/daemon/dist/agent-sdk/drivers/driver.d.ts +8 -8
- package/daemon/dist/agent-sdk/drivers/driver.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/index.d.ts +4 -4
- package/daemon/dist/agent-sdk/index.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/index.js +2 -2
- package/daemon/dist/agent-sdk/index.js.map +1 -1
- package/daemon/dist/agent-sdk/intelligence/contracts.d.ts +2 -2
- package/daemon/dist/agent-sdk/intelligence/contracts.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/memory/subsystem.d.ts +1 -1
- package/daemon/dist/agent-sdk/memory/subsystem.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/{session-events.d.ts → thread-events.d.ts} +18 -18
- package/daemon/dist/agent-sdk/thread-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/{session-events.js → thread-events.js} +15 -15
- package/daemon/dist/agent-sdk/thread-events.js.map +1 -0
- package/daemon/dist/agent-sdk/{session-manager.d.ts → thread-manager.d.ts} +9 -9
- package/daemon/dist/agent-sdk/thread-manager.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/{session-manager.js → thread-manager.js} +7 -7
- package/daemon/dist/agent-sdk/thread-manager.js.map +1 -0
- package/daemon/dist/agent-sdk/types.d.ts +18 -18
- package/daemon/dist/agent-sdk/types.d.ts.map +1 -1
- package/daemon/dist/automation-event-listener.js +6 -6
- package/daemon/dist/automation-event-listener.js.map +1 -1
- package/daemon/dist/automation-runner.d.ts +1 -1
- package/daemon/dist/automation-runner.d.ts.map +1 -1
- package/daemon/dist/automation-runner.js +24 -24
- package/daemon/dist/automation-runner.js.map +1 -1
- package/daemon/dist/autonomy/memory-rules.d.ts +2 -2
- package/daemon/dist/autonomy/memory-rules.d.ts.map +1 -1
- package/daemon/dist/autonomy/memory-rules.js +1 -1
- package/daemon/dist/autonomy/memory-rules.js.map +1 -1
- package/daemon/dist/autonomy/session-autonomy.d.ts +4 -4
- package/daemon/dist/autonomy/session-autonomy.d.ts.map +1 -1
- package/daemon/dist/autonomy/session-autonomy.js +5 -5
- package/daemon/dist/autonomy/session-autonomy.js.map +1 -1
- package/daemon/dist/bin/mcp-os.js +1 -1
- package/daemon/dist/bin/mcp-os.js.map +1 -1
- package/daemon/dist/bin/mcp-scheduler.js +1 -1
- package/daemon/dist/bin/mcp-scheduler.js.map +1 -1
- package/daemon/dist/bin/supen-sys.js +1 -1
- package/daemon/dist/bin/supen-sys.js.map +1 -1
- package/daemon/dist/bootstrap/hub-bootstrap.js +1 -1
- package/daemon/dist/bootstrap/hub-bootstrap.js.map +1 -1
- package/daemon/dist/channels/http-routes.d.ts.map +1 -1
- package/daemon/dist/channels/http-routes.js +54 -60
- package/daemon/dist/channels/http-routes.js.map +1 -1
- package/daemon/dist/channels/http.js +2 -2
- package/daemon/dist/channels/http.js.map +1 -1
- package/daemon/dist/channels/index.d.ts +0 -1
- package/daemon/dist/channels/index.d.ts.map +1 -1
- package/daemon/dist/channels/index.js +0 -2
- package/daemon/dist/channels/index.js.map +1 -1
- package/daemon/dist/channels/registry.d.ts +1 -1
- package/daemon/dist/channels/registry.d.ts.map +1 -1
- package/daemon/dist/commands/builtin.d.ts +1 -1
- package/daemon/dist/commands/builtin.d.ts.map +1 -1
- package/daemon/dist/commands/builtin.js +2 -2
- package/daemon/dist/commands/builtin.js.map +1 -1
- package/daemon/dist/commands/catalog.js +3 -3
- package/daemon/dist/commands/catalog.js.map +1 -1
- package/daemon/dist/core/config.d.ts +0 -2
- package/daemon/dist/core/config.d.ts.map +1 -1
- package/daemon/dist/core/config.js +0 -5
- package/daemon/dist/core/config.js.map +1 -1
- package/daemon/dist/core/control-commands.d.ts +3 -3
- package/daemon/dist/core/control-commands.d.ts.map +1 -1
- package/daemon/dist/core/control-commands.js +6 -6
- package/daemon/dist/core/control-commands.js.map +1 -1
- package/daemon/dist/core/control-log.d.ts +4 -4
- package/daemon/dist/core/control-log.d.ts.map +1 -1
- package/daemon/dist/core/control-log.js +12 -12
- package/daemon/dist/core/control-log.js.map +1 -1
- package/daemon/dist/core/cortex.d.ts +4 -4
- package/daemon/dist/core/cortex.d.ts.map +1 -1
- package/daemon/dist/core/cortex.js +98 -117
- package/daemon/dist/core/cortex.js.map +1 -1
- package/daemon/dist/core/dispatcher.d.ts +3 -3
- package/daemon/dist/core/dispatcher.js +18 -18
- package/daemon/dist/core/dispatcher.js.map +1 -1
- package/daemon/dist/core/gateway-protocol.d.ts +0 -1
- package/daemon/dist/core/gateway-protocol.d.ts.map +1 -1
- package/daemon/dist/core/gateway.d.ts +3 -3
- package/daemon/dist/core/gateway.d.ts.map +1 -1
- package/daemon/dist/core/gateway.js +26 -26
- package/daemon/dist/core/gateway.js.map +1 -1
- package/daemon/dist/core/hub-snapshot.d.ts +1 -1
- package/daemon/dist/core/hub-snapshot.d.ts.map +1 -1
- package/daemon/dist/core/hub-snapshot.js +1 -1
- package/daemon/dist/core/hub-snapshot.js.map +1 -1
- package/daemon/dist/core/pairing.d.ts +2 -2
- package/daemon/dist/core/pairing.js +3 -3
- package/daemon/dist/core/pairing.js.map +1 -1
- package/daemon/dist/core/store.d.ts +38 -38
- package/daemon/dist/core/store.d.ts.map +1 -1
- package/daemon/dist/core/store.js +285 -289
- package/daemon/dist/core/store.js.map +1 -1
- package/daemon/dist/core/task-artifacts.d.ts +4 -4
- package/daemon/dist/core/task-artifacts.d.ts.map +1 -1
- package/daemon/dist/core/task-artifacts.js +10 -10
- package/daemon/dist/core/task-artifacts.js.map +1 -1
- package/daemon/dist/core/thread-context.js +1 -1
- package/daemon/dist/core/thread-context.js.map +1 -1
- package/daemon/dist/core/types.d.ts +28 -28
- package/daemon/dist/core/types.d.ts.map +1 -1
- package/daemon/dist/core/utils.js +1 -1
- package/daemon/dist/core/utils.js.map +1 -1
- package/daemon/dist/http/router.d.ts +2 -2
- package/daemon/dist/http/router.d.ts.map +1 -1
- package/daemon/dist/http/router.js +5 -5
- package/daemon/dist/http/router.js.map +1 -1
- package/daemon/dist/http/routes/agents.js +3 -3
- package/daemon/dist/http/routes/agents.js.map +1 -1
- package/daemon/dist/http/routes/automations.d.ts +2 -2
- package/daemon/dist/http/routes/automations.d.ts.map +1 -1
- package/daemon/dist/http/routes/automations.js +23 -23
- package/daemon/dist/http/routes/automations.js.map +1 -1
- package/daemon/dist/http/routes/chat-input.d.ts +1 -1
- package/daemon/dist/http/routes/chat-input.d.ts.map +1 -1
- package/daemon/dist/http/routes/chat-input.js +2 -2
- package/daemon/dist/http/routes/chat-input.js.map +1 -1
- package/daemon/dist/http/routes/plugins.d.ts.map +1 -1
- package/daemon/dist/http/routes/plugins.js +6 -74
- package/daemon/dist/http/routes/plugins.js.map +1 -1
- package/daemon/dist/http/routes/rpc.d.ts +3 -3
- package/daemon/dist/http/routes/rpc.d.ts.map +1 -1
- package/daemon/dist/http/routes/rpc.js +91 -92
- package/daemon/dist/http/routes/rpc.js.map +1 -1
- package/daemon/dist/http/routes/system.d.ts +7 -7
- package/daemon/dist/http/routes/system.d.ts.map +1 -1
- package/daemon/dist/http/routes/system.js +222 -108
- package/daemon/dist/http/routes/system.js.map +1 -1
- package/daemon/dist/http/routes/threads.d.ts +11 -0
- package/daemon/dist/http/routes/threads.d.ts.map +1 -0
- package/daemon/dist/http/routes/{sessions.js → threads.js} +157 -157
- package/daemon/dist/http/routes/threads.js.map +1 -0
- package/daemon/dist/http/stream.d.ts +2 -2
- package/daemon/dist/http/stream.d.ts.map +1 -1
- package/daemon/dist/http/stream.js +3 -3
- package/daemon/dist/http/stream.js.map +1 -1
- package/daemon/dist/http/thread-title.d.ts +1 -1
- package/daemon/dist/http/thread-title.d.ts.map +1 -1
- package/daemon/dist/http/thread-title.js +6 -6
- package/daemon/dist/http/thread-title.js.map +1 -1
- package/daemon/dist/http/websocket.d.ts +2 -2
- package/daemon/dist/http/websocket.d.ts.map +1 -1
- package/daemon/dist/http/websocket.js +11 -11
- package/daemon/dist/http/websocket.js.map +1 -1
- package/daemon/dist/index.d.ts +3 -3
- package/daemon/dist/index.d.ts.map +1 -1
- package/daemon/dist/index.js +64 -81
- package/daemon/dist/index.js.map +1 -1
- package/daemon/dist/mcp/aggregate-config.d.ts +1 -1
- package/daemon/dist/mcp/index.js +1 -1
- package/daemon/dist/mcp/index.js.map +1 -1
- package/daemon/dist/mcp/tools.d.ts +1 -1
- package/daemon/dist/mcp/tools.js +1 -1
- package/daemon/dist/plugins/hub.d.ts +2 -8
- package/daemon/dist/plugins/hub.d.ts.map +1 -1
- package/daemon/dist/plugins/hub.js +63 -214
- package/daemon/dist/plugins/hub.js.map +1 -1
- package/daemon/dist/plugins/types.d.ts +10 -0
- package/daemon/dist/plugins/types.d.ts.map +1 -1
- package/daemon/dist/sub-agent.d.ts +3 -3
- package/daemon/dist/sub-agent.d.ts.map +1 -1
- package/daemon/dist/sub-agent.js +8 -8
- package/daemon/dist/sub-agent.js.map +1 -1
- package/daemon/dist/sync/supabase-sync.js +18 -18
- package/daemon/dist/sync/supabase-sync.js.map +1 -1
- package/daemon/dist/task-executor.js +1 -1
- package/daemon/dist/task-executor.js.map +1 -1
- package/daemon/dist/tools/shell.js +1 -1
- package/daemon/dist/tools/shell.js.map +1 -1
- package/daemon/dist/tools/types.d.ts +1 -1
- package/daemon/dist/tools/types.d.ts.map +1 -1
- package/daemon/package.json +1 -1
- package/dist/computer.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/daemon/dist/acp-client.d.ts +0 -42
- package/daemon/dist/acp-client.d.ts.map +0 -1
- package/daemon/dist/acp-client.js +0 -149
- package/daemon/dist/acp-client.js.map +0 -1
- package/daemon/dist/acp-types.d.ts +0 -98
- package/daemon/dist/acp-types.d.ts.map +0 -1
- package/daemon/dist/acp-types.js +0 -2
- package/daemon/dist/acp-types.js.map +0 -1
- package/daemon/dist/agent-sdk/session-events.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/session-events.js.map +0 -1
- package/daemon/dist/agent-sdk/session-manager.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/session-manager.js.map +0 -1
- package/daemon/dist/channels/acp.d.ts +0 -23
- package/daemon/dist/channels/acp.d.ts.map +0 -1
- package/daemon/dist/channels/acp.js +0 -915
- package/daemon/dist/channels/acp.js.map +0 -1
- package/daemon/dist/http/routes/sessions.d.ts +0 -11
- package/daemon/dist/http/routes/sessions.d.ts.map +0 -1
- package/daemon/dist/http/routes/sessions.js.map +0 -1
|
@@ -1,915 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { randomUUID } from 'crypto';
|
|
4
|
-
import { ACP_ENABLED, ACP_RELAY_URL } from '../core/config.js';
|
|
5
|
-
import { ensureSession } from '../core/store.js';
|
|
6
|
-
import { logger } from '../core/logger.js';
|
|
7
|
-
import { validateOutboundEndpoint } from '../core/security.js';
|
|
8
|
-
import { submitTask } from '../task-executor.js';
|
|
9
|
-
import { ACPClient } from '../acp-client.js';
|
|
10
|
-
import { registerChannel } from './registry.js';
|
|
11
|
-
// NOTE: This ACP channel is the external transport surface for ACP clients that
|
|
12
|
-
// talk to Supen as a daemon/channel endpoint. It is intentionally separate from
|
|
13
|
-
// the Codex app-server agent transport. We should not collapse coding-agent
|
|
14
|
-
// transport into a "channel" abstraction here.
|
|
15
|
-
const ACP_JID_PREFIX = 'acp:';
|
|
16
|
-
const MAX_SESSION_MESSAGES = 200;
|
|
17
|
-
let runtimeOpts = null;
|
|
18
|
-
let defaultClient = null;
|
|
19
|
-
const sessionsById = new Map();
|
|
20
|
-
const sessionIdByChatJid = new Map();
|
|
21
|
-
function nowIso() {
|
|
22
|
-
return new Date().toISOString();
|
|
23
|
-
}
|
|
24
|
-
function buildThreadId(agentId, sessionId) {
|
|
25
|
-
return `${encodeURIComponent(agentId)}:${encodeURIComponent(sessionId)}`;
|
|
26
|
-
}
|
|
27
|
-
function parseThreadId(threadId) {
|
|
28
|
-
const parts = threadId.split(':');
|
|
29
|
-
if (parts.length < 2)
|
|
30
|
-
return null;
|
|
31
|
-
try {
|
|
32
|
-
return {
|
|
33
|
-
agent_id: decodeURIComponent(parts[0]),
|
|
34
|
-
session_id: decodeURIComponent(parts[1]),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
export function buildAcpChatJid(agentId, sessionId) {
|
|
42
|
-
return `${ACP_JID_PREFIX}${buildThreadId(agentId, sessionId)}`;
|
|
43
|
-
}
|
|
44
|
-
function readJsonBody(req) {
|
|
45
|
-
return new Promise((resolve, reject) => {
|
|
46
|
-
let body = '';
|
|
47
|
-
req.setEncoding('utf-8');
|
|
48
|
-
req.on('data', (chunk) => {
|
|
49
|
-
body += chunk;
|
|
50
|
-
});
|
|
51
|
-
req.on('end', () => {
|
|
52
|
-
if (!body.trim()) {
|
|
53
|
-
resolve({});
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
resolve(JSON.parse(body));
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
reject(new Error('Invalid JSON body'));
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
req.on('error', reject);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
function writeJson(res, statusCode, payload) {
|
|
67
|
-
res.writeHead(statusCode, {
|
|
68
|
-
'Content-Type': 'application/json',
|
|
69
|
-
});
|
|
70
|
-
res.end(JSON.stringify(payload));
|
|
71
|
-
}
|
|
72
|
-
function writeProtocolError(res, statusCode, code, message, details) {
|
|
73
|
-
writeJson(res, statusCode, {
|
|
74
|
-
error: {
|
|
75
|
-
code,
|
|
76
|
-
message,
|
|
77
|
-
...(details ? { details } : {}),
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
function wantsSse(req, url) {
|
|
82
|
-
if (url.searchParams.get('stream') === '1')
|
|
83
|
-
return true;
|
|
84
|
-
const accept = req.headers.accept;
|
|
85
|
-
return typeof accept === 'string' && accept.includes('text/event-stream');
|
|
86
|
-
}
|
|
87
|
-
function inferMimeType(filePath) {
|
|
88
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
89
|
-
if (ext === '.md')
|
|
90
|
-
return 'text/markdown';
|
|
91
|
-
if (ext === '.base')
|
|
92
|
-
return 'text/yaml';
|
|
93
|
-
if (ext === '.txt' || ext === '.log' || ext === '.jsonl')
|
|
94
|
-
return 'text/plain';
|
|
95
|
-
if (ext === '.json')
|
|
96
|
-
return 'application/json';
|
|
97
|
-
if (ext === '.png')
|
|
98
|
-
return 'image/png';
|
|
99
|
-
if (ext === '.jpg' || ext === '.jpeg')
|
|
100
|
-
return 'image/jpeg';
|
|
101
|
-
if (ext === '.webp')
|
|
102
|
-
return 'image/webp';
|
|
103
|
-
if (ext === '.pdf')
|
|
104
|
-
return 'application/pdf';
|
|
105
|
-
return undefined;
|
|
106
|
-
}
|
|
107
|
-
function normalizeContentPart(part) {
|
|
108
|
-
if (!part)
|
|
109
|
-
return null;
|
|
110
|
-
if (typeof part === 'string') {
|
|
111
|
-
return { type: 'text', text: part };
|
|
112
|
-
}
|
|
113
|
-
if (typeof part !== 'object')
|
|
114
|
-
return null;
|
|
115
|
-
const value = part;
|
|
116
|
-
const type = value.type;
|
|
117
|
-
if ((type === 'text' || type === 'markdown') &&
|
|
118
|
-
typeof value.text === 'string') {
|
|
119
|
-
return { type, text: value.text };
|
|
120
|
-
}
|
|
121
|
-
if (type === 'artifact') {
|
|
122
|
-
return {
|
|
123
|
-
type: 'artifact',
|
|
124
|
-
name: typeof value.name === 'string' ? value.name : undefined,
|
|
125
|
-
mime_type: typeof value.mime_type === 'string' ? value.mime_type : undefined,
|
|
126
|
-
uri: typeof value.uri === 'string' ? value.uri : undefined,
|
|
127
|
-
path: typeof value.path === 'string' ? value.path : undefined,
|
|
128
|
-
description: typeof value.description === 'string' ? value.description : undefined,
|
|
129
|
-
data: typeof value.data === 'string' ? value.data : undefined,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
function normalizeContent(content) {
|
|
135
|
-
if (typeof content === 'string') {
|
|
136
|
-
return content.trim() ? [{ type: 'text', text: content }] : [];
|
|
137
|
-
}
|
|
138
|
-
if (Array.isArray(content)) {
|
|
139
|
-
return content
|
|
140
|
-
.map((part) => normalizeContentPart(part))
|
|
141
|
-
.filter((part) => !!part);
|
|
142
|
-
}
|
|
143
|
-
if (content && typeof content === 'object') {
|
|
144
|
-
const single = normalizeContentPart(content);
|
|
145
|
-
return single ? [single] : [];
|
|
146
|
-
}
|
|
147
|
-
return [];
|
|
148
|
-
}
|
|
149
|
-
function normalizeToolCall(toolCall) {
|
|
150
|
-
if (!toolCall || typeof toolCall !== 'object')
|
|
151
|
-
return null;
|
|
152
|
-
const value = toolCall;
|
|
153
|
-
const name = typeof value.name === 'string'
|
|
154
|
-
? value.name
|
|
155
|
-
: typeof value.tool_name === 'string'
|
|
156
|
-
? value.tool_name
|
|
157
|
-
: null;
|
|
158
|
-
if (!name)
|
|
159
|
-
return null;
|
|
160
|
-
return {
|
|
161
|
-
id: typeof value.id === 'string' && value.id
|
|
162
|
-
? value.id
|
|
163
|
-
: typeof value.tool_call_id === 'string' && value.tool_call_id
|
|
164
|
-
? value.tool_call_id
|
|
165
|
-
: randomUUID(),
|
|
166
|
-
name,
|
|
167
|
-
arguments: value.arguments && typeof value.arguments === 'object'
|
|
168
|
-
? value.arguments
|
|
169
|
-
: value.input && typeof value.input === 'object'
|
|
170
|
-
? value.input
|
|
171
|
-
: undefined,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
function normalizeToolResult(toolResult) {
|
|
175
|
-
if (!toolResult || typeof toolResult !== 'object')
|
|
176
|
-
return null;
|
|
177
|
-
const value = toolResult;
|
|
178
|
-
const toolCallId = typeof value.tool_call_id === 'string'
|
|
179
|
-
? value.tool_call_id
|
|
180
|
-
: typeof value.id === 'string'
|
|
181
|
-
? value.id
|
|
182
|
-
: null;
|
|
183
|
-
if (!toolCallId)
|
|
184
|
-
return null;
|
|
185
|
-
return {
|
|
186
|
-
tool_call_id: toolCallId,
|
|
187
|
-
name: typeof value.name === 'string' ? value.name : undefined,
|
|
188
|
-
result: value.result ?? value.output ?? value.content,
|
|
189
|
-
is_error: typeof value.is_error === 'boolean'
|
|
190
|
-
? value.is_error
|
|
191
|
-
: typeof value.error === 'boolean'
|
|
192
|
-
? value.error
|
|
193
|
-
: false,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
export function normalizeAcpEnvelope(input, fallbackRole = 'user') {
|
|
197
|
-
const raw = input && typeof input === 'object' && 'message' in input
|
|
198
|
-
? input.message
|
|
199
|
-
: input;
|
|
200
|
-
const value = raw && typeof raw === 'object' ? raw : {};
|
|
201
|
-
const role = value.role === 'system' ||
|
|
202
|
-
value.role === 'user' ||
|
|
203
|
-
value.role === 'assistant' ||
|
|
204
|
-
value.role === 'tool'
|
|
205
|
-
? value.role
|
|
206
|
-
: fallbackRole;
|
|
207
|
-
const content = normalizeContent(value.content ?? value.parts ?? value.text ?? value.prompt ?? '');
|
|
208
|
-
const toolCalls = Array.isArray(value.tool_calls)
|
|
209
|
-
? value.tool_calls
|
|
210
|
-
.map((toolCall) => normalizeToolCall(toolCall))
|
|
211
|
-
.filter((toolCall) => !!toolCall)
|
|
212
|
-
: [];
|
|
213
|
-
const toolResults = Array.isArray(value.tool_results)
|
|
214
|
-
? value.tool_results
|
|
215
|
-
.map((toolResult) => normalizeToolResult(toolResult))
|
|
216
|
-
.filter((toolResult) => !!toolResult)
|
|
217
|
-
: [];
|
|
218
|
-
return {
|
|
219
|
-
id: typeof value.id === 'string' && value.id ? value.id : randomUUID(),
|
|
220
|
-
role,
|
|
221
|
-
content,
|
|
222
|
-
...(toolCalls.length ? { tool_calls: toolCalls } : {}),
|
|
223
|
-
...(toolResults.length ? { tool_results: toolResults } : {}),
|
|
224
|
-
...(value.metadata && typeof value.metadata === 'object'
|
|
225
|
-
? { metadata: value.metadata }
|
|
226
|
-
: {}),
|
|
227
|
-
created_at: typeof value.created_at === 'string' && value.created_at
|
|
228
|
-
? value.created_at
|
|
229
|
-
: nowIso(),
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
function renderContentForPrompt(content) {
|
|
233
|
-
return content
|
|
234
|
-
.map((part) => {
|
|
235
|
-
if (part.type === 'artifact') {
|
|
236
|
-
return [
|
|
237
|
-
'[Artifact]',
|
|
238
|
-
part.name ? `name: ${part.name}` : null,
|
|
239
|
-
part.path ? `path: ${part.path}` : null,
|
|
240
|
-
part.uri ? `uri: ${part.uri}` : null,
|
|
241
|
-
part.description ? `description: ${part.description}` : null,
|
|
242
|
-
]
|
|
243
|
-
.filter(Boolean)
|
|
244
|
-
.join('\n');
|
|
245
|
-
}
|
|
246
|
-
return part.text;
|
|
247
|
-
})
|
|
248
|
-
.filter(Boolean)
|
|
249
|
-
.join('\n\n');
|
|
250
|
-
}
|
|
251
|
-
function buildPromptFromEnvelope(envelope, mode = 'message') {
|
|
252
|
-
const parts = [];
|
|
253
|
-
if (mode === 'tools' && envelope.tool_calls?.length) {
|
|
254
|
-
parts.push('Execute the requested tool calls and return the results clearly. Include any errors inline.');
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
parts.push(`ACP ${envelope.role} message:`);
|
|
258
|
-
}
|
|
259
|
-
const renderedContent = renderContentForPrompt(envelope.content);
|
|
260
|
-
if (renderedContent)
|
|
261
|
-
parts.push(renderedContent);
|
|
262
|
-
if (envelope.tool_calls?.length) {
|
|
263
|
-
parts.push(`[ACP tool calls]\n${JSON.stringify(envelope.tool_calls, null, 2)}`);
|
|
264
|
-
}
|
|
265
|
-
if (envelope.tool_results?.length) {
|
|
266
|
-
parts.push(`[ACP tool results]\n${JSON.stringify(envelope.tool_results, null, 2)}`);
|
|
267
|
-
}
|
|
268
|
-
return parts.join('\n\n').trim();
|
|
269
|
-
}
|
|
270
|
-
function isExecutableEnvelope(envelope) {
|
|
271
|
-
if (envelope.tool_calls?.length)
|
|
272
|
-
return true;
|
|
273
|
-
return envelope.content.some((part) => part.type === 'artifact' ? true : !!part.text.trim());
|
|
274
|
-
}
|
|
275
|
-
function serializeSession(session) {
|
|
276
|
-
return {
|
|
277
|
-
id: session.id,
|
|
278
|
-
thread_id: session.thread_id,
|
|
279
|
-
agent_id: session.agent_id,
|
|
280
|
-
session_id: session.session_id,
|
|
281
|
-
chat_jid: session.chat_jid,
|
|
282
|
-
created_at: session.created_at,
|
|
283
|
-
updated_at: session.updated_at,
|
|
284
|
-
stream_url: session.stream_url,
|
|
285
|
-
message_url: session.message_url,
|
|
286
|
-
tools_url: session.tools_url,
|
|
287
|
-
last_task_id: session.last_task_id || null,
|
|
288
|
-
metadata: session.metadata || null,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
function writeSseEvent(res, event, eventName) {
|
|
292
|
-
if (eventName) {
|
|
293
|
-
res.write(`event: ${eventName}\n`);
|
|
294
|
-
}
|
|
295
|
-
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
296
|
-
}
|
|
297
|
-
function broadcastSessionEvent(session, event, eventName) {
|
|
298
|
-
for (const client of [...session.clients]) {
|
|
299
|
-
try {
|
|
300
|
-
writeSseEvent(client, event, eventName);
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
session.clients.delete(client);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function appendMessage(session, envelope) {
|
|
308
|
-
session.messages.push(envelope);
|
|
309
|
-
if (session.messages.length > MAX_SESSION_MESSAGES) {
|
|
310
|
-
session.messages.splice(0, session.messages.length - MAX_SESSION_MESSAGES);
|
|
311
|
-
}
|
|
312
|
-
session.updated_at = nowIso();
|
|
313
|
-
}
|
|
314
|
-
function listAgentManifests() {
|
|
315
|
-
const opts = runtimeOpts;
|
|
316
|
-
const uniqueAgents = new Map();
|
|
317
|
-
for (const project of Object.values(opts?.registeredProjects() || {})) {
|
|
318
|
-
const agentId = project.agent_id || project.folder;
|
|
319
|
-
uniqueAgents.set(agentId, agentId);
|
|
320
|
-
}
|
|
321
|
-
if (uniqueAgents.size === 0) {
|
|
322
|
-
uniqueAgents.set('default', 'default');
|
|
323
|
-
}
|
|
324
|
-
return [...uniqueAgents.keys()].map((agentId) => ({
|
|
325
|
-
id: buildThreadId(agentId, agentId),
|
|
326
|
-
protocol: 'acp',
|
|
327
|
-
name: `Supen ${agentId}`,
|
|
328
|
-
description: 'Supen ACP-compatible agent endpoint',
|
|
329
|
-
version: '1.0.0',
|
|
330
|
-
agent_id: agentId,
|
|
331
|
-
endpoints: {
|
|
332
|
-
agents: '/acp/agents',
|
|
333
|
-
sessions: '/acp/sessions',
|
|
334
|
-
},
|
|
335
|
-
capabilities: {
|
|
336
|
-
streaming: true,
|
|
337
|
-
inbound: true,
|
|
338
|
-
outbound: true,
|
|
339
|
-
tool_calls: true,
|
|
340
|
-
tool_results: true,
|
|
341
|
-
content_types: ['text', 'markdown', 'artifact'],
|
|
342
|
-
},
|
|
343
|
-
}));
|
|
344
|
-
}
|
|
345
|
-
function ensureRegisteredProject(agentId, chatJid, timestamp) {
|
|
346
|
-
const opts = runtimeOpts;
|
|
347
|
-
if (!opts)
|
|
348
|
-
return;
|
|
349
|
-
const projects = opts.registeredProjects();
|
|
350
|
-
if (!projects[chatJid] && opts.onGroupRegistered) {
|
|
351
|
-
const project = {
|
|
352
|
-
name: agentId,
|
|
353
|
-
folder: agentId,
|
|
354
|
-
agent_id: agentId,
|
|
355
|
-
trigger: '',
|
|
356
|
-
added_at: timestamp,
|
|
357
|
-
requiresTrigger: false,
|
|
358
|
-
isMain: false,
|
|
359
|
-
};
|
|
360
|
-
opts.onGroupRegistered(chatJid, project);
|
|
361
|
-
}
|
|
362
|
-
opts.onChatMetadata(chatJid, timestamp, undefined, 'acp', false);
|
|
363
|
-
}
|
|
364
|
-
function ensureRelaySync(session) {
|
|
365
|
-
if (!defaultClient || session.relay_sync_started)
|
|
366
|
-
return;
|
|
367
|
-
session.relay_sync_started = true;
|
|
368
|
-
void defaultClient
|
|
369
|
-
.createSession({
|
|
370
|
-
agent_id: session.agent_id,
|
|
371
|
-
session_id: session.session_id,
|
|
372
|
-
thread_id: session.thread_id,
|
|
373
|
-
metadata: {
|
|
374
|
-
origin: 'supen',
|
|
375
|
-
local_session_id: session.id,
|
|
376
|
-
},
|
|
377
|
-
})
|
|
378
|
-
.then(({ session: remoteSession }) => {
|
|
379
|
-
session.relay_session_id = remoteSession.id;
|
|
380
|
-
session.relay_abort = new AbortController();
|
|
381
|
-
return defaultClient.streamSession(remoteSession.id, {
|
|
382
|
-
onEvent: async (event) => {
|
|
383
|
-
await handleRelayEvent(session, event);
|
|
384
|
-
},
|
|
385
|
-
}, session.relay_abort.signal);
|
|
386
|
-
})
|
|
387
|
-
.catch((err) => {
|
|
388
|
-
logger.warn({ err, session_id: session.id, relay_url: ACP_RELAY_URL }, 'ACP relay sync unavailable');
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
async function syncEnvelopeToRelay(session, envelope) {
|
|
392
|
-
if (!defaultClient || !session.relay_session_id)
|
|
393
|
-
return;
|
|
394
|
-
try {
|
|
395
|
-
await defaultClient.sendMessage(session.relay_session_id, {
|
|
396
|
-
message: {
|
|
397
|
-
...envelope,
|
|
398
|
-
metadata: {
|
|
399
|
-
...(envelope.metadata || {}),
|
|
400
|
-
origin: 'supen',
|
|
401
|
-
local_session_id: session.id,
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
catch (err) {
|
|
407
|
-
logger.warn({ err, session_id: session.id }, 'Failed to sync ACP message');
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
async function syncToolCallsToRelay(session, toolCalls) {
|
|
411
|
-
if (!defaultClient || !session.relay_session_id || toolCalls.length === 0)
|
|
412
|
-
return;
|
|
413
|
-
try {
|
|
414
|
-
await defaultClient.sendToolCalls(session.relay_session_id, {
|
|
415
|
-
tool_calls: toolCalls,
|
|
416
|
-
metadata: {
|
|
417
|
-
origin: 'supen',
|
|
418
|
-
local_session_id: session.id,
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
catch (err) {
|
|
423
|
-
logger.warn({ err, session_id: session.id }, 'Failed to sync ACP tool calls');
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
async function handleRelayEvent(session, event) {
|
|
427
|
-
const origin = event.message?.metadata && typeof event.message.metadata.origin === 'string'
|
|
428
|
-
? event.message.metadata.origin
|
|
429
|
-
: undefined;
|
|
430
|
-
if (origin === 'supen')
|
|
431
|
-
return;
|
|
432
|
-
if (event.type === 'message' && event.message) {
|
|
433
|
-
appendMessage(session, event.message);
|
|
434
|
-
broadcastSessionEvent(session, {
|
|
435
|
-
type: 'message',
|
|
436
|
-
session_id: session.id,
|
|
437
|
-
task_id: session.last_task_id,
|
|
438
|
-
message: event.message,
|
|
439
|
-
});
|
|
440
|
-
if (event.message.role !== 'assistant' ||
|
|
441
|
-
event.message.tool_calls?.length) {
|
|
442
|
-
await submitEnvelope(session, event.message, 'acp-relay', 'message', false);
|
|
443
|
-
}
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (event.type === 'tool_call' && event.tool_calls?.length) {
|
|
447
|
-
const envelope = {
|
|
448
|
-
id: randomUUID(),
|
|
449
|
-
role: 'user',
|
|
450
|
-
content: [],
|
|
451
|
-
tool_calls: event.tool_calls,
|
|
452
|
-
created_at: nowIso(),
|
|
453
|
-
metadata: { origin: 'acp-relay' },
|
|
454
|
-
};
|
|
455
|
-
await submitEnvelope(session, envelope, 'acp-relay', 'tools', false);
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
if (event.type === 'tool_result' && event.tool_results?.length) {
|
|
459
|
-
const envelope = {
|
|
460
|
-
id: randomUUID(),
|
|
461
|
-
role: 'tool',
|
|
462
|
-
content: [],
|
|
463
|
-
tool_results: event.tool_results,
|
|
464
|
-
created_at: nowIso(),
|
|
465
|
-
metadata: { origin: 'acp-relay' },
|
|
466
|
-
};
|
|
467
|
-
appendMessage(session, envelope);
|
|
468
|
-
broadcastSessionEvent(session, {
|
|
469
|
-
type: 'tool_result',
|
|
470
|
-
session_id: session.id,
|
|
471
|
-
task_id: session.last_task_id,
|
|
472
|
-
tool_results: event.tool_results,
|
|
473
|
-
message: envelope,
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
function ensureAcpSessionState(input) {
|
|
478
|
-
const parsedThread = input.thread_id ? parseThreadId(input.thread_id) : null;
|
|
479
|
-
const agentId = parsedThread?.agent_id || input.agent_id;
|
|
480
|
-
const sessionId = parsedThread?.session_id ||
|
|
481
|
-
input.session_id ||
|
|
482
|
-
input.thread_id ||
|
|
483
|
-
randomUUID();
|
|
484
|
-
const threadId = buildThreadId(agentId, sessionId);
|
|
485
|
-
const existing = sessionsById.get(threadId);
|
|
486
|
-
if (existing) {
|
|
487
|
-
if (input.metadata) {
|
|
488
|
-
existing.metadata = {
|
|
489
|
-
...(existing.metadata || {}),
|
|
490
|
-
...input.metadata,
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
existing.updated_at = nowIso();
|
|
494
|
-
return existing;
|
|
495
|
-
}
|
|
496
|
-
const chatJid = buildAcpChatJid(agentId, sessionId);
|
|
497
|
-
const timestamp = nowIso();
|
|
498
|
-
ensureRegisteredProject(agentId, chatJid, timestamp);
|
|
499
|
-
ensureSession({
|
|
500
|
-
agent_id: agentId,
|
|
501
|
-
session_id: sessionId,
|
|
502
|
-
channel: 'acp',
|
|
503
|
-
agent_name: agentId,
|
|
504
|
-
});
|
|
505
|
-
const session = {
|
|
506
|
-
id: threadId,
|
|
507
|
-
thread_id: threadId,
|
|
508
|
-
agent_id: agentId,
|
|
509
|
-
session_id: sessionId,
|
|
510
|
-
chat_jid: chatJid,
|
|
511
|
-
created_at: timestamp,
|
|
512
|
-
updated_at: timestamp,
|
|
513
|
-
stream_url: `/acp/sessions/${encodeURIComponent(threadId)}?stream=1`,
|
|
514
|
-
message_url: `/acp/sessions/${encodeURIComponent(threadId)}`,
|
|
515
|
-
tools_url: `/acp/sessions/${encodeURIComponent(threadId)}/tools`,
|
|
516
|
-
metadata: input.metadata,
|
|
517
|
-
messages: [],
|
|
518
|
-
clients: new Set(),
|
|
519
|
-
};
|
|
520
|
-
sessionsById.set(threadId, session);
|
|
521
|
-
sessionIdByChatJid.set(chatJid, threadId);
|
|
522
|
-
ensureRelaySync(session);
|
|
523
|
-
return session;
|
|
524
|
-
}
|
|
525
|
-
function getSessionByRouteId(id) {
|
|
526
|
-
const decoded = decodeURIComponent(id);
|
|
527
|
-
const existing = sessionsById.get(decoded);
|
|
528
|
-
if (existing)
|
|
529
|
-
return existing;
|
|
530
|
-
const parsed = parseThreadId(decoded);
|
|
531
|
-
if (!parsed)
|
|
532
|
-
return null;
|
|
533
|
-
return ensureAcpSessionState(parsed);
|
|
534
|
-
}
|
|
535
|
-
async function submitEnvelope(session, envelope, actorId, mode = 'message', syncToHub = true) {
|
|
536
|
-
appendMessage(session, envelope);
|
|
537
|
-
broadcastSessionEvent(session, {
|
|
538
|
-
type: 'message',
|
|
539
|
-
session_id: session.id,
|
|
540
|
-
task_id: session.last_task_id,
|
|
541
|
-
message: envelope,
|
|
542
|
-
});
|
|
543
|
-
if (syncToHub) {
|
|
544
|
-
await syncEnvelopeToRelay(session, envelope);
|
|
545
|
-
}
|
|
546
|
-
if (!isExecutableEnvelope(envelope)) {
|
|
547
|
-
return { accepted: false };
|
|
548
|
-
}
|
|
549
|
-
const task = submitTask({
|
|
550
|
-
agent_id: session.agent_id,
|
|
551
|
-
session_id: session.session_id,
|
|
552
|
-
prompt: buildPromptFromEnvelope(envelope, mode),
|
|
553
|
-
source: 'acp',
|
|
554
|
-
submitted_by: actorId,
|
|
555
|
-
submitter_type: 'api_key',
|
|
556
|
-
metadata: {
|
|
557
|
-
protocol: 'acp',
|
|
558
|
-
mode,
|
|
559
|
-
envelope,
|
|
560
|
-
},
|
|
561
|
-
});
|
|
562
|
-
session.last_task_id = task.id;
|
|
563
|
-
session.updated_at = nowIso();
|
|
564
|
-
broadcastSessionEvent(session, {
|
|
565
|
-
type: 'task',
|
|
566
|
-
session_id: session.id,
|
|
567
|
-
task_id: task.id,
|
|
568
|
-
data: {
|
|
569
|
-
phase: 'queued',
|
|
570
|
-
agent_id: session.agent_id,
|
|
571
|
-
session_id: session.session_id,
|
|
572
|
-
},
|
|
573
|
-
});
|
|
574
|
-
return {
|
|
575
|
-
accepted: true,
|
|
576
|
-
task_id: task.id,
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
export async function publishAcpTaskEvent(chatJid, event) {
|
|
580
|
-
const sessionId = sessionIdByChatJid.get(chatJid);
|
|
581
|
-
if (!sessionId)
|
|
582
|
-
return;
|
|
583
|
-
const session = sessionsById.get(sessionId);
|
|
584
|
-
if (!session)
|
|
585
|
-
return;
|
|
586
|
-
const phase = typeof event.phase === 'string' ? event.phase : 'activity';
|
|
587
|
-
const payload = {
|
|
588
|
-
type: 'task',
|
|
589
|
-
session_id: session.id,
|
|
590
|
-
task_id: typeof event.task_id === 'string' ? event.task_id : session.last_task_id,
|
|
591
|
-
data: event,
|
|
592
|
-
};
|
|
593
|
-
if (phase === 'message_delta') {
|
|
594
|
-
broadcastSessionEvent(session, {
|
|
595
|
-
type: 'message.delta',
|
|
596
|
-
session_id: session.id,
|
|
597
|
-
task_id: payload.task_id,
|
|
598
|
-
data: event,
|
|
599
|
-
});
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
if (phase === 'tool_call' && Array.isArray(event.tool_calls)) {
|
|
603
|
-
const toolCalls = event.tool_calls;
|
|
604
|
-
broadcastSessionEvent(session, {
|
|
605
|
-
type: 'tool_call',
|
|
606
|
-
session_id: session.id,
|
|
607
|
-
task_id: payload.task_id,
|
|
608
|
-
tool_calls: toolCalls,
|
|
609
|
-
data: event,
|
|
610
|
-
});
|
|
611
|
-
await syncToolCallsToRelay(session, toolCalls);
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
if (phase === 'tool_result' && Array.isArray(event.tool_results)) {
|
|
615
|
-
broadcastSessionEvent(session, {
|
|
616
|
-
type: 'tool_result',
|
|
617
|
-
session_id: session.id,
|
|
618
|
-
task_id: payload.task_id,
|
|
619
|
-
tool_results: event.tool_results,
|
|
620
|
-
data: event,
|
|
621
|
-
});
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
if (phase === 'failed' || phase === 'timeout' || phase === 'canceled') {
|
|
625
|
-
broadcastSessionEvent(session, {
|
|
626
|
-
type: 'error',
|
|
627
|
-
session_id: session.id,
|
|
628
|
-
task_id: payload.task_id,
|
|
629
|
-
error: {
|
|
630
|
-
code: event.error &&
|
|
631
|
-
typeof event.error === 'object' &&
|
|
632
|
-
typeof event.error.code === 'string'
|
|
633
|
-
? event.error.code
|
|
634
|
-
: phase,
|
|
635
|
-
message: event.error &&
|
|
636
|
-
typeof event.error === 'object' &&
|
|
637
|
-
typeof event.error.message === 'string'
|
|
638
|
-
? event.error.message
|
|
639
|
-
: `Task ${phase}`,
|
|
640
|
-
},
|
|
641
|
-
data: event,
|
|
642
|
-
});
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
broadcastSessionEvent(session, payload);
|
|
646
|
-
}
|
|
647
|
-
export async function maybeHandleAcpRequest(req, res, url) {
|
|
648
|
-
if (!url.pathname.startsWith('/acp'))
|
|
649
|
-
return false;
|
|
650
|
-
if (!ACP_ENABLED) {
|
|
651
|
-
writeProtocolError(res, 503, 'acp_disabled', 'ACP channel is disabled');
|
|
652
|
-
return true;
|
|
653
|
-
}
|
|
654
|
-
try {
|
|
655
|
-
if (url.pathname === '/acp/agents' && req.method === 'GET') {
|
|
656
|
-
writeJson(res, 200, {
|
|
657
|
-
protocol: 'acp',
|
|
658
|
-
agents: listAgentManifests(),
|
|
659
|
-
});
|
|
660
|
-
return true;
|
|
661
|
-
}
|
|
662
|
-
if (url.pathname === '/acp/sessions' && req.method === 'POST') {
|
|
663
|
-
const parsed = await readJsonBody(req);
|
|
664
|
-
const agentId = typeof parsed.agent_id === 'string' && parsed.agent_id.trim()
|
|
665
|
-
? parsed.agent_id.trim()
|
|
666
|
-
: '';
|
|
667
|
-
if (!agentId) {
|
|
668
|
-
writeProtocolError(res, 400, 'agent_id_required', 'agent_id is required');
|
|
669
|
-
return true;
|
|
670
|
-
}
|
|
671
|
-
const session = ensureAcpSessionState({
|
|
672
|
-
agent_id: agentId,
|
|
673
|
-
session_id: typeof parsed.session_id === 'string' ? parsed.session_id : undefined,
|
|
674
|
-
thread_id: typeof parsed.thread_id === 'string' ? parsed.thread_id : undefined,
|
|
675
|
-
metadata: parsed.metadata && typeof parsed.metadata === 'object'
|
|
676
|
-
? parsed.metadata
|
|
677
|
-
: undefined,
|
|
678
|
-
});
|
|
679
|
-
let taskId;
|
|
680
|
-
if (parsed.message) {
|
|
681
|
-
const envelope = normalizeAcpEnvelope(parsed.message);
|
|
682
|
-
const result = await submitEnvelope(session, envelope, 'acp');
|
|
683
|
-
taskId = result.task_id;
|
|
684
|
-
}
|
|
685
|
-
writeJson(res, taskId ? 202 : 201, {
|
|
686
|
-
session: serializeSession(session),
|
|
687
|
-
task_id: taskId || null,
|
|
688
|
-
});
|
|
689
|
-
return true;
|
|
690
|
-
}
|
|
691
|
-
const sessionToolsMatch = url.pathname.match(/^\/acp\/sessions\/([^/]+)\/tools$/);
|
|
692
|
-
if (sessionToolsMatch && req.method === 'POST') {
|
|
693
|
-
const session = getSessionByRouteId(sessionToolsMatch[1]);
|
|
694
|
-
if (!session) {
|
|
695
|
-
writeProtocolError(res, 404, 'session_not_found', 'ACP session not found');
|
|
696
|
-
return true;
|
|
697
|
-
}
|
|
698
|
-
const parsed = await readJsonBody(req);
|
|
699
|
-
const toolCalls = Array.isArray(parsed.tool_calls)
|
|
700
|
-
? parsed.tool_calls
|
|
701
|
-
.map((toolCall) => normalizeToolCall(toolCall))
|
|
702
|
-
.filter((toolCall) => !!toolCall)
|
|
703
|
-
: [];
|
|
704
|
-
if (toolCalls.length === 0) {
|
|
705
|
-
writeProtocolError(res, 400, 'tool_calls_required', 'tool_calls must contain at least one tool call');
|
|
706
|
-
return true;
|
|
707
|
-
}
|
|
708
|
-
const envelope = {
|
|
709
|
-
id: randomUUID(),
|
|
710
|
-
role: 'user',
|
|
711
|
-
content: normalizeContent(parsed.content ?? parsed.prompt ?? ''),
|
|
712
|
-
tool_calls: toolCalls,
|
|
713
|
-
created_at: nowIso(),
|
|
714
|
-
...(parsed.metadata && typeof parsed.metadata === 'object'
|
|
715
|
-
? { metadata: parsed.metadata }
|
|
716
|
-
: {}),
|
|
717
|
-
};
|
|
718
|
-
const result = await submitEnvelope(session, envelope, 'acp', 'tools');
|
|
719
|
-
writeJson(res, 202, {
|
|
720
|
-
session: serializeSession(session),
|
|
721
|
-
task_id: result.task_id || null,
|
|
722
|
-
message: envelope,
|
|
723
|
-
});
|
|
724
|
-
return true;
|
|
725
|
-
}
|
|
726
|
-
const sessionMatch = url.pathname.match(/^\/acp\/sessions\/([^/]+)$/);
|
|
727
|
-
if (sessionMatch) {
|
|
728
|
-
const session = getSessionByRouteId(sessionMatch[1]);
|
|
729
|
-
if (!session) {
|
|
730
|
-
writeProtocolError(res, 404, 'session_not_found', 'ACP session not found');
|
|
731
|
-
return true;
|
|
732
|
-
}
|
|
733
|
-
if (req.method === 'GET') {
|
|
734
|
-
if (wantsSse(req, url)) {
|
|
735
|
-
res.writeHead(200, {
|
|
736
|
-
'Content-Type': 'text/event-stream',
|
|
737
|
-
'Cache-Control': 'no-cache',
|
|
738
|
-
Connection: 'keep-alive',
|
|
739
|
-
'X-Accel-Buffering': 'no',
|
|
740
|
-
});
|
|
741
|
-
session.clients.add(res);
|
|
742
|
-
writeSseEvent(res, {
|
|
743
|
-
type: 'session',
|
|
744
|
-
session_id: session.id,
|
|
745
|
-
task_id: session.last_task_id,
|
|
746
|
-
data: {
|
|
747
|
-
session: serializeSession(session),
|
|
748
|
-
messages: session.messages.slice(-20),
|
|
749
|
-
},
|
|
750
|
-
}, 'session');
|
|
751
|
-
const heartbeat = setInterval(() => {
|
|
752
|
-
try {
|
|
753
|
-
res.write(': ping\n\n');
|
|
754
|
-
}
|
|
755
|
-
catch {
|
|
756
|
-
clearInterval(heartbeat);
|
|
757
|
-
}
|
|
758
|
-
}, 20_000);
|
|
759
|
-
req.on('close', () => {
|
|
760
|
-
clearInterval(heartbeat);
|
|
761
|
-
session.clients.delete(res);
|
|
762
|
-
});
|
|
763
|
-
return true;
|
|
764
|
-
}
|
|
765
|
-
writeJson(res, 200, {
|
|
766
|
-
session: serializeSession(session),
|
|
767
|
-
messages: session.messages,
|
|
768
|
-
});
|
|
769
|
-
return true;
|
|
770
|
-
}
|
|
771
|
-
if (req.method === 'POST') {
|
|
772
|
-
const parsed = await readJsonBody(req);
|
|
773
|
-
const envelope = normalizeAcpEnvelope(parsed, 'user');
|
|
774
|
-
const result = await submitEnvelope(session, envelope, 'acp');
|
|
775
|
-
writeJson(res, result.task_id ? 202 : 200, {
|
|
776
|
-
session: serializeSession(session),
|
|
777
|
-
task_id: result.task_id || null,
|
|
778
|
-
message: envelope,
|
|
779
|
-
});
|
|
780
|
-
return true;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
catch (err) {
|
|
785
|
-
const statusCode = err && typeof err === 'object' && 'statusCode' in err
|
|
786
|
-
? Number(err.statusCode) || 500
|
|
787
|
-
: 500;
|
|
788
|
-
const code = err && typeof err === 'object' && 'code' in err
|
|
789
|
-
? String(err.code)
|
|
790
|
-
: 'internal_error';
|
|
791
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
792
|
-
logger.error({ err, pathname: url.pathname }, 'ACP request failed');
|
|
793
|
-
writeProtocolError(res, statusCode, code, message);
|
|
794
|
-
return true;
|
|
795
|
-
}
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
export class AcpChannel {
|
|
799
|
-
name = 'acp';
|
|
800
|
-
opts;
|
|
801
|
-
connected = false;
|
|
802
|
-
constructor(opts) {
|
|
803
|
-
this.opts = opts;
|
|
804
|
-
runtimeOpts = opts;
|
|
805
|
-
if (ACP_RELAY_URL) {
|
|
806
|
-
try {
|
|
807
|
-
const trustedEndpoint = validateOutboundEndpoint(ACP_RELAY_URL, {
|
|
808
|
-
allowedProtocols: ['http:', 'https:'],
|
|
809
|
-
label: 'ACP_RELAY_URL',
|
|
810
|
-
});
|
|
811
|
-
defaultClient = new ACPClient({
|
|
812
|
-
baseUrl: trustedEndpoint.toString(),
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
catch (err) {
|
|
816
|
-
logger.error({ err: err.message, acp_relay_url: ACP_RELAY_URL }, 'ACP channel disabled remote sync due to endpoint security policy');
|
|
817
|
-
defaultClient = null;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
async connect() {
|
|
822
|
-
this.connected = true;
|
|
823
|
-
logger.info({
|
|
824
|
-
relay_url: ACP_RELAY_URL || null,
|
|
825
|
-
}, 'ACP channel ready');
|
|
826
|
-
}
|
|
827
|
-
async sendMessage(jid, text, _options) {
|
|
828
|
-
const sessionId = sessionIdByChatJid.get(jid);
|
|
829
|
-
if (!sessionId)
|
|
830
|
-
return;
|
|
831
|
-
const session = sessionsById.get(sessionId);
|
|
832
|
-
if (!session || !text.trim())
|
|
833
|
-
return;
|
|
834
|
-
const envelope = {
|
|
835
|
-
id: randomUUID(),
|
|
836
|
-
role: 'assistant',
|
|
837
|
-
content: [{ type: 'markdown', text }],
|
|
838
|
-
created_at: nowIso(),
|
|
839
|
-
};
|
|
840
|
-
appendMessage(session, envelope);
|
|
841
|
-
broadcastSessionEvent(session, {
|
|
842
|
-
type: 'message',
|
|
843
|
-
session_id: session.id,
|
|
844
|
-
task_id: session.last_task_id,
|
|
845
|
-
message: envelope,
|
|
846
|
-
});
|
|
847
|
-
await syncEnvelopeToRelay(session, envelope);
|
|
848
|
-
}
|
|
849
|
-
async sendFile(jid, filePath, caption) {
|
|
850
|
-
const sessionId = sessionIdByChatJid.get(jid);
|
|
851
|
-
if (!sessionId)
|
|
852
|
-
return;
|
|
853
|
-
const session = sessionsById.get(sessionId);
|
|
854
|
-
if (!session)
|
|
855
|
-
return;
|
|
856
|
-
const artifactPart = {
|
|
857
|
-
type: 'artifact',
|
|
858
|
-
name: path.basename(filePath),
|
|
859
|
-
path: filePath,
|
|
860
|
-
mime_type: inferMimeType(filePath),
|
|
861
|
-
description: caption,
|
|
862
|
-
};
|
|
863
|
-
if (fs.existsSync(filePath)) {
|
|
864
|
-
artifactPart.uri = filePath;
|
|
865
|
-
}
|
|
866
|
-
const envelope = {
|
|
867
|
-
id: randomUUID(),
|
|
868
|
-
role: 'assistant',
|
|
869
|
-
content: [
|
|
870
|
-
...(caption ? [{ type: 'markdown', text: caption }] : []),
|
|
871
|
-
artifactPart,
|
|
872
|
-
],
|
|
873
|
-
created_at: nowIso(),
|
|
874
|
-
};
|
|
875
|
-
appendMessage(session, envelope);
|
|
876
|
-
broadcastSessionEvent(session, {
|
|
877
|
-
type: 'message',
|
|
878
|
-
session_id: session.id,
|
|
879
|
-
task_id: session.last_task_id,
|
|
880
|
-
message: envelope,
|
|
881
|
-
});
|
|
882
|
-
await syncEnvelopeToRelay(session, envelope);
|
|
883
|
-
}
|
|
884
|
-
isConnected() {
|
|
885
|
-
return this.connected;
|
|
886
|
-
}
|
|
887
|
-
ownsJid(jid) {
|
|
888
|
-
return jid.startsWith(ACP_JID_PREFIX);
|
|
889
|
-
}
|
|
890
|
-
async disconnect() {
|
|
891
|
-
this.connected = false;
|
|
892
|
-
for (const session of sessionsById.values()) {
|
|
893
|
-
session.relay_abort?.abort();
|
|
894
|
-
for (const client of session.clients) {
|
|
895
|
-
try {
|
|
896
|
-
client.end();
|
|
897
|
-
}
|
|
898
|
-
catch {
|
|
899
|
-
/* ignore */
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
session.clients.clear();
|
|
903
|
-
}
|
|
904
|
-
logger.info('ACP channel disconnected');
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
function createAcpChannel(opts) {
|
|
908
|
-
if (!ACP_ENABLED) {
|
|
909
|
-
logger.debug('ACP channel disabled (ACP_ENABLED=false)');
|
|
910
|
-
return null;
|
|
911
|
-
}
|
|
912
|
-
return new AcpChannel(opts);
|
|
913
|
-
}
|
|
914
|
-
registerChannel('acp', createAcpChannel);
|
|
915
|
-
//# sourceMappingURL=acp.js.map
|