agentgui 1.0.762 → 1.0.763
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/CLAUDE.md +1 -0
- package/lib/routes-threads.js +99 -0
- package/package.json +1 -1
- package/server.js +4 -155
package/CLAUDE.md
CHANGED
|
@@ -50,6 +50,7 @@ lib/routes-speech.js Speech/TTS HTTP route handlers (stt, tts, voices, speech-
|
|
|
50
50
|
lib/routes-oauth.js OAuth HTTP route handlers (gemini-oauth/*, codex-oauth/*)
|
|
51
51
|
lib/routes-tools.js Tool management HTTP route handlers (list, install, update, history, refresh)
|
|
52
52
|
lib/routes-util.js Utility HTTP route handlers (clone, folders, git, home, version, import)
|
|
53
|
+
lib/routes-threads.js Thread CRUD HTTP route handlers (ACP v0.2.3 thread API)
|
|
53
54
|
lib/ws-protocol.js WebSocket RPC router (WsRouter class)
|
|
54
55
|
lib/ws-optimizer.js Per-client priority queue for WS event batching
|
|
55
56
|
lib/ws-handlers-conv.js Conversation CRUD, chunks, cancel, steer, inject RPC handlers
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export function register(deps) {
|
|
2
|
+
const { sendJSON, parseBody, queries } = deps;
|
|
3
|
+
const routes = {};
|
|
4
|
+
|
|
5
|
+
routes['POST /api/threads'] = async (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const body = await parseBody(req);
|
|
8
|
+
sendJSON(req, res, 201, queries.createThread(body.metadata || {}));
|
|
9
|
+
} catch (err) {
|
|
10
|
+
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
routes['POST /api/threads/search'] = async (req, res) => {
|
|
15
|
+
try {
|
|
16
|
+
sendJSON(req, res, 200, queries.searchThreads(await parseBody(req)));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
routes['_match'] = (method, pathOnly) => {
|
|
23
|
+
const key = `${method} ${pathOnly}`;
|
|
24
|
+
if (routes[key]) return routes[key];
|
|
25
|
+
let m;
|
|
26
|
+
if ((m = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})$/))) {
|
|
27
|
+
if (method === 'GET') return (req, res) => handleGetThread(req, res, m[1]);
|
|
28
|
+
if (method === 'PATCH') return (req, res) => handlePatchThread(req, res, m[1]);
|
|
29
|
+
if (method === 'DELETE') return (req, res) => handleDeleteThread(req, res, m[1]);
|
|
30
|
+
}
|
|
31
|
+
if (method === 'GET' && (m = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/history$/)))
|
|
32
|
+
return (req, res) => handleThreadHistory(req, res, m[1]);
|
|
33
|
+
if (method === 'POST' && (m = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/copy$/)))
|
|
34
|
+
return (req, res) => handleThreadCopy(req, res, m[1]);
|
|
35
|
+
if (method === 'POST' && pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/runs\/stream$/))
|
|
36
|
+
return (req, res) => { res.writeHead(410); res.end(JSON.stringify({ error: 'SSE removed, use WebSocket' })); };
|
|
37
|
+
if (method === 'GET' && pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/runs\/([a-f0-9-]{36})\/stream$/))
|
|
38
|
+
return (req, res) => { res.writeHead(410); res.end(JSON.stringify({ error: 'SSE removed, use WebSocket' })); };
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
async function handleGetThread(req, res, threadId) {
|
|
43
|
+
try {
|
|
44
|
+
const thread = queries.getThread(threadId);
|
|
45
|
+
if (!thread) sendJSON(req, res, 404, { error: 'Thread not found', type: 'not_found' });
|
|
46
|
+
else sendJSON(req, res, 200, thread);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function handlePatchThread(req, res, threadId) {
|
|
53
|
+
try {
|
|
54
|
+
sendJSON(req, res, 200, queries.patchThread(threadId, await parseBody(req)));
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const code = err.message.includes('not found') ? 404 : 422;
|
|
57
|
+
sendJSON(req, res, code, { error: err.message, type: code === 404 ? 'not_found' : 'validation_error' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function handleDeleteThread(req, res, threadId) {
|
|
62
|
+
try {
|
|
63
|
+
queries.deleteThread(threadId);
|
|
64
|
+
res.writeHead(204); res.end();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.message.includes('not found')) sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
67
|
+
else if (err.message.includes('pending runs')) sendJSON(req, res, 409, { error: err.message, type: 'conflict' });
|
|
68
|
+
else sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function handleThreadHistory(req, res, threadId) {
|
|
73
|
+
try {
|
|
74
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
75
|
+
const limit = parseInt(url.searchParams.get('limit') || '50', 10);
|
|
76
|
+
const before = url.searchParams.get('before') || null;
|
|
77
|
+
let offset = before ? parseInt(before, 10) : 0;
|
|
78
|
+
const result = queries.getThreadHistory(threadId, limit, offset);
|
|
79
|
+
sendJSON(req, res, 200, { states: result.states, next_cursor: result.hasMore ? String(offset + limit) : null });
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const code = err.message.includes('not found') ? 404 : 422;
|
|
82
|
+
sendJSON(req, res, code, { error: err.message, type: code === 404 ? 'not_found' : 'validation_error' });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function handleThreadCopy(req, res, sourceThreadId) {
|
|
87
|
+
try {
|
|
88
|
+
const body = await parseBody(req);
|
|
89
|
+
const newThread = queries.copyThread(sourceThreadId);
|
|
90
|
+
if (body.metadata) sendJSON(req, res, 201, queries.patchThread(newThread.thread_id, { metadata: body.metadata }));
|
|
91
|
+
else sendJSON(req, res, 201, newThread);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const code = err.message.includes('not found') ? 404 : 500;
|
|
94
|
+
sendJSON(req, res, code, { error: err.message, type: code === 404 ? 'not_found' : 'internal_error' });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return routes;
|
|
99
|
+
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -23,6 +23,7 @@ import { register as registerSpeechRoutes } from './lib/routes-speech.js';
|
|
|
23
23
|
import { register as registerOAuthRoutes } from './lib/routes-oauth.js';
|
|
24
24
|
import { register as registerUtilRoutes } from './lib/routes-util.js';
|
|
25
25
|
import { register as registerToolRoutes } from './lib/routes-tools.js';
|
|
26
|
+
import { register as registerThreadRoutes } from './lib/routes-threads.js';
|
|
26
27
|
import { startCodexOAuth, exchangeCodexOAuthCode, handleCodexOAuthCallback, getCodexOAuthStatus, getCodexOAuthState, CODEX_HOME, CODEX_AUTH_FILE } from './lib/oauth-codex.js';
|
|
27
28
|
import { WSOptimizer } from './lib/ws-optimizer.js';
|
|
28
29
|
import { WsRouter } from './lib/ws-protocol.js';
|
|
@@ -1927,161 +1928,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1927
1928
|
const utilHandler = _utilRoutes._match(req.method, pathOnly);
|
|
1928
1929
|
if (utilHandler) { await utilHandler(req, res); return; }
|
|
1929
1930
|
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
// POST /threads - Create empty thread
|
|
1934
|
-
if (pathOnly === '/api/threads' && req.method === 'POST') {
|
|
1935
|
-
console.log('[ACP] POST /api/threads HIT');
|
|
1936
|
-
try {
|
|
1937
|
-
const body = await parseBody(req);
|
|
1938
|
-
const metadata = body.metadata || {};
|
|
1939
|
-
const thread = queries.createThread(metadata);
|
|
1940
|
-
sendJSON(req, res, 201, thread);
|
|
1941
|
-
} catch (err) {
|
|
1942
|
-
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
1943
|
-
}
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
// POST /threads/search - Search threads
|
|
1948
|
-
if (pathOnly === '/api/threads/search' && req.method === 'POST') {
|
|
1949
|
-
try {
|
|
1950
|
-
const body = await parseBody(req);
|
|
1951
|
-
const result = queries.searchThreads(body);
|
|
1952
|
-
sendJSON(req, res, 200, result);
|
|
1953
|
-
} catch (err) {
|
|
1954
|
-
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
1955
|
-
}
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
// GET /threads/{thread_id} - Get thread by ID
|
|
1960
|
-
const acpThreadMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})$/);
|
|
1961
|
-
if (acpThreadMatch && req.method === 'GET') {
|
|
1962
|
-
const threadId = acpThreadMatch[1];
|
|
1963
|
-
try {
|
|
1964
|
-
const thread = queries.getThread(threadId);
|
|
1965
|
-
if (!thread) {
|
|
1966
|
-
sendJSON(req, res, 404, { error: 'Thread not found', type: 'not_found' });
|
|
1967
|
-
} else {
|
|
1968
|
-
sendJSON(req, res, 200, thread);
|
|
1969
|
-
}
|
|
1970
|
-
} catch (err) {
|
|
1971
|
-
sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
1972
|
-
}
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
// PATCH /threads/{thread_id} - Update thread metadata
|
|
1977
|
-
if (acpThreadMatch && req.method === 'PATCH') {
|
|
1978
|
-
const threadId = acpThreadMatch[1];
|
|
1979
|
-
try {
|
|
1980
|
-
const body = await parseBody(req);
|
|
1981
|
-
const thread = queries.patchThread(threadId, body);
|
|
1982
|
-
sendJSON(req, res, 200, thread);
|
|
1983
|
-
} catch (err) {
|
|
1984
|
-
if (err.message.includes('not found')) {
|
|
1985
|
-
sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
1986
|
-
} else {
|
|
1987
|
-
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
return;
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
// DELETE /threads/{thread_id} - Delete thread (fail if pending runs exist)
|
|
1994
|
-
if (acpThreadMatch && req.method === 'DELETE') {
|
|
1995
|
-
const threadId = acpThreadMatch[1];
|
|
1996
|
-
try {
|
|
1997
|
-
queries.deleteThread(threadId);
|
|
1998
|
-
res.writeHead(204);
|
|
1999
|
-
res.end();
|
|
2000
|
-
} catch (err) {
|
|
2001
|
-
if (err.message.includes('not found')) {
|
|
2002
|
-
sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
2003
|
-
} else if (err.message.includes('pending runs')) {
|
|
2004
|
-
sendJSON(req, res, 409, { error: err.message, type: 'conflict' });
|
|
2005
|
-
} else {
|
|
2006
|
-
sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
return;
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
// GET /threads/{thread_id}/history - Get thread state history with pagination
|
|
2013
|
-
const threadHistoryMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/history$/);
|
|
2014
|
-
if (threadHistoryMatch && req.method === 'GET') {
|
|
2015
|
-
const threadId = threadHistoryMatch[1];
|
|
2016
|
-
try {
|
|
2017
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
2018
|
-
const limit = parseInt(url.searchParams.get('limit') || '50', 10);
|
|
2019
|
-
const before = url.searchParams.get('before') || null;
|
|
2020
|
-
|
|
2021
|
-
// Convert 'before' cursor to offset if needed
|
|
2022
|
-
let offset = 0;
|
|
2023
|
-
if (before) {
|
|
2024
|
-
// Simple cursor-based pagination: before is an offset value
|
|
2025
|
-
offset = parseInt(before, 10);
|
|
2026
|
-
}
|
|
2027
|
-
|
|
2028
|
-
const result = queries.getThreadHistory(threadId, limit, offset);
|
|
2029
|
-
|
|
2030
|
-
// Generate next_cursor if there are more results
|
|
2031
|
-
const response = {
|
|
2032
|
-
states: result.states,
|
|
2033
|
-
next_cursor: result.hasMore ? String(offset + limit) : null
|
|
2034
|
-
};
|
|
2035
|
-
|
|
2036
|
-
sendJSON(req, res, 200, response);
|
|
2037
|
-
} catch (err) {
|
|
2038
|
-
if (err.message.includes('not found')) {
|
|
2039
|
-
sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
2040
|
-
} else {
|
|
2041
|
-
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
return;
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
// POST /threads/{thread_id}/copy - Copy thread with all states/checkpoints
|
|
2048
|
-
const threadCopyMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/copy$/);
|
|
2049
|
-
if (threadCopyMatch && req.method === 'POST') {
|
|
2050
|
-
const sourceThreadId = threadCopyMatch[1];
|
|
2051
|
-
try {
|
|
2052
|
-
const body = await parseBody(req);
|
|
2053
|
-
const newThread = queries.copyThread(sourceThreadId);
|
|
2054
|
-
|
|
2055
|
-
// Update metadata if provided
|
|
2056
|
-
if (body.metadata) {
|
|
2057
|
-
const updated = queries.patchThread(newThread.thread_id, { metadata: body.metadata });
|
|
2058
|
-
sendJSON(req, res, 201, updated);
|
|
2059
|
-
} else {
|
|
2060
|
-
sendJSON(req, res, 201, newThread);
|
|
2061
|
-
}
|
|
2062
|
-
} catch (err) {
|
|
2063
|
-
if (err.message.includes('not found')) {
|
|
2064
|
-
sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
2065
|
-
} else {
|
|
2066
|
-
sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
// POST /threads/{thread_id}/runs/stream - SSE removed, use WebSocket
|
|
2073
|
-
const threadRunsStreamMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/runs\/stream$/);
|
|
2074
|
-
if (threadRunsStreamMatch && req.method === 'POST') {
|
|
2075
|
-
res.writeHead(410); res.end(JSON.stringify({ error: 'SSE removed, use WebSocket' }));
|
|
2076
|
-
return;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
// GET /threads/{thread_id}/runs/{run_id}/stream - SSE removed, use WebSocket
|
|
2080
|
-
const threadRunStreamMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/runs\/([a-f0-9-]{36})\/stream$/);
|
|
2081
|
-
if (threadRunStreamMatch && req.method === 'GET') {
|
|
2082
|
-
res.writeHead(410); res.end(JSON.stringify({ error: 'SSE removed, use WebSocket' }));
|
|
2083
|
-
return;
|
|
2084
|
-
}
|
|
1931
|
+
const threadHandler = _threadRoutes._match(req.method, pathOnly);
|
|
1932
|
+
if (threadHandler) { await threadHandler(req, res); return; }
|
|
2085
1933
|
|
|
2086
1934
|
if (routePath.startsWith('/api/image/')) {
|
|
2087
1935
|
const imagePath = routePath.slice('/api/image/'.length);
|
|
@@ -3072,6 +2920,7 @@ const _speechRoutes = registerSpeechRoutes({ sendJSON, parseBody, broadcastSync,
|
|
|
3072
2920
|
const _oauthRoutes = registerOAuthRoutes({ sendJSON, parseBody, PORT, BASE_URL, rootDir });
|
|
3073
2921
|
const _utilRoutes = registerUtilRoutes({ sendJSON, parseBody, queries, STARTUP_CWD, PKG_VERSION });
|
|
3074
2922
|
const _toolRoutes = registerToolRoutes({ sendJSON, parseBody, queries, broadcastSync, logError, toolManager });
|
|
2923
|
+
const _threadRoutes = registerThreadRoutes({ sendJSON, parseBody, queries });
|
|
3075
2924
|
|
|
3076
2925
|
registerConvHandlers(wsRouter, {
|
|
3077
2926
|
queries, activeExecutions, rateLimitState,
|