@varveai/adit-cli 0.3.0 → 0.3.2
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/dist/commands/cli-agent/claude-code-provider.d.ts +86 -0
- package/dist/commands/cli-agent/claude-code-provider.d.ts.map +1 -0
- package/dist/commands/cli-agent/claude-code-provider.js +1347 -0
- package/dist/commands/cli-agent/claude-code-provider.js.map +1 -0
- package/dist/commands/cli-agent/claude-transcript-sync.d.ts +19 -0
- package/dist/commands/cli-agent/claude-transcript-sync.d.ts.map +1 -0
- package/dist/commands/cli-agent/claude-transcript-sync.js +365 -0
- package/dist/commands/cli-agent/claude-transcript-sync.js.map +1 -0
- package/dist/commands/cli-agent/codex-app-server-client.d.ts +42 -0
- package/dist/commands/cli-agent/codex-app-server-client.d.ts.map +1 -0
- package/dist/commands/cli-agent/codex-app-server-client.js +160 -0
- package/dist/commands/cli-agent/codex-app-server-client.js.map +1 -0
- package/dist/commands/cli-agent/codex-cli-provider.d.ts +78 -0
- package/dist/commands/cli-agent/codex-cli-provider.d.ts.map +1 -0
- package/dist/commands/cli-agent/codex-cli-provider.js +974 -0
- package/dist/commands/cli-agent/codex-cli-provider.js.map +1 -0
- package/dist/commands/cli-agent/codex-transcript-sync.d.ts +34 -0
- package/dist/commands/cli-agent/codex-transcript-sync.d.ts.map +1 -0
- package/dist/commands/cli-agent/codex-transcript-sync.js +522 -0
- package/dist/commands/cli-agent/codex-transcript-sync.js.map +1 -0
- package/dist/commands/cli-agent/hook-server.d.ts +13 -0
- package/dist/commands/cli-agent/hook-server.d.ts.map +1 -0
- package/dist/commands/cli-agent/hook-server.js +74 -0
- package/dist/commands/cli-agent/hook-server.js.map +1 -0
- package/dist/commands/cli-agent/hooks-bootstrap.d.ts +17 -0
- package/dist/commands/cli-agent/hooks-bootstrap.d.ts.map +1 -0
- package/dist/commands/cli-agent/hooks-bootstrap.js +449 -0
- package/dist/commands/cli-agent/hooks-bootstrap.js.map +1 -0
- package/dist/commands/cli-agent/relay-client.d.ts +50 -0
- package/dist/commands/cli-agent/relay-client.d.ts.map +1 -0
- package/dist/commands/cli-agent/relay-client.js +202 -0
- package/dist/commands/cli-agent/relay-client.js.map +1 -0
- package/dist/commands/cli-agent/types.d.ts +58 -0
- package/dist/commands/cli-agent/types.d.ts.map +1 -0
- package/dist/commands/cli-agent/types.js +2 -0
- package/dist/commands/cli-agent/types.js.map +1 -0
- package/dist/commands/cloud-claude.d.ts +7 -0
- package/dist/commands/cloud-claude.d.ts.map +1 -0
- package/dist/commands/cloud-claude.js +331 -0
- package/dist/commands/cloud-claude.js.map +1 -0
- package/dist/commands/cloud-codex.d.ts +7 -0
- package/dist/commands/cloud-codex.d.ts.map +1 -0
- package/dist/commands/cloud-codex.js +373 -0
- package/dist/commands/cloud-codex.js.map +1 -0
- package/dist/commands/complete.d.ts +15 -0
- package/dist/commands/complete.d.ts.map +1 -0
- package/dist/commands/complete.js +129 -0
- package/dist/commands/complete.js.map +1 -0
- package/dist/commands/docs.d.ts +16 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +206 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +7 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +139 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/plugin.d.ts.map +1 -1
- package/dist/commands/plugin.js +3 -0
- package/dist/commands/plugin.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +1 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/index.js +56 -21
- package/dist/index.js.map +1 -1
- package/package.json +9 -6
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { CodexAppServerClient, } from "./codex-app-server-client.js";
|
|
4
|
+
const RECLAIM_COMMAND = "/local";
|
|
5
|
+
const TERMINAL_RECLAIM_RESET = [
|
|
6
|
+
"\x1b[?1004l", // Focus in/out reporting.
|
|
7
|
+
"\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l", // Mouse modes.
|
|
8
|
+
"\x1b[?2004l", // Bracketed paste.
|
|
9
|
+
"\x1b[?1l", // Application cursor keys.
|
|
10
|
+
"\x1b[=0u\x1b[<u\x1b[<u\x1b[<u", // Kitty/CSI-u keyboard protocol.
|
|
11
|
+
"\x1b[>4;0m", // xterm modifyOtherKeys.
|
|
12
|
+
"\x1b[?25h", // Cursor visible.
|
|
13
|
+
].join("");
|
|
14
|
+
const CODEX_PLAN_MODE_INSTRUCTION = [
|
|
15
|
+
"ADIT Plan mode is active.",
|
|
16
|
+
"Do not modify files, create files, apply patches, install packages, start services, commit changes, or otherwise change the working tree.",
|
|
17
|
+
"Use read-only inspection only. If implementation is needed, return a concrete plan with files, steps, risks, and questions instead of editing.",
|
|
18
|
+
"Do not announce that you are about to edit files; stop at the plan.",
|
|
19
|
+
].join("\n");
|
|
20
|
+
export class CodexCliProvider extends EventEmitter {
|
|
21
|
+
opts;
|
|
22
|
+
provider = "codex";
|
|
23
|
+
local = null;
|
|
24
|
+
appServer = null;
|
|
25
|
+
ownerValue = "stopped";
|
|
26
|
+
busyValue = false;
|
|
27
|
+
thinkingValue = false;
|
|
28
|
+
activeSessionId = null;
|
|
29
|
+
resumeSessionId = null;
|
|
30
|
+
sdkSessionId = null;
|
|
31
|
+
activeModelId = null;
|
|
32
|
+
promptQueue = [];
|
|
33
|
+
promptActive = false;
|
|
34
|
+
activeTurnId = null;
|
|
35
|
+
loadedThreadIds = new Set();
|
|
36
|
+
boundPendingSessionIds = new Set();
|
|
37
|
+
pendingPermissions = new Map();
|
|
38
|
+
pendingQuestions = new Map();
|
|
39
|
+
lastAssistantMessageBySession = new Map();
|
|
40
|
+
reclaimAttached = false;
|
|
41
|
+
reclaimBuffer = "";
|
|
42
|
+
suppressNextLocalExit = false;
|
|
43
|
+
constructor(opts) {
|
|
44
|
+
super();
|
|
45
|
+
this.opts = opts;
|
|
46
|
+
this.startLocal();
|
|
47
|
+
}
|
|
48
|
+
get state() {
|
|
49
|
+
return {
|
|
50
|
+
owner: this.ownerValue,
|
|
51
|
+
busy: this.busyValue,
|
|
52
|
+
thinking: this.thinkingValue,
|
|
53
|
+
activeSessionId: this.activeSessionId,
|
|
54
|
+
resumeSessionId: this.resumeSessionId,
|
|
55
|
+
sdkSessionId: this.sdkSessionId,
|
|
56
|
+
activeModelId: this.activeModelId,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
get permissions() {
|
|
60
|
+
return [...this.pendingPermissions.values()].map((item) => item.request);
|
|
61
|
+
}
|
|
62
|
+
noteModel(modelId) {
|
|
63
|
+
if (!modelId || modelId === this.activeModelId)
|
|
64
|
+
return;
|
|
65
|
+
this.activeModelId = modelId;
|
|
66
|
+
this.emitState();
|
|
67
|
+
}
|
|
68
|
+
noteLocalSession(id) {
|
|
69
|
+
if (!id)
|
|
70
|
+
return;
|
|
71
|
+
const changed = this.activeSessionId !== id || this.resumeSessionId !== id;
|
|
72
|
+
this.activeSessionId = id;
|
|
73
|
+
this.resumeSessionId = id;
|
|
74
|
+
this.sdkSessionId = id;
|
|
75
|
+
if (!changed)
|
|
76
|
+
return;
|
|
77
|
+
this.emitState();
|
|
78
|
+
}
|
|
79
|
+
markLocalBusy() {
|
|
80
|
+
this.setBusy(true);
|
|
81
|
+
this.setThinking(true);
|
|
82
|
+
}
|
|
83
|
+
markLocalIdle() {
|
|
84
|
+
this.setThinking(false);
|
|
85
|
+
this.setBusy(false);
|
|
86
|
+
}
|
|
87
|
+
async takeover() {
|
|
88
|
+
if (this.ownerValue === "web")
|
|
89
|
+
return;
|
|
90
|
+
if (this.ownerValue !== "local") {
|
|
91
|
+
throw Object.assign(new Error("local Codex owner is not available"), {
|
|
92
|
+
statusCode: 409,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (this.busyValue || this.thinkingValue) {
|
|
96
|
+
throw Object.assign(new Error("local Codex is busy"), {
|
|
97
|
+
statusCode: 409,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
await this.ensureAppServer();
|
|
101
|
+
const resumeId = this.activeSessionId ?? this.resumeSessionId;
|
|
102
|
+
if (resumeId) {
|
|
103
|
+
await this.resumeThread(resumeId);
|
|
104
|
+
}
|
|
105
|
+
this.suppressNextLocalExit = true;
|
|
106
|
+
const oldLocal = this.local;
|
|
107
|
+
this.local = null;
|
|
108
|
+
try {
|
|
109
|
+
oldLocal?.kill("SIGTERM");
|
|
110
|
+
}
|
|
111
|
+
catch { }
|
|
112
|
+
this.ownerValue = "web";
|
|
113
|
+
this.emitState();
|
|
114
|
+
restoreTerminalForCodexReclaim();
|
|
115
|
+
process.stderr.write(`\n[adit cloud codex] Web has taken over Codex CLI. Type ${RECLAIM_COMMAND} here to reclaim local control.\n`);
|
|
116
|
+
this.attachReclaimInput();
|
|
117
|
+
}
|
|
118
|
+
async releaseToLocal() {
|
|
119
|
+
if (this.ownerValue !== "web")
|
|
120
|
+
return;
|
|
121
|
+
this.detachReclaimInput();
|
|
122
|
+
process.stderr.write("\n[adit cloud codex] releasing Web control back to local Codex CLI...\n");
|
|
123
|
+
this.finishWebPrompts(new Error("Web control released to local CLI"));
|
|
124
|
+
this.rejectPendingRequests(new Error("Web control released to local CLI"));
|
|
125
|
+
this.appServer?.stop();
|
|
126
|
+
this.appServer = null;
|
|
127
|
+
this.loadedThreadIds = new Set();
|
|
128
|
+
const resumeId = this.resumeSessionId ?? this.activeSessionId;
|
|
129
|
+
this.startLocal(resumeId ? ["resume", resumeId] : []);
|
|
130
|
+
}
|
|
131
|
+
async switchSession(sessionId) {
|
|
132
|
+
if (!sessionId.trim()) {
|
|
133
|
+
throw Object.assign(new Error("Codex session not found for this project"), {
|
|
134
|
+
statusCode: 404,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (this.ownerValue === "local") {
|
|
138
|
+
this.suppressNextLocalExit = true;
|
|
139
|
+
const oldLocal = this.local;
|
|
140
|
+
this.local = null;
|
|
141
|
+
try {
|
|
142
|
+
oldLocal?.kill("SIGTERM");
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
145
|
+
this.activeSessionId = sessionId;
|
|
146
|
+
this.resumeSessionId = sessionId;
|
|
147
|
+
this.sdkSessionId = sessionId;
|
|
148
|
+
this.emitState();
|
|
149
|
+
this.startLocal(["resume", sessionId]);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (this.ownerValue === "web") {
|
|
153
|
+
await this.ensureAppServer();
|
|
154
|
+
await this.resumeThread(sessionId);
|
|
155
|
+
this.finishWebPrompts(new Error("Codex session switched"));
|
|
156
|
+
this.setBusy(false);
|
|
157
|
+
this.setThinking(false);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async sendPrompt(prompt, opts = {}) {
|
|
162
|
+
if (this.ownerValue !== "web") {
|
|
163
|
+
throw Object.assign(new Error("Web has not taken over this Codex session"), {
|
|
164
|
+
statusCode: 409,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const trimmed = prompt.trim();
|
|
168
|
+
if (!trimmed)
|
|
169
|
+
return;
|
|
170
|
+
await new Promise((resolve, reject) => {
|
|
171
|
+
this.promptQueue.push({
|
|
172
|
+
message: trimmed,
|
|
173
|
+
mode: opts.mode === "plan" ? "plan" : "build",
|
|
174
|
+
pendingSessionId: opts.pendingSessionId ?? null,
|
|
175
|
+
resolve,
|
|
176
|
+
reject,
|
|
177
|
+
});
|
|
178
|
+
void this.drainPromptQueue();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async answerPermission(id, approved, _reason) {
|
|
182
|
+
const pending = this.pendingPermissions.get(id);
|
|
183
|
+
if (!pending) {
|
|
184
|
+
throw Object.assign(new Error("permission request not found"), {
|
|
185
|
+
statusCode: 404,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
this.pendingPermissions.delete(id);
|
|
189
|
+
this.pushEvent("permission-resolved", { id, approved });
|
|
190
|
+
pending.resolve(buildApprovalResponse(pending.method, pending.params, approved));
|
|
191
|
+
}
|
|
192
|
+
async answerQuestion(response) {
|
|
193
|
+
const pending = this.pendingQuestions.get(response.id);
|
|
194
|
+
if (!pending) {
|
|
195
|
+
throw Object.assign(new Error("question request not found"), {
|
|
196
|
+
statusCode: 404,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
this.pendingQuestions.delete(response.id);
|
|
200
|
+
if (response.rejected) {
|
|
201
|
+
this.pushEvent("question.rejected", {
|
|
202
|
+
id: response.id,
|
|
203
|
+
requestID: response.id,
|
|
204
|
+
});
|
|
205
|
+
pending.resolve(buildQuestionResponse(pending.method, pending.params, []));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
this.pushEvent("question.replied", {
|
|
209
|
+
id: response.id,
|
|
210
|
+
requestID: response.id,
|
|
211
|
+
});
|
|
212
|
+
pending.resolve(buildQuestionResponse(pending.method, pending.params, response.answers));
|
|
213
|
+
}
|
|
214
|
+
async abort() {
|
|
215
|
+
if (this.ownerValue !== "web")
|
|
216
|
+
return;
|
|
217
|
+
const threadId = this.activeSessionId ?? this.resumeSessionId;
|
|
218
|
+
const turnId = this.activeTurnId;
|
|
219
|
+
this.finishWebPrompts(new Error("Codex run aborted"));
|
|
220
|
+
if (threadId && turnId) {
|
|
221
|
+
try {
|
|
222
|
+
await this.appServer?.request("turn/interrupt", { threadId, turnId });
|
|
223
|
+
}
|
|
224
|
+
catch { }
|
|
225
|
+
}
|
|
226
|
+
this.activeTurnId = null;
|
|
227
|
+
this.setThinking(false);
|
|
228
|
+
this.setBusy(false);
|
|
229
|
+
this.pushEvent("error", { message: "Codex run aborted." });
|
|
230
|
+
}
|
|
231
|
+
stop() {
|
|
232
|
+
this.finishWebPrompts(new Error("Codex provider stopped"));
|
|
233
|
+
this.rejectPendingRequests(new Error("Codex provider stopped"));
|
|
234
|
+
this.detachReclaimInput();
|
|
235
|
+
this.appServer?.stop();
|
|
236
|
+
this.appServer = null;
|
|
237
|
+
try {
|
|
238
|
+
this.local?.kill("SIGTERM");
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
this.local = null;
|
|
242
|
+
this.ownerValue = "stopped";
|
|
243
|
+
this.setThinking(false);
|
|
244
|
+
this.setBusy(false);
|
|
245
|
+
this.emitState();
|
|
246
|
+
}
|
|
247
|
+
startLocal(extraArgs = []) {
|
|
248
|
+
this.detachReclaimInput();
|
|
249
|
+
this.finishWebPrompts(new Error("local mode active"));
|
|
250
|
+
this.appServer?.stop();
|
|
251
|
+
this.appServer = null;
|
|
252
|
+
const child = spawn(this.opts.bin, [...extraArgs, ...this.opts.args], {
|
|
253
|
+
cwd: this.opts.cwd,
|
|
254
|
+
env: this.buildEnv(),
|
|
255
|
+
stdio: "inherit",
|
|
256
|
+
windowsHide: true,
|
|
257
|
+
});
|
|
258
|
+
this.local = child;
|
|
259
|
+
this.ownerValue = "local";
|
|
260
|
+
this.emitState();
|
|
261
|
+
child.on("error", (error) => {
|
|
262
|
+
this.pushEvent("error", { message: error.message });
|
|
263
|
+
process.stderr.write(`\n[adit cloud codex] failed to start Codex CLI: ${error.message}\n`);
|
|
264
|
+
});
|
|
265
|
+
child.on("exit", (code, signal) => {
|
|
266
|
+
this.setThinking(false);
|
|
267
|
+
this.setBusy(false);
|
|
268
|
+
if (this.suppressNextLocalExit) {
|
|
269
|
+
this.suppressNextLocalExit = false;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (this.local === child) {
|
|
273
|
+
this.local = null;
|
|
274
|
+
this.ownerValue = "stopped";
|
|
275
|
+
this.emitState();
|
|
276
|
+
this.emit("exit", { code, signal });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async drainPromptQueue() {
|
|
281
|
+
if (this.promptActive || this.busyValue || this.ownerValue !== "web")
|
|
282
|
+
return;
|
|
283
|
+
const item = this.promptQueue.shift();
|
|
284
|
+
if (!item)
|
|
285
|
+
return;
|
|
286
|
+
this.promptActive = true;
|
|
287
|
+
try {
|
|
288
|
+
await this.ensureAppServer();
|
|
289
|
+
const threadId = await this.ensureThreadForPrompt(item.pendingSessionId, item.mode);
|
|
290
|
+
this.pushEvent("message", {
|
|
291
|
+
role: "user",
|
|
292
|
+
sessionId: threadId,
|
|
293
|
+
text: item.message,
|
|
294
|
+
createdAt: Date.now(),
|
|
295
|
+
});
|
|
296
|
+
const result = asRecord(await this.appServer?.request("turn/start", {
|
|
297
|
+
threadId,
|
|
298
|
+
input: [
|
|
299
|
+
{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: promptInputForCodexMode(item.message, item.mode),
|
|
302
|
+
text_elements: [],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
cwd: this.opts.cwd,
|
|
306
|
+
...codexTurnModeOverrides(item.mode, this.opts.cwd),
|
|
307
|
+
}));
|
|
308
|
+
const turn = asRecord(result?.turn);
|
|
309
|
+
this.activeTurnId = readString(turn?.id) ?? this.activeTurnId;
|
|
310
|
+
this.setBusy(true);
|
|
311
|
+
this.setThinking(true);
|
|
312
|
+
item.resolve();
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
item.reject(error instanceof Error ? error : new Error(String(error)));
|
|
316
|
+
this.pushEvent("error", {
|
|
317
|
+
message: error instanceof Error ? error.message : String(error),
|
|
318
|
+
createdAt: Date.now(),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
this.promptActive = false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async ensureThreadForPrompt(pendingSessionId, mode) {
|
|
326
|
+
if (pendingSessionId) {
|
|
327
|
+
const threadId = await this.startThread(mode);
|
|
328
|
+
this.bindPendingSession(pendingSessionId, threadId);
|
|
329
|
+
return threadId;
|
|
330
|
+
}
|
|
331
|
+
const existing = this.activeSessionId ?? this.resumeSessionId;
|
|
332
|
+
if (existing) {
|
|
333
|
+
if (!this.loadedThreadIds.has(existing)) {
|
|
334
|
+
await this.resumeThread(existing, mode);
|
|
335
|
+
}
|
|
336
|
+
return existing;
|
|
337
|
+
}
|
|
338
|
+
return this.startThread(mode);
|
|
339
|
+
}
|
|
340
|
+
async ensureAppServer() {
|
|
341
|
+
if (this.appServer?.isRunning)
|
|
342
|
+
return;
|
|
343
|
+
const client = new CodexAppServerClient({
|
|
344
|
+
bin: this.opts.bin,
|
|
345
|
+
cwd: this.opts.cwd,
|
|
346
|
+
env: this.buildEnv(),
|
|
347
|
+
onNotification: (message) => this.handleAppNotification(message),
|
|
348
|
+
onServerRequest: (message) => this.handleAppServerRequest(message),
|
|
349
|
+
onError: (error) => {
|
|
350
|
+
this.pushEvent("error", { message: error.message, createdAt: Date.now() });
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
this.appServer = client;
|
|
354
|
+
await client.start();
|
|
355
|
+
}
|
|
356
|
+
async startThread(mode) {
|
|
357
|
+
await this.ensureAppServer();
|
|
358
|
+
const result = asRecord(await this.appServer?.request("thread/start", {
|
|
359
|
+
cwd: this.opts.cwd,
|
|
360
|
+
...codexThreadModeOverrides(mode),
|
|
361
|
+
experimentalRawEvents: false,
|
|
362
|
+
persistExtendedHistory: true,
|
|
363
|
+
}));
|
|
364
|
+
const thread = asRecord(result?.thread);
|
|
365
|
+
const threadId = readString(thread?.id);
|
|
366
|
+
if (!threadId)
|
|
367
|
+
throw new Error("Codex app-server did not return a thread id");
|
|
368
|
+
this.noteThread(thread, result);
|
|
369
|
+
return threadId;
|
|
370
|
+
}
|
|
371
|
+
async resumeThread(threadId, mode = "build") {
|
|
372
|
+
await this.ensureAppServer();
|
|
373
|
+
const result = asRecord(await this.appServer?.request("thread/resume", {
|
|
374
|
+
threadId,
|
|
375
|
+
cwd: this.opts.cwd,
|
|
376
|
+
...codexThreadModeOverrides(mode),
|
|
377
|
+
persistExtendedHistory: true,
|
|
378
|
+
}));
|
|
379
|
+
const thread = asRecord(result?.thread);
|
|
380
|
+
const resumedId = readString(thread?.id) ?? threadId;
|
|
381
|
+
this.noteThread({ ...(thread ?? {}), id: resumedId }, result);
|
|
382
|
+
}
|
|
383
|
+
handleAppNotification(message) {
|
|
384
|
+
const method = message.method;
|
|
385
|
+
const params = asRecord(message.params) ?? {};
|
|
386
|
+
if (method === "error") {
|
|
387
|
+
this.pushEvent("error", {
|
|
388
|
+
message: readString(params.message) ?? "Codex app-server error",
|
|
389
|
+
createdAt: Date.now(),
|
|
390
|
+
});
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (method === "thread/started") {
|
|
394
|
+
this.noteThread(asRecord(params.thread), null);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (method === "thread/status/changed") {
|
|
398
|
+
const status = asRecord(params.status);
|
|
399
|
+
const active = status?.type === "active";
|
|
400
|
+
this.setBusy(active);
|
|
401
|
+
this.setThinking(active);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (method === "turn/started") {
|
|
405
|
+
this.activeTurnId = readString(asRecord(params.turn)?.id) ?? this.activeTurnId;
|
|
406
|
+
this.setBusy(true);
|
|
407
|
+
this.setThinking(true);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (method === "turn/completed") {
|
|
411
|
+
const turn = asRecord(params.turn);
|
|
412
|
+
const status = readString(turn?.status);
|
|
413
|
+
if (status === "failed") {
|
|
414
|
+
const error = asRecord(turn?.error);
|
|
415
|
+
this.pushEvent("error", {
|
|
416
|
+
message: readString(error?.message) ?? "Codex turn failed",
|
|
417
|
+
createdAt: Date.now(),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
this.activeTurnId = null;
|
|
421
|
+
this.setThinking(false);
|
|
422
|
+
this.setBusy(false);
|
|
423
|
+
void this.drainPromptQueue();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (method === "item/started") {
|
|
427
|
+
this.emitThreadItem(asRecord(params.item), {
|
|
428
|
+
threadId: readString(params.threadId),
|
|
429
|
+
running: true,
|
|
430
|
+
});
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (method === "item/completed") {
|
|
434
|
+
this.emitThreadItem(asRecord(params.item), {
|
|
435
|
+
threadId: readString(params.threadId),
|
|
436
|
+
running: false,
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (method === "item/agentMessage/delta" || method === "item/plan/delta") {
|
|
441
|
+
const text = readString(params.delta);
|
|
442
|
+
const sessionId = readString(params.threadId);
|
|
443
|
+
const messageId = readString(params.itemId);
|
|
444
|
+
if (text && sessionId && messageId) {
|
|
445
|
+
this.lastAssistantMessageBySession.set(sessionId, messageId);
|
|
446
|
+
this.pushEvent("assistant-delta", {
|
|
447
|
+
sessionId,
|
|
448
|
+
messageId,
|
|
449
|
+
text,
|
|
450
|
+
modelId: this.activeModelId ?? undefined,
|
|
451
|
+
createdAt: Date.now(),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (method === "item/reasoning/textDelta" || method === "item/reasoning/summaryTextDelta") {
|
|
457
|
+
const text = readString(params.delta);
|
|
458
|
+
const sessionId = readString(params.threadId);
|
|
459
|
+
const messageId = readString(params.itemId);
|
|
460
|
+
if (text && sessionId && messageId) {
|
|
461
|
+
this.pushEvent("reasoning", {
|
|
462
|
+
sessionId,
|
|
463
|
+
messageId,
|
|
464
|
+
text,
|
|
465
|
+
createdAt: Date.now(),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (method === "thread/tokenUsage/updated") {
|
|
471
|
+
const sessionId = readString(params.threadId);
|
|
472
|
+
const usage = normalizeCodexUsage(asRecord(asRecord(params.tokenUsage)?.last));
|
|
473
|
+
if (sessionId && usage) {
|
|
474
|
+
this.pushEvent("usage", {
|
|
475
|
+
sessionId,
|
|
476
|
+
messageId: this.lastAssistantMessageBySession.get(sessionId),
|
|
477
|
+
usage,
|
|
478
|
+
createdAt: Date.now(),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
handleAppServerRequest(message) {
|
|
484
|
+
if (message.id === undefined || !message.method) {
|
|
485
|
+
return Promise.resolve(null);
|
|
486
|
+
}
|
|
487
|
+
const id = String(message.id);
|
|
488
|
+
const params = asRecord(message.params) ?? {};
|
|
489
|
+
const sessionId = readString(params.threadId) ?? this.activeSessionId ?? this.resumeSessionId;
|
|
490
|
+
if (message.method === "item/tool/requestUserInput" ||
|
|
491
|
+
message.method === "mcpServer/elicitation/request") {
|
|
492
|
+
return new Promise((resolve, reject) => {
|
|
493
|
+
this.pendingQuestions.set(id, {
|
|
494
|
+
id,
|
|
495
|
+
method: message.method ?? "",
|
|
496
|
+
params,
|
|
497
|
+
resolve,
|
|
498
|
+
reject,
|
|
499
|
+
});
|
|
500
|
+
this.pushEvent("question.asked", {
|
|
501
|
+
id,
|
|
502
|
+
sessionId,
|
|
503
|
+
questions: normalizeCodexQuestions(message.method ?? "", params),
|
|
504
|
+
createdAt: Date.now(),
|
|
505
|
+
});
|
|
506
|
+
this.emitState();
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
if (message.method === "item/commandExecution/requestApproval" ||
|
|
510
|
+
message.method === "item/fileChange/requestApproval" ||
|
|
511
|
+
message.method === "item/permissions/requestApproval" ||
|
|
512
|
+
message.method === "applyPatchApproval" ||
|
|
513
|
+
message.method === "execCommandApproval") {
|
|
514
|
+
return new Promise((resolve, reject) => {
|
|
515
|
+
const toolName = approvalToolName(message.method ?? "", params);
|
|
516
|
+
const request = {
|
|
517
|
+
id,
|
|
518
|
+
toolName,
|
|
519
|
+
input: params,
|
|
520
|
+
createdAt: Date.now(),
|
|
521
|
+
};
|
|
522
|
+
this.pendingPermissions.set(id, {
|
|
523
|
+
request,
|
|
524
|
+
method: message.method ?? "",
|
|
525
|
+
params,
|
|
526
|
+
resolve,
|
|
527
|
+
reject,
|
|
528
|
+
});
|
|
529
|
+
this.pushEvent("permission", {
|
|
530
|
+
id,
|
|
531
|
+
toolName,
|
|
532
|
+
input: params,
|
|
533
|
+
createdAt: request.createdAt,
|
|
534
|
+
sessionId,
|
|
535
|
+
});
|
|
536
|
+
this.emitState();
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return Promise.reject(new Error(`Unsupported Codex app-server request: ${message.method}`));
|
|
540
|
+
}
|
|
541
|
+
noteThread(thread, result) {
|
|
542
|
+
const threadId = readString(thread?.id);
|
|
543
|
+
if (!threadId)
|
|
544
|
+
return;
|
|
545
|
+
this.activeSessionId = threadId;
|
|
546
|
+
this.resumeSessionId = threadId;
|
|
547
|
+
this.sdkSessionId = threadId;
|
|
548
|
+
this.loadedThreadIds.add(threadId);
|
|
549
|
+
const modelId = readString(result?.model);
|
|
550
|
+
if (modelId)
|
|
551
|
+
this.activeModelId = modelId;
|
|
552
|
+
this.emitState();
|
|
553
|
+
}
|
|
554
|
+
emitThreadItem(item, opts) {
|
|
555
|
+
if (!item)
|
|
556
|
+
return;
|
|
557
|
+
const type = readString(item.type);
|
|
558
|
+
const sessionId = opts.threadId ?? this.activeSessionId ?? this.resumeSessionId;
|
|
559
|
+
const id = readString(item.id);
|
|
560
|
+
if (!type || !sessionId || !id)
|
|
561
|
+
return;
|
|
562
|
+
const createdAt = Date.now();
|
|
563
|
+
if ((type === "agentMessage" || type === "plan") && !opts.running) {
|
|
564
|
+
const text = readString(item.text);
|
|
565
|
+
if (!text)
|
|
566
|
+
return;
|
|
567
|
+
this.lastAssistantMessageBySession.set(sessionId, id);
|
|
568
|
+
this.pushEvent("message", {
|
|
569
|
+
role: "assistant",
|
|
570
|
+
sessionId,
|
|
571
|
+
messageId: id,
|
|
572
|
+
modelId: this.activeModelId ?? undefined,
|
|
573
|
+
text,
|
|
574
|
+
createdAt,
|
|
575
|
+
});
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (type === "reasoning" && !opts.running) {
|
|
579
|
+
const content = [
|
|
580
|
+
...readStringArray(item.summary),
|
|
581
|
+
...readStringArray(item.content),
|
|
582
|
+
].join("\n");
|
|
583
|
+
if (!content)
|
|
584
|
+
return;
|
|
585
|
+
this.pushEvent("reasoning", {
|
|
586
|
+
sessionId,
|
|
587
|
+
messageId: id,
|
|
588
|
+
text: content,
|
|
589
|
+
createdAt,
|
|
590
|
+
});
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (type === "commandExecution") {
|
|
594
|
+
const command = readString(item.command) ?? "command";
|
|
595
|
+
const status = readString(item.status);
|
|
596
|
+
this.pushEvent("tool", {
|
|
597
|
+
sessionId,
|
|
598
|
+
messageId: this.lastAssistantMessageBySession.get(sessionId) ?? `codex-tools-${sessionId}`,
|
|
599
|
+
toolUseId: id,
|
|
600
|
+
toolName: "Bash",
|
|
601
|
+
input: {
|
|
602
|
+
command,
|
|
603
|
+
cwd: readString(item.cwd),
|
|
604
|
+
source: item.source,
|
|
605
|
+
commandActions: item.commandActions,
|
|
606
|
+
},
|
|
607
|
+
output: readString(item.aggregatedOutput) ?? undefined,
|
|
608
|
+
error: status === "failed" ? readString(item.aggregatedOutput) ?? "Command failed" : undefined,
|
|
609
|
+
status: opts.running || status === "inProgress"
|
|
610
|
+
? "running"
|
|
611
|
+
: status === "completed"
|
|
612
|
+
? "completed"
|
|
613
|
+
: "error",
|
|
614
|
+
createdAt,
|
|
615
|
+
});
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (type === "fileChange") {
|
|
619
|
+
const status = readString(item.status);
|
|
620
|
+
this.pushEvent("tool", {
|
|
621
|
+
sessionId,
|
|
622
|
+
messageId: this.lastAssistantMessageBySession.get(sessionId) ?? `codex-tools-${sessionId}`,
|
|
623
|
+
toolUseId: id,
|
|
624
|
+
toolName: "apply_patch",
|
|
625
|
+
input: { changes: item.changes },
|
|
626
|
+
output: status === "completed" ? safeJson(item.changes) : undefined,
|
|
627
|
+
error: status === "failed" || status === "declined" ? `File change ${status}` : undefined,
|
|
628
|
+
status: opts.running || status === "inProgress"
|
|
629
|
+
? "running"
|
|
630
|
+
: status === "completed"
|
|
631
|
+
? "completed"
|
|
632
|
+
: "error",
|
|
633
|
+
createdAt,
|
|
634
|
+
});
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (type === "mcpToolCall" || type === "dynamicToolCall" || type === "webSearch") {
|
|
638
|
+
const toolName = readString(item.tool) ?? readString(item.server) ?? type;
|
|
639
|
+
const status = readString(item.status);
|
|
640
|
+
this.pushEvent("tool", {
|
|
641
|
+
sessionId,
|
|
642
|
+
messageId: this.lastAssistantMessageBySession.get(sessionId) ?? `codex-tools-${sessionId}`,
|
|
643
|
+
toolUseId: id,
|
|
644
|
+
toolName,
|
|
645
|
+
input: asRecord(item.arguments) ?? { query: item.query },
|
|
646
|
+
output: item.result !== undefined ? safeJson(item.result) : undefined,
|
|
647
|
+
error: item.error !== undefined ? safeJson(item.error) : undefined,
|
|
648
|
+
status: opts.running || status === "inProgress" || status === "running"
|
|
649
|
+
? "running"
|
|
650
|
+
: item.error ? "error" : "completed",
|
|
651
|
+
createdAt,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
bindPendingSession(pendingSessionId, sessionId) {
|
|
656
|
+
if (!pendingSessionId || !sessionId)
|
|
657
|
+
return;
|
|
658
|
+
this.activeSessionId = sessionId;
|
|
659
|
+
this.resumeSessionId = sessionId;
|
|
660
|
+
this.sdkSessionId = sessionId;
|
|
661
|
+
if (!this.boundPendingSessionIds.has(pendingSessionId)) {
|
|
662
|
+
this.boundPendingSessionIds.add(pendingSessionId);
|
|
663
|
+
this.pushEvent("session-bound", {
|
|
664
|
+
pendingSessionId,
|
|
665
|
+
sessionId,
|
|
666
|
+
createdAt: Date.now(),
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
this.emitState();
|
|
670
|
+
}
|
|
671
|
+
finishWebPrompts(error) {
|
|
672
|
+
for (const item of this.promptQueue.splice(0)) {
|
|
673
|
+
item.reject(error);
|
|
674
|
+
}
|
|
675
|
+
this.promptActive = false;
|
|
676
|
+
}
|
|
677
|
+
rejectPendingRequests(error) {
|
|
678
|
+
for (const pending of this.pendingPermissions.values()) {
|
|
679
|
+
pending.reject(error);
|
|
680
|
+
}
|
|
681
|
+
this.pendingPermissions.clear();
|
|
682
|
+
for (const pending of this.pendingQuestions.values()) {
|
|
683
|
+
pending.reject(error);
|
|
684
|
+
}
|
|
685
|
+
this.pendingQuestions.clear();
|
|
686
|
+
this.pushEvent("permission-resolved", { id: "all", approved: false });
|
|
687
|
+
this.pushEvent("question.rejected", { id: "all" });
|
|
688
|
+
}
|
|
689
|
+
setBusy(value) {
|
|
690
|
+
if (this.busyValue === value)
|
|
691
|
+
return;
|
|
692
|
+
this.busyValue = value;
|
|
693
|
+
this.emitState();
|
|
694
|
+
}
|
|
695
|
+
setThinking(value) {
|
|
696
|
+
if (this.thinkingValue === value)
|
|
697
|
+
return;
|
|
698
|
+
this.thinkingValue = value;
|
|
699
|
+
this.emitState();
|
|
700
|
+
}
|
|
701
|
+
emitState() {
|
|
702
|
+
this.emit("state", this.state);
|
|
703
|
+
this.pushEvent("state", {
|
|
704
|
+
owner: this.ownerValue,
|
|
705
|
+
busy: this.busyValue,
|
|
706
|
+
thinking: this.thinkingValue,
|
|
707
|
+
activeSessionId: this.activeSessionId,
|
|
708
|
+
resumeSessionId: this.resumeSessionId,
|
|
709
|
+
sdkSessionId: this.sdkSessionId,
|
|
710
|
+
activeModelId: this.activeModelId,
|
|
711
|
+
createdAt: Date.now(),
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
pushEvent(type, payload) {
|
|
715
|
+
this.opts.onEvent?.({ type, payload });
|
|
716
|
+
}
|
|
717
|
+
buildEnv() {
|
|
718
|
+
return {
|
|
719
|
+
...process.env,
|
|
720
|
+
TERM: process.env.TERM || "xterm-256color",
|
|
721
|
+
COLORTERM: process.env.COLORTERM || "truecolor",
|
|
722
|
+
FORCE_COLOR: process.env.FORCE_COLOR || "3",
|
|
723
|
+
DISABLE_AUTOUPDATER: "1",
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
attachReclaimInput() {
|
|
727
|
+
if (this.reclaimAttached || !process.stdin.isTTY)
|
|
728
|
+
return;
|
|
729
|
+
this.reclaimAttached = true;
|
|
730
|
+
this.reclaimBuffer = "";
|
|
731
|
+
try {
|
|
732
|
+
restoreTerminalForCodexReclaim();
|
|
733
|
+
process.stdin.setEncoding("utf8");
|
|
734
|
+
process.stdin.resume();
|
|
735
|
+
process.stdin.on("data", this.onReclaimInput);
|
|
736
|
+
}
|
|
737
|
+
catch {
|
|
738
|
+
this.reclaimAttached = false;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
detachReclaimInput() {
|
|
742
|
+
if (!this.reclaimAttached)
|
|
743
|
+
return;
|
|
744
|
+
this.reclaimAttached = false;
|
|
745
|
+
process.stdin.off("data", this.onReclaimInput);
|
|
746
|
+
this.reclaimBuffer = "";
|
|
747
|
+
try {
|
|
748
|
+
process.stdin.pause();
|
|
749
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
750
|
+
process.stdin.setRawMode(false);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
catch { }
|
|
754
|
+
}
|
|
755
|
+
onReclaimInput = (chunk) => {
|
|
756
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
757
|
+
if (text.includes("\u0003")) {
|
|
758
|
+
this.stop();
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
this.reclaimBuffer = applyReclaimText(this.reclaimBuffer, normalizeCodexReclaimInput(text));
|
|
762
|
+
if (this.reclaimBuffer.length > 200) {
|
|
763
|
+
this.reclaimBuffer = this.reclaimBuffer.slice(-200);
|
|
764
|
+
}
|
|
765
|
+
if (this.reclaimBuffer.includes(RECLAIM_COMMAND)) {
|
|
766
|
+
this.reclaimBuffer = "";
|
|
767
|
+
void this.releaseToLocal();
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (text.includes("\n") || text.includes("\r")) {
|
|
771
|
+
process.stderr.write(`[adit cloud codex] Web owns this session. Type ${RECLAIM_COMMAND} to reclaim.\n`);
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function restoreTerminalForCodexReclaim() {
|
|
776
|
+
try {
|
|
777
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
778
|
+
process.stdin.setRawMode(false);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
catch { }
|
|
782
|
+
try {
|
|
783
|
+
if (process.stderr.isTTY) {
|
|
784
|
+
process.stderr.write(TERMINAL_RECLAIM_RESET);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch { }
|
|
788
|
+
}
|
|
789
|
+
export function normalizeCodexReclaimInput(text) {
|
|
790
|
+
return text
|
|
791
|
+
.replace(/\x1b\[(\d+)(?:;[0-9:]+)?u/g, (_match, rawCode) => decodeCsiUCode(Number(rawCode)))
|
|
792
|
+
.replace(/\x1b\[(?:I|O)/g, "")
|
|
793
|
+
.replace(/\x1b\[[?=>]?[0-9;:]*[A-Za-z~]/g, "");
|
|
794
|
+
}
|
|
795
|
+
function decodeCsiUCode(code) {
|
|
796
|
+
if (code === 13)
|
|
797
|
+
return "\n";
|
|
798
|
+
if (code === 9)
|
|
799
|
+
return "\t";
|
|
800
|
+
if (code === 8 || code === 127)
|
|
801
|
+
return "\b";
|
|
802
|
+
if (code < 32 || !Number.isFinite(code))
|
|
803
|
+
return "";
|
|
804
|
+
try {
|
|
805
|
+
return String.fromCodePoint(code);
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
return "";
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function applyReclaimText(buffer, text) {
|
|
812
|
+
let next = buffer;
|
|
813
|
+
for (const char of text) {
|
|
814
|
+
if (char === "\b" || char === "\x7f") {
|
|
815
|
+
next = next.slice(0, -1);
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
next += char;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return next;
|
|
822
|
+
}
|
|
823
|
+
export function promptInputForCodexMode(prompt, mode) {
|
|
824
|
+
if (mode !== "plan")
|
|
825
|
+
return prompt;
|
|
826
|
+
return `${CODEX_PLAN_MODE_INSTRUCTION}\n\nUser request:\n${prompt}`;
|
|
827
|
+
}
|
|
828
|
+
export function codexThreadModeOverrides(mode) {
|
|
829
|
+
return {
|
|
830
|
+
approvalPolicy: mode === "plan" ? "never" : "on-request",
|
|
831
|
+
approvalsReviewer: "user",
|
|
832
|
+
sandbox: mode === "plan" ? "read-only" : "workspace-write",
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
export function codexTurnModeOverrides(mode, cwd) {
|
|
836
|
+
if (mode === "plan") {
|
|
837
|
+
return {
|
|
838
|
+
approvalPolicy: "never",
|
|
839
|
+
approvalsReviewer: "user",
|
|
840
|
+
sandboxPolicy: {
|
|
841
|
+
type: "readOnly",
|
|
842
|
+
networkAccess: false,
|
|
843
|
+
},
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
return {
|
|
847
|
+
approvalPolicy: "on-request",
|
|
848
|
+
approvalsReviewer: "user",
|
|
849
|
+
sandboxPolicy: {
|
|
850
|
+
type: "workspaceWrite",
|
|
851
|
+
writableRoots: [cwd],
|
|
852
|
+
networkAccess: false,
|
|
853
|
+
excludeTmpdirEnvVar: false,
|
|
854
|
+
excludeSlashTmp: false,
|
|
855
|
+
},
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function readString(value) {
|
|
859
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
860
|
+
}
|
|
861
|
+
function asRecord(value) {
|
|
862
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
863
|
+
? value
|
|
864
|
+
: null;
|
|
865
|
+
}
|
|
866
|
+
function readStringArray(value) {
|
|
867
|
+
return Array.isArray(value)
|
|
868
|
+
? value.filter((item) => typeof item === "string" && item.length > 0)
|
|
869
|
+
: [];
|
|
870
|
+
}
|
|
871
|
+
function safeJson(value) {
|
|
872
|
+
if (typeof value === "string")
|
|
873
|
+
return value;
|
|
874
|
+
try {
|
|
875
|
+
return JSON.stringify(value ?? {}, null, 2);
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
return String(value);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function normalizeCodexUsage(value) {
|
|
882
|
+
if (!value)
|
|
883
|
+
return undefined;
|
|
884
|
+
const usage = {
|
|
885
|
+
input_tokens: readNumber(value.inputTokens) ?? 0,
|
|
886
|
+
output_tokens: readNumber(value.outputTokens) ?? 0,
|
|
887
|
+
reasoning_tokens: readNumber(value.reasoningOutputTokens) ?? 0,
|
|
888
|
+
cache_read_input_tokens: readNumber(value.cachedInputTokens) ?? 0,
|
|
889
|
+
};
|
|
890
|
+
return Object.values(usage).some((item) => item > 0) ? usage : undefined;
|
|
891
|
+
}
|
|
892
|
+
function readNumber(value) {
|
|
893
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
894
|
+
}
|
|
895
|
+
function approvalToolName(method, params) {
|
|
896
|
+
if (method === "item/commandExecution/requestApproval" || method === "execCommandApproval") {
|
|
897
|
+
return "Bash";
|
|
898
|
+
}
|
|
899
|
+
if (method === "item/fileChange/requestApproval" || method === "applyPatchApproval") {
|
|
900
|
+
return "apply_patch";
|
|
901
|
+
}
|
|
902
|
+
if (method === "item/permissions/requestApproval") {
|
|
903
|
+
return "CodexPermissions";
|
|
904
|
+
}
|
|
905
|
+
return readString(params.toolName) ?? "Codex";
|
|
906
|
+
}
|
|
907
|
+
function buildApprovalResponse(method, params, approved) {
|
|
908
|
+
if (method === "item/commandExecution/requestApproval") {
|
|
909
|
+
return { decision: approved ? "accept" : "decline" };
|
|
910
|
+
}
|
|
911
|
+
if (method === "item/fileChange/requestApproval") {
|
|
912
|
+
return { decision: approved ? "accept" : "decline" };
|
|
913
|
+
}
|
|
914
|
+
if (method === "item/permissions/requestApproval") {
|
|
915
|
+
return {
|
|
916
|
+
permissions: approved ? asRecord(params.permissions) ?? {} : {},
|
|
917
|
+
scope: "turn",
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
if (method === "applyPatchApproval" || method === "execCommandApproval") {
|
|
921
|
+
return { decision: approved ? "approved" : "denied" };
|
|
922
|
+
}
|
|
923
|
+
return { decision: approved ? "accept" : "decline" };
|
|
924
|
+
}
|
|
925
|
+
function normalizeCodexQuestions(method, params) {
|
|
926
|
+
if (method === "mcpServer/elicitation/request") {
|
|
927
|
+
return [{
|
|
928
|
+
question: readString(params.message) ?? readString(params.title) ?? "Codex needs input.",
|
|
929
|
+
header: readString(params.title) ?? "Question",
|
|
930
|
+
options: [],
|
|
931
|
+
multiple: false,
|
|
932
|
+
custom: true,
|
|
933
|
+
}];
|
|
934
|
+
}
|
|
935
|
+
const rawQuestions = Array.isArray(params.questions) ? params.questions : [];
|
|
936
|
+
return rawQuestions.map((item) => {
|
|
937
|
+
const question = asRecord(item) ?? {};
|
|
938
|
+
const options = Array.isArray(question.options)
|
|
939
|
+
? question.options.map((option) => {
|
|
940
|
+
const record = asRecord(option) ?? {};
|
|
941
|
+
const label = readString(record.label) ?? readString(record.value) ?? "";
|
|
942
|
+
return {
|
|
943
|
+
label,
|
|
944
|
+
description: readString(record.description) ?? "",
|
|
945
|
+
};
|
|
946
|
+
}).filter((option) => option.label)
|
|
947
|
+
: [];
|
|
948
|
+
return {
|
|
949
|
+
question: readString(question.question) ?? "Codex needs input.",
|
|
950
|
+
header: readString(question.header) ?? "Question",
|
|
951
|
+
options,
|
|
952
|
+
multiple: false,
|
|
953
|
+
custom: question.isOther !== false,
|
|
954
|
+
};
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
function buildQuestionResponse(method, params, answers) {
|
|
958
|
+
if (method === "mcpServer/elicitation/request") {
|
|
959
|
+
return {
|
|
960
|
+
action: answers.length > 0 ? "accept" : "decline",
|
|
961
|
+
content: answers.length > 0 ? { answer: answers.flat().join("\n") } : null,
|
|
962
|
+
_meta: null,
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
const rawQuestions = Array.isArray(params.questions) ? params.questions : [];
|
|
966
|
+
const mapped = {};
|
|
967
|
+
rawQuestions.forEach((item, index) => {
|
|
968
|
+
const question = asRecord(item) ?? {};
|
|
969
|
+
const id = readString(question.id) ?? `question-${index}`;
|
|
970
|
+
mapped[id] = { answers: answers[index] ?? [] };
|
|
971
|
+
});
|
|
972
|
+
return { answers: mapped };
|
|
973
|
+
}
|
|
974
|
+
//# sourceMappingURL=codex-cli-provider.js.map
|