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.
@@ -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");