@yuandc/aica 0.1.1 → 0.1.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/acp/agent.js +1 -54
- package/dist/acp/client/acp-client.js +1 -102
- package/dist/acp/client/acp-content.js +1 -13
- package/dist/acp/client/acp-events.js +1 -106
- package/dist/acp/client/acp-process.js +1 -34
- package/dist/acp/client/acp-runtime-pool.js +1 -248
- package/dist/acp/client/context-usage.js +1 -29
- package/dist/acp/client/json-rpc.js +4 -128
- package/dist/acp/provider-types.js +0 -1
- package/dist/acp/providers/codex/codex-process.js +1 -51
- package/dist/acp/providers/codex/events.js +28 -1473
- package/dist/acp/providers/codex/permissions.js +1 -49
- package/dist/acp/providers/codex/provider.js +1 -376
- package/dist/acp/providers/codex-acp/adapter.js +5 -947
- package/dist/acp/providers/codex-acp/context-maintenance.js +5 -148
- package/dist/acp/providers/codex-acp/launch.js +1 -35
- package/dist/acp/providers/codex-acp/provider.js +1 -486
- package/dist/acp/providers/mimo/provider.js +5 -448
- package/dist/acp/providers/opencode/provider.js +4 -489
- package/dist/acp/providers/registry.js +1 -23
- package/dist/acp/standard-events.js +1 -167
- package/dist/commands/start.js +1 -137
- package/dist/commands/worker-auth.js +4 -100
- package/dist/commands/worker-project.js +1 -57
- package/dist/core/aca-config.js +1 -74
- package/dist/core/aca-server-client.js +1 -57
- package/dist/core/acp-event-coalescer.js +1 -108
- package/dist/core/acp-event-upload-filter.js +1 -16
- package/dist/core/acp-orphan-cleanup.js +1 -91
- package/dist/core/affected-files.js +2 -268
- package/dist/core/auth.js +1 -36
- package/dist/core/file-transfer-worker.js +1 -169
- package/dist/core/fs.js +2 -28
- package/dist/core/heartbeat.js +3 -578
- package/dist/core/job-permission-policy.js +1 -42
- package/dist/core/job-worker.js +6 -749
- package/dist/core/logger.js +3 -42
- package/dist/core/long-poll-worker.js +1 -26
- package/dist/core/machine-filesystem-worker.js +3 -352
- package/dist/core/paths.js +1 -26
- package/dist/core/process-identity.js +1 -34
- package/dist/core/process.js +2 -33
- package/dist/core/provider-health.js +1 -54
- package/dist/core/runtime-options.js +1 -38
- package/dist/core/worktree.js +1 -95
- package/dist/worker-cli.js +1 -28
- package/dist/worker-single-cli.js +1 -16
- package/package.json +1 -1
|
@@ -1,49 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const options = approvalOptionsForMethod(method);
|
|
3
|
-
if (callback) {
|
|
4
|
-
const response = await callback({
|
|
5
|
-
requestId: `perm-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
6
|
-
params,
|
|
7
|
-
options
|
|
8
|
-
});
|
|
9
|
-
return codexApprovalResponse(method, response.outcome.outcome === "selected");
|
|
10
|
-
}
|
|
11
|
-
return codexApprovalResponse(method, false);
|
|
12
|
-
}
|
|
13
|
-
export function isCodexApprovalRequest(method) {
|
|
14
|
-
return method === "item/commandExecution/requestApproval"
|
|
15
|
-
|| method === "item/fileChange/requestApproval"
|
|
16
|
-
|| method === "item/permissions/requestApproval"
|
|
17
|
-
|| method === "execCommandApproval"
|
|
18
|
-
|| method === "applyPatchApproval";
|
|
19
|
-
}
|
|
20
|
-
function approvalOptionsForMethod(method) {
|
|
21
|
-
if (method === "item/permissions/requestApproval") {
|
|
22
|
-
return [
|
|
23
|
-
{ optionId: "allow", kind: "allow", label: "允许" },
|
|
24
|
-
{ optionId: "deny", kind: "reject", label: "拒绝" }
|
|
25
|
-
];
|
|
26
|
-
}
|
|
27
|
-
return [
|
|
28
|
-
{ optionId: "accept", kind: "allow", label: "允许" },
|
|
29
|
-
{ optionId: "decline", kind: "reject", label: "拒绝" }
|
|
30
|
-
];
|
|
31
|
-
}
|
|
32
|
-
function codexApprovalResponse(method, allowed) {
|
|
33
|
-
switch (method) {
|
|
34
|
-
case "item/commandExecution/requestApproval":
|
|
35
|
-
return { decision: allowed ? "accept" : "decline" };
|
|
36
|
-
case "item/fileChange/requestApproval":
|
|
37
|
-
return { decision: allowed ? "accept" : "decline" };
|
|
38
|
-
case "item/permissions/requestApproval":
|
|
39
|
-
return allowed
|
|
40
|
-
? { permissions: {}, scope: "turn" }
|
|
41
|
-
: { permissions: {}, scope: "turn", strictAutoReview: true };
|
|
42
|
-
case "execCommandApproval":
|
|
43
|
-
return { decision: allowed ? "approved" : "denied" };
|
|
44
|
-
case "applyPatchApproval":
|
|
45
|
-
return { decision: allowed ? "approved" : "denied" };
|
|
46
|
-
default:
|
|
47
|
-
return {};
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
async function s(e,r,n){const t=i(e);if(n){const p=await n({requestId:`perm-${Date.now()}-${Math.random().toString(16).slice(2)}`,params:r,options:t});return o(e,p.outcome.outcome==="selected")}return o(e,!1)}function a(e){return e==="item/commandExecution/requestApproval"||e==="item/fileChange/requestApproval"||e==="item/permissions/requestApproval"||e==="execCommandApproval"||e==="applyPatchApproval"}function i(e){return e==="item/permissions/requestApproval"?[{optionId:"allow",kind:"allow",label:"允许"},{optionId:"deny",kind:"reject",label:"拒绝"}]:[{optionId:"accept",kind:"allow",label:"允许"},{optionId:"decline",kind:"reject",label:"拒绝"}]}function o(e,r){switch(e){case"item/commandExecution/requestApproval":return{decision:r?"accept":"decline"};case"item/fileChange/requestApproval":return{decision:r?"accept":"decline"};case"item/permissions/requestApproval":return r?{permissions:{},scope:"turn"}:{permissions:{},scope:"turn",strictAutoReview:!0};case"execCommandApproval":return{decision:r?"approved":"denied"};case"applyPatchApproval":return{decision:r?"approved":"denied"};default:return{}}}export{a as isCodexApprovalRequest,s as resolveCodexApprovalRequest};
|
|
@@ -1,376 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { JsonLineRpcClient } from "../../client/json-rpc.js";
|
|
3
|
-
import { startCodexAppServer } from "./codex-process.js";
|
|
4
|
-
import { extractAssistantTextFromTurn, mapCodexNotification } from "./events.js";
|
|
5
|
-
import { isCodexApprovalRequest, resolveCodexApprovalRequest } from "./permissions.js";
|
|
6
|
-
const CANCEL_INTERRUPT_TIMEOUT_MS = 1_500;
|
|
7
|
-
export const nativeCodexCapabilities = {
|
|
8
|
-
sessionResume: true,
|
|
9
|
-
imageInput: true,
|
|
10
|
-
fileAttachment: true,
|
|
11
|
-
filesystem: true,
|
|
12
|
-
terminal: true,
|
|
13
|
-
permissionRequest: true,
|
|
14
|
-
configOptions: true,
|
|
15
|
-
usage: true,
|
|
16
|
-
contextUsage: true,
|
|
17
|
-
contextCompaction: true,
|
|
18
|
-
plan: true,
|
|
19
|
-
diff: true
|
|
20
|
-
};
|
|
21
|
-
export class NativeCodexProvider {
|
|
22
|
-
id = "native-codex";
|
|
23
|
-
name = "Native Codex";
|
|
24
|
-
capabilities = nativeCodexCapabilities;
|
|
25
|
-
async createSession(input) {
|
|
26
|
-
return new NativeCodexSession(input.cwd, input.providerSessionId ?? null, input.timeoutMs);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
class NativeCodexSession {
|
|
30
|
-
cwd;
|
|
31
|
-
providerSessionId;
|
|
32
|
-
defaultTimeoutMs;
|
|
33
|
-
started;
|
|
34
|
-
rpc;
|
|
35
|
-
initialized = false;
|
|
36
|
-
content = "";
|
|
37
|
-
promptActive = false;
|
|
38
|
-
updates = [];
|
|
39
|
-
statusTimeline = [];
|
|
40
|
-
statusCallback = null;
|
|
41
|
-
updateCallback = null;
|
|
42
|
-
permissionCallback = null;
|
|
43
|
-
abortHandler = null;
|
|
44
|
-
completedTurns = new Map();
|
|
45
|
-
pendingTurnCompletions = new Map();
|
|
46
|
-
agentMessagePhases = new Map();
|
|
47
|
-
constructor(cwd, providerSessionId, defaultTimeoutMs = readAcpPromptTimeoutMs()) {
|
|
48
|
-
this.cwd = cwd;
|
|
49
|
-
this.providerSessionId = providerSessionId;
|
|
50
|
-
this.defaultTimeoutMs = defaultTimeoutMs;
|
|
51
|
-
this.started = startCodexAppServer(cwd);
|
|
52
|
-
this.rpc = new JsonLineRpcClient(this.started.child, {
|
|
53
|
-
onNotification: (method, params) => this.handleNotification(method, params),
|
|
54
|
-
onRequest: (method, params) => this.handleReverseRequest(method, params)
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
async sendPrompt(input) {
|
|
58
|
-
const startedAt = Date.now();
|
|
59
|
-
const effectiveConfig = codexAccessConfigFromMode(input.mode);
|
|
60
|
-
this.statusCallback = input.onStatus ?? null;
|
|
61
|
-
this.updateCallback = input.onUpdate ?? null;
|
|
62
|
-
this.permissionCallback = input.onPermissionRequest ?? null;
|
|
63
|
-
this.attachAbortSignal(input.signal);
|
|
64
|
-
this.emitStatus("initializing", "Codex 初始化中", "initialize");
|
|
65
|
-
await this.initialize();
|
|
66
|
-
const threadId = await this.establishThread(input);
|
|
67
|
-
this.emitStatus("thinking", "思考中");
|
|
68
|
-
this.content = "";
|
|
69
|
-
this.updates.length = 0;
|
|
70
|
-
this.agentMessagePhases.clear();
|
|
71
|
-
this.promptActive = true;
|
|
72
|
-
let promptResponse;
|
|
73
|
-
let turn;
|
|
74
|
-
try {
|
|
75
|
-
promptResponse = await this.rpc.request("turn/start", {
|
|
76
|
-
threadId,
|
|
77
|
-
clientUserMessageId: `aca-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
78
|
-
input: promptBlocksToCodexInput(input.prompt, input.promptBlocks),
|
|
79
|
-
cwd: input.cwd,
|
|
80
|
-
...(input.model ? { model: input.model } : {}),
|
|
81
|
-
...turnConfigFromOptions(input.configOptionValues)
|
|
82
|
-
}, input.timeoutMs ?? this.defaultTimeoutMs);
|
|
83
|
-
const turnId = String(promptResponse?.turn?.id ?? "");
|
|
84
|
-
turn = turnId
|
|
85
|
-
? await this.waitForTurnCompleted(turnId, input.timeoutMs ?? this.defaultTimeoutMs)
|
|
86
|
-
: promptResponse?.turn;
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
this.promptActive = false;
|
|
90
|
-
}
|
|
91
|
-
const finalContent = this.content.trim() || extractAssistantTextFromTurn(turn);
|
|
92
|
-
this.emitStatus("completed", "已完成");
|
|
93
|
-
return {
|
|
94
|
-
providerSessionId: threadId,
|
|
95
|
-
cliType: "builtin",
|
|
96
|
-
agentType: "codex",
|
|
97
|
-
effectiveConfig,
|
|
98
|
-
command: this.started.command,
|
|
99
|
-
args: this.started.args,
|
|
100
|
-
cwd: this.cwd,
|
|
101
|
-
exitCode: this.rpc.exitCode,
|
|
102
|
-
signal: this.rpc.signal,
|
|
103
|
-
durationMs: Date.now() - startedAt,
|
|
104
|
-
stdout: this.rpc.stdout,
|
|
105
|
-
stderr: this.rpc.stderr,
|
|
106
|
-
content: finalContent,
|
|
107
|
-
updates: this.updates,
|
|
108
|
-
statusTimeline: this.statusTimeline,
|
|
109
|
-
promptResponse
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
async cancel() {
|
|
113
|
-
try {
|
|
114
|
-
if (this.providerSessionId && this.started.child.exitCode === null && this.started.child.signalCode === null) {
|
|
115
|
-
await this.rpc.request("turn/interrupt", { threadId: this.providerSessionId }, CANCEL_INTERRUPT_TIMEOUT_MS).catch(() => void 0);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
finally {
|
|
119
|
-
await this.close();
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
async close() {
|
|
123
|
-
this.statusCallback = null;
|
|
124
|
-
this.updateCallback = null;
|
|
125
|
-
this.permissionCallback = null;
|
|
126
|
-
for (const pending of this.pendingTurnCompletions.values()) {
|
|
127
|
-
clearTimeout(pending.timer);
|
|
128
|
-
pending.reject(new Error("Codex app-server session closed"));
|
|
129
|
-
}
|
|
130
|
-
this.pendingTurnCompletions.clear();
|
|
131
|
-
if (this.abortHandler)
|
|
132
|
-
this.abortHandler = null;
|
|
133
|
-
await this.closeCodexProcess();
|
|
134
|
-
}
|
|
135
|
-
async closeCodexProcess() {
|
|
136
|
-
if (this.started.child.exitCode !== null || this.started.child.signalCode !== null)
|
|
137
|
-
return;
|
|
138
|
-
this.started.child.stdin.end();
|
|
139
|
-
if (await this.waitForProcessClose(2_000))
|
|
140
|
-
return;
|
|
141
|
-
this.started.child.kill("SIGTERM");
|
|
142
|
-
if (await this.waitForProcessClose(1_000))
|
|
143
|
-
return;
|
|
144
|
-
this.started.child.kill("SIGKILL");
|
|
145
|
-
await this.waitForProcessClose(1_000);
|
|
146
|
-
}
|
|
147
|
-
waitForProcessClose(timeoutMs) {
|
|
148
|
-
if (this.started.child.exitCode !== null || this.started.child.signalCode !== null)
|
|
149
|
-
return Promise.resolve(true);
|
|
150
|
-
return new Promise((resolve) => {
|
|
151
|
-
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
152
|
-
timer.unref();
|
|
153
|
-
this.started.child.once("close", () => {
|
|
154
|
-
clearTimeout(timer);
|
|
155
|
-
resolve(true);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
async initialize() {
|
|
160
|
-
if (this.initialized)
|
|
161
|
-
return;
|
|
162
|
-
await this.rpc.request("initialize", {
|
|
163
|
-
clientInfo: { name: "aca", version: "0.1.0" },
|
|
164
|
-
capabilities: {
|
|
165
|
-
experimentalApi: true,
|
|
166
|
-
requestAttestation: false
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
this.initialized = true;
|
|
170
|
-
}
|
|
171
|
-
async establishThread(input) {
|
|
172
|
-
const accessConfig = codexAccessConfigFromMode(input.mode);
|
|
173
|
-
if (this.providerSessionId) {
|
|
174
|
-
try {
|
|
175
|
-
this.emitStatus("resuming", "Codex 会话恢复中", "thread/resume");
|
|
176
|
-
const resumed = await this.rpc.request("thread/resume", {
|
|
177
|
-
threadId: this.providerSessionId,
|
|
178
|
-
cwd: input.cwd,
|
|
179
|
-
...(input.model ? { model: input.model } : {}),
|
|
180
|
-
approvalsReviewer: "user",
|
|
181
|
-
...accessConfig
|
|
182
|
-
}, 60_000);
|
|
183
|
-
this.providerSessionId = String(resumed?.thread?.id ?? this.providerSessionId);
|
|
184
|
-
return this.providerSessionId;
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
this.providerSessionId = null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
this.emitStatus("acp", "Codex 启动中", "thread/start");
|
|
191
|
-
const created = await this.rpc.request("thread/start", {
|
|
192
|
-
cwd: input.cwd,
|
|
193
|
-
...(input.model ? { model: input.model } : {}),
|
|
194
|
-
approvalsReviewer: "user",
|
|
195
|
-
...accessConfig,
|
|
196
|
-
ephemeral: false,
|
|
197
|
-
threadSource: "aca"
|
|
198
|
-
}, 60_000);
|
|
199
|
-
const threadId = created?.thread?.id;
|
|
200
|
-
if (!threadId)
|
|
201
|
-
throw new Error("Codex app-server thread/start did not return thread id");
|
|
202
|
-
this.providerSessionId = threadId;
|
|
203
|
-
return threadId;
|
|
204
|
-
}
|
|
205
|
-
attachAbortSignal(signal) {
|
|
206
|
-
if (!signal)
|
|
207
|
-
return;
|
|
208
|
-
this.abortHandler = () => {
|
|
209
|
-
void this.cancel();
|
|
210
|
-
};
|
|
211
|
-
if (signal.aborted)
|
|
212
|
-
throw new Error("ACP prompt cancelled");
|
|
213
|
-
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
214
|
-
}
|
|
215
|
-
handleNotification(method, params) {
|
|
216
|
-
if (!this.promptActive && !shouldHandleIdleCodexNotification(method))
|
|
217
|
-
return;
|
|
218
|
-
this.rpc.refreshPendingRequestTimeout("turn/start");
|
|
219
|
-
this.updates.push({ method, params });
|
|
220
|
-
this.captureAgentMessagePhase(method, params);
|
|
221
|
-
this.appendFinalAnswerDelta(method, params);
|
|
222
|
-
if (method === "turn/completed") {
|
|
223
|
-
this.resolveTurnCompleted(params);
|
|
224
|
-
}
|
|
225
|
-
mapCodexNotification(method, params, {
|
|
226
|
-
pushUpdate: (event) => this.updateCallback?.(event),
|
|
227
|
-
pushStatus: (phase, label, detail, updateType) => this.emitStatus(phase, label, detail, updateType)
|
|
228
|
-
}, { agentMessagePhases: this.agentMessagePhases });
|
|
229
|
-
}
|
|
230
|
-
captureAgentMessagePhase(method, params) {
|
|
231
|
-
if (method !== "item/started" && method !== "item/completed")
|
|
232
|
-
return;
|
|
233
|
-
const item = params?.item;
|
|
234
|
-
if (item?.type !== "agentMessage" || !item.id)
|
|
235
|
-
return;
|
|
236
|
-
this.agentMessagePhases.set(item.id, item.phase ?? "");
|
|
237
|
-
}
|
|
238
|
-
appendFinalAnswerDelta(method, params) {
|
|
239
|
-
if (method !== "item/agentMessage/delta")
|
|
240
|
-
return;
|
|
241
|
-
const deltaParams = params;
|
|
242
|
-
if (!deltaParams.itemId || typeof deltaParams.delta !== "string")
|
|
243
|
-
return;
|
|
244
|
-
const phase = this.agentMessagePhases.get(deltaParams.itemId);
|
|
245
|
-
if (phase === "final_answer" || !phase) {
|
|
246
|
-
this.content += deltaParams.delta;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
waitForTurnCompleted(turnId, timeoutMs) {
|
|
250
|
-
const completed = this.completedTurns.get(turnId);
|
|
251
|
-
if (completed)
|
|
252
|
-
return Promise.resolve(completed);
|
|
253
|
-
return new Promise((resolve, reject) => {
|
|
254
|
-
const timer = setTimeout(() => {
|
|
255
|
-
this.pendingTurnCompletions.delete(turnId);
|
|
256
|
-
reject(new Error(`Codex app-server request timed out while waiting for turn/completed`));
|
|
257
|
-
}, timeoutMs);
|
|
258
|
-
timer.unref();
|
|
259
|
-
this.pendingTurnCompletions.set(turnId, { resolve, reject, timer });
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
resolveTurnCompleted(params) {
|
|
263
|
-
const turn = params?.turn;
|
|
264
|
-
const turnId = turn?.id;
|
|
265
|
-
if (!turnId)
|
|
266
|
-
return;
|
|
267
|
-
this.completedTurns.set(turnId, turn);
|
|
268
|
-
const pending = this.pendingTurnCompletions.get(turnId);
|
|
269
|
-
if (!pending)
|
|
270
|
-
return;
|
|
271
|
-
this.pendingTurnCompletions.delete(turnId);
|
|
272
|
-
clearTimeout(pending.timer);
|
|
273
|
-
if (turn.status === "failed") {
|
|
274
|
-
pending.reject(new Error(`Codex turn failed: ${JSON.stringify(turn.error ?? {})}`));
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
pending.resolve(turn);
|
|
278
|
-
}
|
|
279
|
-
async handleReverseRequest(method, params) {
|
|
280
|
-
if (isCodexApprovalRequest(method)) {
|
|
281
|
-
this.emitStatus("requestPermission", "等待授权", summarizeRequest(params), method);
|
|
282
|
-
return resolveCodexApprovalRequest(method, params, this.permissionCallback);
|
|
283
|
-
}
|
|
284
|
-
return {};
|
|
285
|
-
}
|
|
286
|
-
emitStatus(phase, label, detail, updateType) {
|
|
287
|
-
const status = {
|
|
288
|
-
phase,
|
|
289
|
-
label,
|
|
290
|
-
...(detail ? { detail } : {}),
|
|
291
|
-
...(updateType ? { updateType } : {}),
|
|
292
|
-
atMs: Date.now()
|
|
293
|
-
};
|
|
294
|
-
const last = this.statusTimeline[this.statusTimeline.length - 1];
|
|
295
|
-
if (last && last.phase === status.phase && last.label === status.label && last.detail === status.detail && last.updateType === status.updateType)
|
|
296
|
-
return;
|
|
297
|
-
this.statusTimeline.push(status);
|
|
298
|
-
this.statusCallback?.(status);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
function promptBlocksToCodexInput(prompt, blocks) {
|
|
302
|
-
const inputBlocks = blocks?.length ? blocks : [{ type: "text", text: prompt }];
|
|
303
|
-
return inputBlocks.map((block) => {
|
|
304
|
-
if (block.type === "image") {
|
|
305
|
-
if (block.uri && fs.existsSync(block.uri))
|
|
306
|
-
return { type: "localImage", path: block.uri };
|
|
307
|
-
return { type: "image", url: `data:${block.mimeType};base64,${block.data}` };
|
|
308
|
-
}
|
|
309
|
-
return { type: "text", text: block.text, text_elements: [] };
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
function codexAccessConfigFromMode(mode) {
|
|
313
|
-
const normalized = String(mode || "").trim();
|
|
314
|
-
if (normalized === "read-only") {
|
|
315
|
-
return {
|
|
316
|
-
approvalPolicy: "on-request",
|
|
317
|
-
sandbox: "read-only"
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
if (normalized === "acceptEdits" || normalized === "accept-edits") {
|
|
321
|
-
return {
|
|
322
|
-
approvalPolicy: "on-failure",
|
|
323
|
-
sandbox: "workspace-write"
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
if (["agent-full-access", "full-access", "bypassPermissions", "danger-full-access"].includes(normalized)) {
|
|
327
|
-
return {
|
|
328
|
-
approvalPolicy: "never",
|
|
329
|
-
sandbox: "danger-full-access"
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
return {
|
|
333
|
-
approvalPolicy: "on-request",
|
|
334
|
-
sandbox: "workspace-write"
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function turnConfigFromOptions(values) {
|
|
338
|
-
if (!values)
|
|
339
|
-
return {};
|
|
340
|
-
const result = {};
|
|
341
|
-
const effort = values.reasoningEffort ?? values["model_reasoning_effort"];
|
|
342
|
-
if (typeof effort === "string" && effort)
|
|
343
|
-
result.effort = effort;
|
|
344
|
-
return result;
|
|
345
|
-
}
|
|
346
|
-
function summarizeRequest(params) {
|
|
347
|
-
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
348
|
-
return undefined;
|
|
349
|
-
const record = params;
|
|
350
|
-
for (const key of ["command", "cwd", "reason", "grantRoot"]) {
|
|
351
|
-
const value = record[key];
|
|
352
|
-
if (typeof value === "string" && value.trim())
|
|
353
|
-
return textPreview(value);
|
|
354
|
-
}
|
|
355
|
-
return undefined;
|
|
356
|
-
}
|
|
357
|
-
function textPreview(value) {
|
|
358
|
-
const normalized = value.replace(/\s+/g, " ").trim();
|
|
359
|
-
return normalized.length > 120 ? `${normalized.slice(0, 117)}...` : normalized;
|
|
360
|
-
}
|
|
361
|
-
function shouldHandleIdleCodexNotification(method) {
|
|
362
|
-
if (method.startsWith("thread/"))
|
|
363
|
-
return true;
|
|
364
|
-
return [
|
|
365
|
-
"account/rateLimits/updated",
|
|
366
|
-
"account/updated",
|
|
367
|
-
"configWarning",
|
|
368
|
-
"warning",
|
|
369
|
-
"model/rerouted",
|
|
370
|
-
"thread/compacted"
|
|
371
|
-
].includes(method);
|
|
372
|
-
}
|
|
373
|
-
function readAcpPromptTimeoutMs() {
|
|
374
|
-
const parsed = Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS ?? "", 10);
|
|
375
|
-
return Number.isInteger(parsed) && parsed >= 30_000 ? parsed : 6 * 60 * 60 * 1000;
|
|
376
|
-
}
|
|
1
|
+
import c from"node:fs";import{JsonLineRpcClient as u}from"../../client/json-rpc.js";import{startCodexAppServer as h}from"./codex-process.js";import{extractAssistantTextFromTurn as p,mapCodexNotification as m}from"./events.js";import{isCodexApprovalRequest as f,resolveCodexApprovalRequest as g}from"./permissions.js";const C=1500,w={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class _{id="native-codex";name="Native Codex";capabilities=w;async createSession(e){return new v(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class v{cwd;providerSessionId;defaultTimeoutMs;started;rpc;initialized=!1;content="";promptActive=!1;updates=[];statusTimeline=[];statusCallback=null;updateCallback=null;permissionCallback=null;abortHandler=null;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;constructor(e,s,t=I()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t,this.started=h(e),this.rpc=new u(this.started.child,{onNotification:(i,n)=>this.handleNotification(i,n),onRequest:(i,n)=>this.handleReverseRequest(i,n)})}async sendPrompt(e){const s=Date.now(),t=l(e.mode);this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.attachAbortSignal(e.signal),this.emitStatus("initializing","Codex 初始化中","initialize"),await this.initialize();const i=await this.establishThread(e);this.emitStatus("thinking","思考中"),this.content="",this.updates.length=0,this.agentMessagePhases.clear(),this.promptActive=!0;let n,a;try{n=await this.rpc.request("turn/start",{threadId:i,clientUserMessageId:`aca-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:T(e.prompt,e.promptBlocks),cwd:e.cwd,...e.model?{model:e.model}:{},...x(e.configOptionValues)},e.timeoutMs??this.defaultTimeoutMs);const o=String(n?.turn?.id??"");a=o?await this.waitForTurnCompleted(o,e.timeoutMs??this.defaultTimeoutMs):n?.turn}finally{this.promptActive=!1}const d=this.content.trim()||p(a);return this.emitStatus("completed","已完成"),{providerSessionId:i,cliType:"builtin",agentType:"codex",effectiveConfig:t,command:this.started.command,args:this.started.args,cwd:this.cwd,exitCode:this.rpc.exitCode,signal:this.rpc.signal,durationMs:Date.now()-s,stdout:this.rpc.stdout,stderr:this.rpc.stderr,content:d,updates:this.updates,statusTimeline:this.statusTimeline,promptResponse:n}}async cancel(){try{this.providerSessionId&&this.started.child.exitCode===null&&this.started.child.signalCode===null&&await this.rpc.request("turn/interrupt",{threadId:this.providerSessionId},C).catch(()=>{})}finally{await this.close()}}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null;for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex app-server session closed"));this.pendingTurnCompletions.clear(),this.abortHandler&&(this.abortHandler=null),await this.closeCodexProcess()}async closeCodexProcess(){this.started.child.exitCode!==null||this.started.child.signalCode!==null||(this.started.child.stdin.end(),!await this.waitForProcessClose(2e3)&&(this.started.child.kill("SIGTERM"),!await this.waitForProcessClose(1e3)&&(this.started.child.kill("SIGKILL"),await this.waitForProcessClose(1e3))))}waitForProcessClose(e){return this.started.child.exitCode!==null||this.started.child.signalCode!==null?Promise.resolve(!0):new Promise(s=>{const t=setTimeout(()=>s(!1),e);t.unref(),this.started.child.once("close",()=>{clearTimeout(t),s(!0)})})}async initialize(){this.initialized||(await this.rpc.request("initialize",{clientInfo:{name:"aca",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}}),this.initialized=!0)}async establishThread(e){const s=l(e.mode);if(this.providerSessionId)try{this.emitStatus("resuming","Codex 会话恢复中","thread/resume");const n=await this.rpc.request("thread/resume",{threadId:this.providerSessionId,cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s},6e4);return this.providerSessionId=String(n?.thread?.id??this.providerSessionId),this.providerSessionId}catch{this.providerSessionId=null}this.emitStatus("acp","Codex 启动中","thread/start");const i=(await this.rpc.request("thread/start",{cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s,ephemeral:!1,threadSource:"aca"},6e4))?.thread?.id;if(!i)throw new Error("Codex app-server thread/start did not return thread id");return this.providerSessionId=i,i}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}handleNotification(e,s){!this.promptActive&&!P(e)||(this.rpc.refreshPendingRequestTimeout("turn/start"),this.updates.push({method:e,params:s}),this.captureAgentMessagePhase(e,s),this.appendFinalAnswerDelta(e,s),e==="turn/completed"&&this.resolveTurnCompleted(s),m(e,s,{pushUpdate:t=>this.updateCallback?.(t),pushStatus:(t,i,n,a)=>this.emitStatus(t,i,n,a)},{agentMessagePhases:this.agentMessagePhases}))}captureAgentMessagePhase(e,s){if(e!=="item/started"&&e!=="item/completed")return;const t=s?.item;t?.type!=="agentMessage"||!t.id||this.agentMessagePhases.set(t.id,t.phase??"")}appendFinalAnswerDelta(e,s){if(e!=="item/agentMessage/delta")return;const t=s;if(!t.itemId||typeof t.delta!="string")return;const i=this.agentMessagePhases.get(t.itemId);(i==="final_answer"||!i)&&(this.content+=t.delta)}waitForTurnCompleted(e,s){const t=this.completedTurns.get(e);return t?Promise.resolve(t):new Promise((i,n)=>{const a=setTimeout(()=>{this.pendingTurnCompletions.delete(e),n(new Error("Codex app-server request timed out while waiting for turn/completed"))},s);a.unref(),this.pendingTurnCompletions.set(e,{resolve:i,reject:n,timer:a})})}resolveTurnCompleted(e){const s=e?.turn,t=s?.id;if(!t)return;this.completedTurns.set(t,s);const i=this.pendingTurnCompletions.get(t);if(i){if(this.pendingTurnCompletions.delete(t),clearTimeout(i.timer),s.status==="failed"){i.reject(new Error(`Codex turn failed: ${JSON.stringify(s.error??{})}`));return}i.resolve(s)}}async handleReverseRequest(e,s){return f(e)?(this.emitStatus("requestPermission","等待授权",y(s),e),g(e,s,this.permissionCallback)):{}}emitStatus(e,s,t,i){const n={phase:e,label:s,...t?{detail:t}:{},...i?{updateType:i}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===n.phase&&a.label===n.label&&a.detail===n.detail&&a.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}}function T(r,e){return(e?.length?e:[{type:"text",text:r}]).map(t=>t.type==="image"?t.uri&&c.existsSync(t.uri)?{type:"localImage",path:t.uri}:{type:"image",url:`data:${t.mimeType};base64,${t.data}`}:{type:"text",text:t.text,text_elements:[]})}function l(r){const e=String(r||"").trim();return e==="read-only"?{approvalPolicy:"on-request",sandbox:"read-only"}:e==="acceptEdits"||e==="accept-edits"?{approvalPolicy:"on-failure",sandbox:"workspace-write"}:["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?{approvalPolicy:"never",sandbox:"danger-full-access"}:{approvalPolicy:"on-request",sandbox:"workspace-write"}}function x(r){if(!r)return{};const e={},s=r.reasoningEffort??r.model_reasoning_effort;return typeof s=="string"&&s&&(e.effort=s),e}function y(r){if(!r||typeof r!="object"||Array.isArray(r))return;const e=r;for(const s of["command","cwd","reason","grantRoot"]){const t=e[s];if(typeof t=="string"&&t.trim())return S(t)}}function S(r){const e=r.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function P(r){return r.startsWith("thread/")?!0:["account/rateLimits/updated","account/updated","configWarning","warning","model/rerouted","thread/compacted"].includes(r)}function I(){const r=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(r)&&r>=3e4?r:360*60*1e3}export{_ as NativeCodexProvider,w as nativeCodexCapabilities};
|