agent-anywhere-gateway 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/README.md +46 -0
- package/bin/agent-anywhere-gateway.js +13 -0
- package/config/cloudflare.gateway.env.example +9 -0
- package/package.json +29 -0
- package/src/adapters/local-agent-adapter.js +131 -0
- package/src/gateway/client.js +422 -0
- package/src/gateway/main.js +224 -0
- package/src/gateway/providers.js +28 -0
- package/src/gateway/runner.js +337 -0
- package/src/gateway.js +7 -0
- package/src/lib/capabilities.js +1 -0
- package/src/lib/local-discovery.js +322 -0
- package/src/lib/path-policy.js +1 -0
- package/src/runtimes/claude-code-headless-runtime.js +547 -0
- package/src/runtimes/claude-code-runtime.js +984 -0
- package/src/runtimes/codex-app-server-client.js +157 -0
- package/src/runtimes/codex-app-server-runtime.js +790 -0
- package/src/runtimes/codex-runtime.js +418 -0
- package/src/runtimes/mock-runtime.js +140 -0
- package/src/shared/capabilities.js +175 -0
- package/src/shared/gateway-protocol.js +26 -0
- package/src/shared/http-utils.js +78 -0
- package/src/shared/image-attachments.js +269 -0
- package/src/shared/path-policy.js +110 -0
- package/src/shared/project-files.js +119 -0
- package/src/shared/providers.js +27 -0
- package/src/shared/runtime-environment.js +32 -0
- package/src/shared/websocket.js +258 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
const { execFileSync } = require("node:child_process");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const os = require("node:os");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { DEFAULT_REASONING_EFFORT, MODEL_OPTIONS, MODE_OPTIONS, REASONING_EFFORT_OPTIONS } = require("./capabilities");
|
|
6
|
+
const { isInside } = require("./path-policy");
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CODEX_DB = path.join(os.homedir(), ".codex", "state_5.sqlite");
|
|
9
|
+
|
|
10
|
+
function shouldSkipDir(name) {
|
|
11
|
+
return [".git", "node_modules", ".venv", "venv", "dist", "build", ".next"].includes(name);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function discoverGitProjects(allowedRoots, { maxDepth = 2 } = {}) {
|
|
15
|
+
const projects = [];
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
|
|
18
|
+
function visit(dir, depth) {
|
|
19
|
+
let entries;
|
|
20
|
+
try {
|
|
21
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
22
|
+
} catch {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (entries.some((entry) => entry.isDirectory() && entry.name === ".git")) {
|
|
27
|
+
const resolved = path.resolve(dir);
|
|
28
|
+
if (!seen.has(resolved)) {
|
|
29
|
+
seen.add(resolved);
|
|
30
|
+
projects.push({
|
|
31
|
+
name: path.basename(resolved),
|
|
32
|
+
path: resolved
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (depth >= maxDepth) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (!entry.isDirectory() || shouldSkipDir(entry.name)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
visit(path.join(dir, entry.name), depth + 1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const root of allowedRoots) {
|
|
51
|
+
visit(root, 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return projects.sort((a, b) => a.name.localeCompare(b.name));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeModeFromSandbox(sandboxPolicy) {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(sandboxPolicy || "{}");
|
|
60
|
+
if (parsed.type === "danger-full-access") {
|
|
61
|
+
return "full-access";
|
|
62
|
+
}
|
|
63
|
+
if (parsed.type === "read-only") {
|
|
64
|
+
return "auto-review";
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
return "default";
|
|
68
|
+
}
|
|
69
|
+
return "default";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeCodexThread(row) {
|
|
73
|
+
const model = MODEL_OPTIONS.includes(row.model) ? row.model : MODEL_OPTIONS[0];
|
|
74
|
+
const reasoning_effort = REASONING_EFFORT_OPTIONS.includes(row.reasoning_effort)
|
|
75
|
+
? row.reasoning_effort
|
|
76
|
+
: DEFAULT_REASONING_EFFORT;
|
|
77
|
+
const mode = MODE_OPTIONS.includes(row.mode) ? row.mode : "default";
|
|
78
|
+
const updatedAt = row.updated_at_ms ? new Date(Number(row.updated_at_ms)).toISOString() : row.updated_at;
|
|
79
|
+
const createdAt = row.created_at_ms ? new Date(Number(row.created_at_ms)).toISOString() : updatedAt;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
runtime_session_id: row.id,
|
|
83
|
+
rollout_path: row.rollout_path || null,
|
|
84
|
+
title: row.title || row.first_user_message || row.id,
|
|
85
|
+
provider: "codex",
|
|
86
|
+
model,
|
|
87
|
+
reasoning_effort,
|
|
88
|
+
approval_policy: row.approval_mode === "never" ? "never" : "on-request",
|
|
89
|
+
mode,
|
|
90
|
+
status: "idle",
|
|
91
|
+
created_at: createdAt,
|
|
92
|
+
updated_at: updatedAt,
|
|
93
|
+
external_source: "codex",
|
|
94
|
+
external_updated_at: updatedAt
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseSqliteJsonLines(raw) {
|
|
99
|
+
return String(raw || "")
|
|
100
|
+
.split(/\r?\n/)
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
.map((line) => JSON.parse(line));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function renderRolloutContent(content) {
|
|
106
|
+
if (typeof content === "string") {
|
|
107
|
+
return content;
|
|
108
|
+
}
|
|
109
|
+
if (!Array.isArray(content)) {
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
return content
|
|
113
|
+
.map((item) => {
|
|
114
|
+
if (typeof item === "string") {
|
|
115
|
+
return item;
|
|
116
|
+
}
|
|
117
|
+
return item?.text || "";
|
|
118
|
+
})
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
.join("\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function isFinalRolloutAgentMessage(payload = {}) {
|
|
124
|
+
const phase = String(payload.phase || payload.kind || "").toLowerCase();
|
|
125
|
+
if (!phase) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return ["final", "answer", "assistant", "output"].includes(phase);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readCodexRolloutTranscript(rolloutPath, { maxMessages = 200 } = {}) {
|
|
132
|
+
if (!rolloutPath || !fs.existsSync(rolloutPath) || !fs.statSync(rolloutPath).isFile()) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const transcript = [];
|
|
137
|
+
const fallback = [];
|
|
138
|
+
const lines = fs.readFileSync(rolloutPath, "utf8").split(/\r?\n/).filter(Boolean);
|
|
139
|
+
for (const line of lines) {
|
|
140
|
+
let row;
|
|
141
|
+
try {
|
|
142
|
+
row = JSON.parse(line);
|
|
143
|
+
} catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const timestamp = row.timestamp || null;
|
|
147
|
+
const payload = row.payload || {};
|
|
148
|
+
if (row.type === "event_msg" && payload.type === "user_message") {
|
|
149
|
+
transcript.push({ role: "user", text: String(payload.message || "").trim(), timestamp });
|
|
150
|
+
} else if (row.type === "event_msg" && payload.type === "agent_message") {
|
|
151
|
+
if (!isFinalRolloutAgentMessage(payload)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
transcript.push({ role: "assistant", text: String(payload.message || "").trim(), timestamp });
|
|
155
|
+
} else if (row.type === "response_item" && payload.type === "message") {
|
|
156
|
+
const text = renderRolloutContent(payload.content).trim();
|
|
157
|
+
if (["user", "assistant"].includes(payload.role) && text && !text.includes("<environment_context>")) {
|
|
158
|
+
fallback.push({ role: payload.role, text, timestamp });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const messages = transcript.length ? transcript : fallback;
|
|
164
|
+
return messages.filter((message) => message.text).slice(-maxMessages);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function codexTranscriptToSessionHistory(sessionId, transcript) {
|
|
168
|
+
const turns = [];
|
|
169
|
+
const events = [];
|
|
170
|
+
let currentTurn = null;
|
|
171
|
+
let assistantParts = [];
|
|
172
|
+
|
|
173
|
+
function flushAssistant(timestamp) {
|
|
174
|
+
if (!currentTurn || !assistantParts.length) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
events.push({
|
|
178
|
+
id: `${currentTurn.id}:assistant`,
|
|
179
|
+
session_id: sessionId,
|
|
180
|
+
turn_id: currentTurn.id,
|
|
181
|
+
type: "delta",
|
|
182
|
+
payload: { text: assistantParts.join("\n\n") },
|
|
183
|
+
created_at: timestamp || currentTurn.completed_at || currentTurn.started_at
|
|
184
|
+
});
|
|
185
|
+
assistantParts = [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const message of transcript) {
|
|
189
|
+
if (message.role === "user") {
|
|
190
|
+
flushAssistant(message.timestamp);
|
|
191
|
+
const turn = {
|
|
192
|
+
id: `${sessionId}:external-turn-${turns.length + 1}`,
|
|
193
|
+
session_id: sessionId,
|
|
194
|
+
prompt: message.text,
|
|
195
|
+
status: "completed",
|
|
196
|
+
started_at: message.timestamp,
|
|
197
|
+
completed_at: message.timestamp,
|
|
198
|
+
error: null
|
|
199
|
+
};
|
|
200
|
+
turns.push(turn);
|
|
201
|
+
currentTurn = turn;
|
|
202
|
+
} else if (message.role === "assistant") {
|
|
203
|
+
if (!currentTurn) {
|
|
204
|
+
currentTurn = {
|
|
205
|
+
id: `${sessionId}:external-turn-${turns.length + 1}`,
|
|
206
|
+
session_id: sessionId,
|
|
207
|
+
prompt: "历史对话",
|
|
208
|
+
status: "completed",
|
|
209
|
+
started_at: message.timestamp,
|
|
210
|
+
completed_at: message.timestamp,
|
|
211
|
+
error: null
|
|
212
|
+
};
|
|
213
|
+
turns.push(currentTurn);
|
|
214
|
+
}
|
|
215
|
+
currentTurn.completed_at = message.timestamp || currentTurn.completed_at;
|
|
216
|
+
assistantParts.push(message.text);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
flushAssistant(transcript.at(-1)?.timestamp || null);
|
|
220
|
+
|
|
221
|
+
return { turns, events };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function readCodexRolloutHistory(sessionId, rolloutPath) {
|
|
225
|
+
return codexTranscriptToSessionHistory(sessionId, readCodexRolloutTranscript(rolloutPath));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function discoverCodexThreadsForProject(projectPath, {
|
|
229
|
+
dbPath = process.env.CODEX_STATE_DB_PATH || DEFAULT_CODEX_DB,
|
|
230
|
+
allowedRoots = []
|
|
231
|
+
} = {}) {
|
|
232
|
+
const resolvedProjectPath = path.resolve(projectPath);
|
|
233
|
+
if (allowedRoots.length && !allowedRoots.some((root) => isInside(resolvedProjectPath, root))) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
if (!fs.existsSync(dbPath)) {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let output;
|
|
241
|
+
try {
|
|
242
|
+
const escapedPath = resolvedProjectPath.replaceAll("'", "''");
|
|
243
|
+
output = execFileSync("sqlite3", [
|
|
244
|
+
"-json",
|
|
245
|
+
dbPath,
|
|
246
|
+
`select id, cwd, rollout_path, title, first_user_message, model, reasoning_effort, approval_mode,
|
|
247
|
+
sandbox_policy, created_at_ms, updated_at_ms, datetime(updated_at, 'unixepoch') as updated_at
|
|
248
|
+
from threads
|
|
249
|
+
where cwd = '${escapedPath}'
|
|
250
|
+
and archived = 0
|
|
251
|
+
order by coalesce(updated_at_ms, updated_at * 1000) desc
|
|
252
|
+
limit 100;`
|
|
253
|
+
], {
|
|
254
|
+
encoding: "utf8",
|
|
255
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
256
|
+
});
|
|
257
|
+
} catch {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const rows = JSON.parse(output || "[]");
|
|
262
|
+
return rows.map((row) => normalizeCodexThread({
|
|
263
|
+
...row,
|
|
264
|
+
mode: normalizeModeFromSandbox(row.sandbox_policy)
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function findCodexThreadById(threadId, {
|
|
269
|
+
dbPath = process.env.CODEX_STATE_DB_PATH || DEFAULT_CODEX_DB,
|
|
270
|
+
allowedRoots = []
|
|
271
|
+
} = {}) {
|
|
272
|
+
const id = String(threadId || "").trim();
|
|
273
|
+
if (!id || !fs.existsSync(dbPath)) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let output;
|
|
278
|
+
try {
|
|
279
|
+
const escapedId = id.replaceAll("'", "''");
|
|
280
|
+
output = execFileSync("sqlite3", [
|
|
281
|
+
"-json",
|
|
282
|
+
dbPath,
|
|
283
|
+
`select id, cwd, rollout_path, title, first_user_message, model, reasoning_effort, approval_mode,
|
|
284
|
+
sandbox_policy, created_at_ms, updated_at_ms, datetime(updated_at, 'unixepoch') as updated_at
|
|
285
|
+
from threads
|
|
286
|
+
where id = '${escapedId}'
|
|
287
|
+
and archived = 0
|
|
288
|
+
limit 1;`
|
|
289
|
+
], {
|
|
290
|
+
encoding: "utf8",
|
|
291
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
292
|
+
});
|
|
293
|
+
} catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const row = JSON.parse(output || "[]")[0];
|
|
298
|
+
if (!row) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
const cwd = path.resolve(row.cwd || "");
|
|
302
|
+
if (allowedRoots.length && !allowedRoots.some((root) => isInside(cwd, root))) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return normalizeCodexThread({
|
|
306
|
+
...row,
|
|
307
|
+
mode: normalizeModeFromSandbox(row.sandbox_policy)
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
DEFAULT_CODEX_DB,
|
|
313
|
+
codexTranscriptToSessionHistory,
|
|
314
|
+
discoverCodexThreadsForProject,
|
|
315
|
+
discoverGitProjects,
|
|
316
|
+
findCodexThreadById,
|
|
317
|
+
normalizeCodexThread,
|
|
318
|
+
normalizeModeFromSandbox,
|
|
319
|
+
parseSqliteJsonLines,
|
|
320
|
+
readCodexRolloutHistory,
|
|
321
|
+
readCodexRolloutTranscript
|
|
322
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("../shared/path-policy");
|