cyrus-codex-runner 0.2.64-test.6 → 0.2.64
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/CodexEventMapper.d.ts +56 -0
- package/dist/CodexEventMapper.d.ts.map +1 -0
- package/dist/CodexEventMapper.js +469 -0
- package/dist/CodexEventMapper.js.map +1 -0
- package/dist/CodexRunner.d.ts +37 -46
- package/dist/CodexRunner.d.ts.map +1 -1
- package/dist/CodexRunner.js +136 -851
- package/dist/CodexRunner.js.map +1 -1
- package/dist/CodexSkillStager.d.ts +42 -0
- package/dist/CodexSkillStager.d.ts.map +1 -0
- package/dist/CodexSkillStager.js +182 -0
- package/dist/CodexSkillStager.js.map +1 -0
- package/dist/backend/AppServerCodexBackend.d.ts +74 -0
- package/dist/backend/AppServerCodexBackend.d.ts.map +1 -0
- package/dist/backend/AppServerCodexBackend.js +352 -0
- package/dist/backend/AppServerCodexBackend.js.map +1 -0
- package/dist/backend/appServerClient.d.ts +75 -0
- package/dist/backend/appServerClient.d.ts.map +1 -0
- package/dist/backend/appServerClient.js +223 -0
- package/dist/backend/appServerClient.js.map +1 -0
- package/dist/backend/appServerEvents.d.ts +9 -0
- package/dist/backend/appServerEvents.d.ts.map +1 -0
- package/dist/backend/appServerEvents.js +110 -0
- package/dist/backend/appServerEvents.js.map +1 -0
- package/dist/backend/appServerProcess.d.ts +38 -0
- package/dist/backend/appServerProcess.d.ts.map +1 -0
- package/dist/backend/appServerProcess.js +283 -0
- package/dist/backend/appServerProcess.js.map +1 -0
- package/dist/backend/codexBinary.d.ts +24 -0
- package/dist/backend/codexBinary.d.ts.map +1 -0
- package/dist/backend/codexBinary.js +44 -0
- package/dist/backend/codexBinary.js.map +1 -0
- package/dist/backend/types.d.ts +210 -0
- package/dist/backend/types.d.ts.map +1 -0
- package/dist/backend/types.js +2 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/config/CodexConfigBuilder.d.ts +40 -0
- package/dist/config/CodexConfigBuilder.d.ts.map +1 -0
- package/dist/config/CodexConfigBuilder.js +182 -0
- package/dist/config/CodexConfigBuilder.js.map +1 -0
- package/dist/config/mcpConfigTranslator.d.ts +17 -0
- package/dist/config/mcpConfigTranslator.d.ts.map +1 -0
- package/dist/config/mcpConfigTranslator.js +245 -0
- package/dist/config/mcpConfigTranslator.js.map +1 -0
- package/dist/config/sandboxPolicy.d.ts +43 -0
- package/dist/config/sandboxPolicy.d.ts.map +1 -0
- package/dist/config/sandboxPolicy.js +56 -0
- package/dist/config/sandboxPolicy.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +9 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { translateAppServerItem, } from "./appServerEvents.js";
|
|
3
|
+
import { AppServerProcessManager, defaultAppServerProcessManager, } from "./appServerProcess.js";
|
|
4
|
+
/**
|
|
5
|
+
* Backend that drives one Codex app-server thread over the process-wide shared
|
|
6
|
+
* JSON-RPC connection. The app-server process is shared; this class owns only
|
|
7
|
+
* per-thread state and supports injecting input into an active turn via
|
|
8
|
+
* `turn/steer` ({@link supportsSteer} is true).
|
|
9
|
+
*/
|
|
10
|
+
export class AppServerCodexBackend extends EventEmitter {
|
|
11
|
+
supportsSteer = true;
|
|
12
|
+
appServer = null;
|
|
13
|
+
threadId = null;
|
|
14
|
+
activeTurnId = null;
|
|
15
|
+
turnActive = false;
|
|
16
|
+
lastUsage = {
|
|
17
|
+
input_tokens: 0,
|
|
18
|
+
output_tokens: 0,
|
|
19
|
+
cached_input_tokens: 0,
|
|
20
|
+
};
|
|
21
|
+
/** Structured-output schema for turns, captured at open() for turn/start. */
|
|
22
|
+
outputSchema;
|
|
23
|
+
/** Resolver for the in-flight {@link runTurn} promise. */
|
|
24
|
+
turnResolve = null;
|
|
25
|
+
turnReject = null;
|
|
26
|
+
/** Watchdog: fails a turn that goes fully silent for too long. */
|
|
27
|
+
idleTimer = null;
|
|
28
|
+
turnIdleTimeoutMs;
|
|
29
|
+
processManager;
|
|
30
|
+
threadHandler = {
|
|
31
|
+
onNotification: (method, params) => this.onNotification(method, params),
|
|
32
|
+
onProcessGone: () => this.onProcessGone(),
|
|
33
|
+
onProcessError: (error) => this.onProcessError(error),
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* @param processManagerOrFactory Overridable shared process manager. Tests may
|
|
37
|
+
* still pass the older client factory shape; it is wrapped in an isolated
|
|
38
|
+
* manager for compatibility.
|
|
39
|
+
* @param options.turnIdleTimeoutMs Fail an in-flight turn if the app-server
|
|
40
|
+
* emits no notifications for this long (default 5min; Codex streams
|
|
41
|
+
* continuously, so prolonged silence means a wedged turn). 0 disables it.
|
|
42
|
+
* @param options.requestTimeoutMs Forwarded when wrapping a test client factory.
|
|
43
|
+
*/
|
|
44
|
+
constructor(processManagerOrFactory = defaultAppServerProcessManager, options) {
|
|
45
|
+
super();
|
|
46
|
+
this.processManager =
|
|
47
|
+
typeof processManagerOrFactory === "function"
|
|
48
|
+
? new AppServerProcessManager(processManagerOrFactory, {
|
|
49
|
+
...(options?.requestTimeoutMs !== undefined
|
|
50
|
+
? { requestTimeoutMs: options.requestTimeoutMs }
|
|
51
|
+
: {}),
|
|
52
|
+
idleCloseMs: 0,
|
|
53
|
+
})
|
|
54
|
+
: processManagerOrFactory;
|
|
55
|
+
this.turnIdleTimeoutMs = options?.turnIdleTimeoutMs ?? 300_000;
|
|
56
|
+
}
|
|
57
|
+
async open(config) {
|
|
58
|
+
this.outputSchema = config.outputSchema;
|
|
59
|
+
const appServer = await this.processManager.acquire(config);
|
|
60
|
+
this.appServer = appServer;
|
|
61
|
+
try {
|
|
62
|
+
const threadId = config.resumeSessionId
|
|
63
|
+
? await this.resumeThread(config)
|
|
64
|
+
: await this.startThread(config);
|
|
65
|
+
this.threadId = threadId;
|
|
66
|
+
appServer.registerThread(threadId, this.threadHandler);
|
|
67
|
+
this.emit("event", { kind: "thread-started", threadId });
|
|
68
|
+
return { threadId };
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
this.appServer = null;
|
|
72
|
+
appServer.release();
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async runTurn(input) {
|
|
77
|
+
if (!this.appServer || !this.threadId) {
|
|
78
|
+
throw new Error("AppServerCodexBackend.runTurn called before open()");
|
|
79
|
+
}
|
|
80
|
+
const turnPromise = new Promise((resolve, reject) => {
|
|
81
|
+
this.turnResolve = resolve;
|
|
82
|
+
this.turnReject = reject;
|
|
83
|
+
});
|
|
84
|
+
this.turnActive = true;
|
|
85
|
+
this.armIdleWatchdog();
|
|
86
|
+
try {
|
|
87
|
+
const result = await this.appServer.request("turn/start", {
|
|
88
|
+
threadId: this.threadId,
|
|
89
|
+
input: this.toProtocolInput(input),
|
|
90
|
+
...(this.outputSchema !== undefined
|
|
91
|
+
? { outputSchema: this.outputSchema }
|
|
92
|
+
: {}),
|
|
93
|
+
});
|
|
94
|
+
this.activeTurnId = result?.turn?.id ?? this.activeTurnId;
|
|
95
|
+
// NOTE: the turn is not steerable the instant turn/start returns — the
|
|
96
|
+
// server only accepts turn/steer once it has emitted the `turn/started`
|
|
97
|
+
// notification. The runner is signalled to flush buffered follow-ups
|
|
98
|
+
// from that notification handler, not here.
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.turnActive = false;
|
|
102
|
+
this.turnResolve = null;
|
|
103
|
+
this.turnReject = null;
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
await turnPromise;
|
|
107
|
+
}
|
|
108
|
+
async steer(input) {
|
|
109
|
+
if (!this.appServer || !this.threadId) {
|
|
110
|
+
throw new Error("AppServerCodexBackend.steer called before open()");
|
|
111
|
+
}
|
|
112
|
+
if (!this.turnActive || !this.activeTurnId) {
|
|
113
|
+
throw new Error("Cannot steer: no active turn");
|
|
114
|
+
}
|
|
115
|
+
await this.appServer.request("turn/steer", {
|
|
116
|
+
threadId: this.threadId,
|
|
117
|
+
expectedTurnId: this.activeTurnId,
|
|
118
|
+
input: this.toProtocolInput(input),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
isTurnActive() {
|
|
122
|
+
// A turn is steerable only once turn/start has returned its id — during
|
|
123
|
+
// the brief turn/start request itself, turnActive is true but there is no
|
|
124
|
+
// id to target yet.
|
|
125
|
+
return this.turnActive && this.activeTurnId !== null;
|
|
126
|
+
}
|
|
127
|
+
async interrupt() {
|
|
128
|
+
if (!this.appServer || !this.threadId || !this.activeTurnId) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await this.appServer.request("turn/interrupt", {
|
|
133
|
+
threadId: this.threadId,
|
|
134
|
+
turnId: this.activeTurnId,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Interrupt is best-effort; the turn may already have ended.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async close() {
|
|
142
|
+
const appServer = this.appServer;
|
|
143
|
+
const threadId = this.threadId;
|
|
144
|
+
const turnId = this.activeTurnId;
|
|
145
|
+
this.appServer = null;
|
|
146
|
+
this.threadId = null;
|
|
147
|
+
this.activeTurnId = null;
|
|
148
|
+
if (appServer && threadId && turnId) {
|
|
149
|
+
void appServer
|
|
150
|
+
.request("turn/interrupt", { threadId, turnId })
|
|
151
|
+
.catch(() => undefined);
|
|
152
|
+
}
|
|
153
|
+
if (appServer && threadId) {
|
|
154
|
+
appServer.unregisterThread(threadId, this.threadHandler);
|
|
155
|
+
}
|
|
156
|
+
this.settleTurn(new Error("app-server backend closed"));
|
|
157
|
+
appServer?.release();
|
|
158
|
+
}
|
|
159
|
+
// ---- Thread setup -------------------------------------------------------
|
|
160
|
+
async startThread(config) {
|
|
161
|
+
const result = await this.appServer?.request("thread/start", this.threadOptionsParams(config));
|
|
162
|
+
const id = result?.thread?.id;
|
|
163
|
+
if (!id) {
|
|
164
|
+
throw new Error("thread/start did not return a thread id");
|
|
165
|
+
}
|
|
166
|
+
return id;
|
|
167
|
+
}
|
|
168
|
+
async resumeThread(config) {
|
|
169
|
+
const result = await this.appServer?.request("thread/resume", {
|
|
170
|
+
threadId: config.resumeSessionId,
|
|
171
|
+
...this.threadOptionsParams(config),
|
|
172
|
+
});
|
|
173
|
+
// Resuming returns the same id we asked for; fall back to it defensively.
|
|
174
|
+
return result?.thread?.id ?? config.resumeSessionId ?? "";
|
|
175
|
+
}
|
|
176
|
+
threadOptionsParams(config) {
|
|
177
|
+
const sandbox = config.sandbox;
|
|
178
|
+
// `sandbox` (coarse mode) and `permissions` (named profile) are mutually
|
|
179
|
+
// exclusive on thread/start; pick exactly one based on the resolved arm.
|
|
180
|
+
const sandboxParams = sandbox.kind === "profile"
|
|
181
|
+
? { permissions: sandbox.profileId }
|
|
182
|
+
: { sandbox: sandbox.mode };
|
|
183
|
+
return {
|
|
184
|
+
...(config.workingDirectory ? { cwd: config.workingDirectory } : {}),
|
|
185
|
+
approvalPolicy: config.approvalPolicy,
|
|
186
|
+
...sandboxParams,
|
|
187
|
+
...(config.model ? { model: config.model } : {}),
|
|
188
|
+
...(config.developerInstructions
|
|
189
|
+
? { developerInstructions: config.developerInstructions }
|
|
190
|
+
: {}),
|
|
191
|
+
config: this.buildThreadConfig(config),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Build the free-form Codex `config` for thread/start. The app-server has no
|
|
196
|
+
* `--add-dir` flag, so:
|
|
197
|
+
* - `workspace-mode`: writable roots + network ride on `sandbox_workspace_write`
|
|
198
|
+
* (only meaningful in workspace-write mode; omitted otherwise).
|
|
199
|
+
* - `profile`: the granular permission profile body is registered under
|
|
200
|
+
* `permissions.<id>` and selected via the `permissions` thread param.
|
|
201
|
+
* MCP servers etc. ride along in configOverrides.
|
|
202
|
+
*/
|
|
203
|
+
buildThreadConfig(config) {
|
|
204
|
+
const base = config.configOverrides
|
|
205
|
+
? { ...config.configOverrides }
|
|
206
|
+
: {};
|
|
207
|
+
const sandbox = config.sandbox;
|
|
208
|
+
if (sandbox.kind === "profile") {
|
|
209
|
+
base.permissions = {
|
|
210
|
+
[sandbox.profileId]: {
|
|
211
|
+
filesystem: { ...sandbox.filesystem },
|
|
212
|
+
network: { enabled: sandbox.networkAccess },
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
else if (sandbox.mode === "workspace-write") {
|
|
217
|
+
base.sandbox_workspace_write = {
|
|
218
|
+
network_access: sandbox.networkAccess,
|
|
219
|
+
...(sandbox.writableRoots.length > 0
|
|
220
|
+
? { writable_roots: [...sandbox.writableRoots] }
|
|
221
|
+
: {}),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return base;
|
|
225
|
+
}
|
|
226
|
+
toProtocolInput(input) {
|
|
227
|
+
return input.map((item) => item.type === "text"
|
|
228
|
+
? { type: "text", text: item.text }
|
|
229
|
+
: { type: "localImage", path: item.path });
|
|
230
|
+
}
|
|
231
|
+
// ---- Notification / request handling ------------------------------------
|
|
232
|
+
onNotification(method, params) {
|
|
233
|
+
// Any notification is a sign of life — reset the idle watchdog.
|
|
234
|
+
if (this.turnActive) {
|
|
235
|
+
this.armIdleWatchdog();
|
|
236
|
+
}
|
|
237
|
+
const p = (params ?? {});
|
|
238
|
+
switch (method) {
|
|
239
|
+
case "turn/started": {
|
|
240
|
+
// The server now accepts turn/steer for this turn. Capture the id
|
|
241
|
+
// (defensively) and signal the runner to flush buffered follow-ups.
|
|
242
|
+
const turn = p.turn;
|
|
243
|
+
if (turn?.id) {
|
|
244
|
+
this.activeTurnId = turn.id;
|
|
245
|
+
}
|
|
246
|
+
this.emit("event", { kind: "turn-started" });
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "item/started": {
|
|
250
|
+
const item = translateAppServerItem(p.item);
|
|
251
|
+
if (item)
|
|
252
|
+
this.emit("event", { kind: "item-started", item });
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case "item/completed": {
|
|
256
|
+
const item = translateAppServerItem(p.item);
|
|
257
|
+
if (item)
|
|
258
|
+
this.emit("event", { kind: "item-completed", item });
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case "thread/tokenUsage/updated": {
|
|
262
|
+
this.lastUsage = this.readUsage(p);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case "turn/completed": {
|
|
266
|
+
this.onTurnCompleted(p);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
default:
|
|
270
|
+
// Other notifications (rate limits, mcp startup, warnings, deltas)
|
|
271
|
+
// are not needed for the current activity mapping.
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
onTurnCompleted(params) {
|
|
276
|
+
const turn = (params.turn ?? {});
|
|
277
|
+
this.turnActive = false;
|
|
278
|
+
this.activeTurnId = null;
|
|
279
|
+
if (turn.status === "failed") {
|
|
280
|
+
const message = turn.error?.message || "Codex turn failed";
|
|
281
|
+
this.emit("event", { kind: "turn-failed", message });
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
this.emit("event", { kind: "turn-completed", usage: this.lastUsage });
|
|
285
|
+
}
|
|
286
|
+
this.settleTurn();
|
|
287
|
+
}
|
|
288
|
+
readUsage(params) {
|
|
289
|
+
const total = (params.tokenUsage?.total ?? {});
|
|
290
|
+
return {
|
|
291
|
+
input_tokens: numberOr(total.inputTokens, this.lastUsage.input_tokens),
|
|
292
|
+
output_tokens: numberOr(total.outputTokens, this.lastUsage.output_tokens),
|
|
293
|
+
cached_input_tokens: numberOr(total.cachedInputTokens, this.lastUsage.cached_input_tokens),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
onProcessGone() {
|
|
297
|
+
if (this.turnActive) {
|
|
298
|
+
this.emit("event", {
|
|
299
|
+
kind: "turn-failed",
|
|
300
|
+
message: "codex app-server exited before the turn completed",
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
this.settleTurn();
|
|
304
|
+
}
|
|
305
|
+
onProcessError(err) {
|
|
306
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
307
|
+
this.emit("event", { kind: "error", message });
|
|
308
|
+
}
|
|
309
|
+
/** Resolve or reject the in-flight runTurn promise exactly once. */
|
|
310
|
+
settleTurn(error) {
|
|
311
|
+
this.clearIdleWatchdog();
|
|
312
|
+
const resolve = this.turnResolve;
|
|
313
|
+
const reject = this.turnReject;
|
|
314
|
+
this.turnResolve = null;
|
|
315
|
+
this.turnReject = null;
|
|
316
|
+
this.turnActive = false;
|
|
317
|
+
if (error && reject) {
|
|
318
|
+
reject(error);
|
|
319
|
+
}
|
|
320
|
+
else if (resolve) {
|
|
321
|
+
resolve();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/** (Re)start the idle watchdog for the current turn. */
|
|
325
|
+
armIdleWatchdog() {
|
|
326
|
+
if (this.turnIdleTimeoutMs <= 0) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
this.clearIdleWatchdog();
|
|
330
|
+
this.idleTimer = setTimeout(() => {
|
|
331
|
+
if (!this.turnActive) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
this.emit("event", {
|
|
335
|
+
kind: "turn-failed",
|
|
336
|
+
message: `codex app-server produced no activity for ${this.turnIdleTimeoutMs}ms`,
|
|
337
|
+
});
|
|
338
|
+
this.settleTurn();
|
|
339
|
+
}, this.turnIdleTimeoutMs);
|
|
340
|
+
this.idleTimer.unref?.();
|
|
341
|
+
}
|
|
342
|
+
clearIdleWatchdog() {
|
|
343
|
+
if (this.idleTimer) {
|
|
344
|
+
clearTimeout(this.idleTimer);
|
|
345
|
+
this.idleTimer = null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function numberOr(value, fallback) {
|
|
350
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=AppServerCodexBackend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppServerCodexBackend.js","sourceRoot":"","sources":["../../src/backend/AppServerCodexBackend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
/** Handles a server→client notification (no response expected). */
|
|
3
|
+
export type NotificationHandler = (method: string, params: unknown) => void;
|
|
4
|
+
/** Handles a server→client request; the returned value becomes the response. */
|
|
5
|
+
export type ServerRequestHandler = (method: string, params: unknown) => unknown | Promise<unknown>;
|
|
6
|
+
export interface AppServerClientOptions {
|
|
7
|
+
binaryPath: string;
|
|
8
|
+
args?: string[];
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
/** Optional logger; defaults to console. */
|
|
11
|
+
logger?: Pick<typeof console, "warn" | "error">;
|
|
12
|
+
/**
|
|
13
|
+
* Per-request timeout in milliseconds for control-plane calls
|
|
14
|
+
* (`initialize`, `thread/*`, `turn/start`, `turn/steer`, `turn/interrupt`).
|
|
15
|
+
* A wedged app-server then rejects the pending request instead of hanging
|
|
16
|
+
* the session forever. Defaults to 60s. Set to 0 to disable.
|
|
17
|
+
*/
|
|
18
|
+
requestTimeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The slice of {@link AppServerClient} the backend depends on. Declaring it lets
|
|
22
|
+
* tests inject a fake transport without spawning a process (Dependency
|
|
23
|
+
* Inversion).
|
|
24
|
+
*/
|
|
25
|
+
export interface IAppServerClient {
|
|
26
|
+
setNotificationHandler(handler: NotificationHandler): void;
|
|
27
|
+
setServerRequestHandler(handler: ServerRequestHandler): void;
|
|
28
|
+
on(event: "exit", listener: (...args: unknown[]) => void): unknown;
|
|
29
|
+
on(event: "error", listener: (...args: unknown[]) => void): unknown;
|
|
30
|
+
on(event: string, listener: (...args: unknown[]) => void): unknown;
|
|
31
|
+
start(): void;
|
|
32
|
+
request<T = unknown>(method: string, params: unknown): Promise<T>;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/** Factory used by the backend to create a client; overridable in tests. */
|
|
36
|
+
export type AppServerClientFactory = (options: AppServerClientOptions) => IAppServerClient;
|
|
37
|
+
/**
|
|
38
|
+
* Minimal JSON-RPC 2.0 client over a `codex app-server` child process speaking
|
|
39
|
+
* newline-delimited JSON on stdio. Single responsibility: framing + request
|
|
40
|
+
* correlation + dispatch of notifications/server-requests. Knows nothing about
|
|
41
|
+
* Codex semantics.
|
|
42
|
+
*/
|
|
43
|
+
export declare class AppServerClient extends EventEmitter {
|
|
44
|
+
private readonly options;
|
|
45
|
+
private child;
|
|
46
|
+
private rl;
|
|
47
|
+
private nextId;
|
|
48
|
+
private readonly pending;
|
|
49
|
+
private notificationHandler;
|
|
50
|
+
private serverRequestHandler;
|
|
51
|
+
private closed;
|
|
52
|
+
private readonly logger;
|
|
53
|
+
constructor(options: AppServerClientOptions);
|
|
54
|
+
setNotificationHandler(handler: NotificationHandler): void;
|
|
55
|
+
setServerRequestHandler(handler: ServerRequestHandler): void;
|
|
56
|
+
start(): void;
|
|
57
|
+
request<T = unknown>(method: string, params: unknown): Promise<T>;
|
|
58
|
+
/** Clear a pending request's timeout (called when settled). */
|
|
59
|
+
private settlePending;
|
|
60
|
+
notify(method: string, params: unknown): void;
|
|
61
|
+
close(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Terminate the child and any grandchildren. On POSIX the child was spawned
|
|
64
|
+
* `detached`, so it leads its own process group; signalling the negative pid
|
|
65
|
+
* reaps the whole group (the Node bin shim + the native codex binary) at
|
|
66
|
+
* once. Falls back to a direct kill if the group is already gone or on
|
|
67
|
+
* Windows (no process groups).
|
|
68
|
+
*/
|
|
69
|
+
private terminateChild;
|
|
70
|
+
private write;
|
|
71
|
+
private onLine;
|
|
72
|
+
private handleServerRequest;
|
|
73
|
+
private failAllPending;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=appServerClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appServerClient.d.ts","sourceRoot":"","sources":["../../src/backend/appServerClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import readline from "node:readline";
|
|
4
|
+
/** Default control-plane request timeout. Turn execution is awaited separately
|
|
5
|
+
* (via notifications), so this only bounds quick request/response calls. */
|
|
6
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
7
|
+
/**
|
|
8
|
+
* Minimal JSON-RPC 2.0 client over a `codex app-server` child process speaking
|
|
9
|
+
* newline-delimited JSON on stdio. Single responsibility: framing + request
|
|
10
|
+
* correlation + dispatch of notifications/server-requests. Knows nothing about
|
|
11
|
+
* Codex semantics.
|
|
12
|
+
*/
|
|
13
|
+
export class AppServerClient extends EventEmitter {
|
|
14
|
+
options;
|
|
15
|
+
child = null;
|
|
16
|
+
rl = null;
|
|
17
|
+
nextId = 1;
|
|
18
|
+
pending = new Map();
|
|
19
|
+
notificationHandler = null;
|
|
20
|
+
serverRequestHandler = null;
|
|
21
|
+
closed = false;
|
|
22
|
+
logger;
|
|
23
|
+
constructor(options) {
|
|
24
|
+
super();
|
|
25
|
+
this.options = options;
|
|
26
|
+
this.logger = options.logger ?? console;
|
|
27
|
+
}
|
|
28
|
+
setNotificationHandler(handler) {
|
|
29
|
+
this.notificationHandler = handler;
|
|
30
|
+
}
|
|
31
|
+
setServerRequestHandler(handler) {
|
|
32
|
+
this.serverRequestHandler = handler;
|
|
33
|
+
}
|
|
34
|
+
start() {
|
|
35
|
+
if (this.child) {
|
|
36
|
+
throw new Error("AppServerClient already started");
|
|
37
|
+
}
|
|
38
|
+
const args = this.options.args ?? ["app-server", "--listen", "stdio://"];
|
|
39
|
+
const child = spawn(this.options.binaryPath, args, {
|
|
40
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
41
|
+
// Run in its own process group (POSIX) so teardown can signal the whole
|
|
42
|
+
// tree at once. We launch codex via the `@openai/codex` Node bin shim,
|
|
43
|
+
// which spawns the native binary as a grandchild; killing only the shim
|
|
44
|
+
// would leave the native process to linger until its stdin pipe closes.
|
|
45
|
+
// Group-killing reaps both immediately. Not supported on Windows.
|
|
46
|
+
...(process.platform !== "win32" ? { detached: true } : {}),
|
|
47
|
+
...(this.options.env ? { env: this.options.env } : {}),
|
|
48
|
+
});
|
|
49
|
+
this.child = child;
|
|
50
|
+
child.once("error", (err) => {
|
|
51
|
+
this.failAllPending(err);
|
|
52
|
+
this.emit("error", err);
|
|
53
|
+
});
|
|
54
|
+
child.once("exit", (code, signal) => {
|
|
55
|
+
this.closed = true;
|
|
56
|
+
const reason = new Error(`codex app-server exited (code=${code ?? "null"}, signal=${signal ?? "null"})`);
|
|
57
|
+
this.failAllPending(reason);
|
|
58
|
+
this.emit("exit", code, signal);
|
|
59
|
+
});
|
|
60
|
+
child.stderr.on("data", (data) => {
|
|
61
|
+
const text = data.toString().trim();
|
|
62
|
+
if (text) {
|
|
63
|
+
this.emit("stderr", text);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
this.rl = readline.createInterface({
|
|
67
|
+
input: child.stdout,
|
|
68
|
+
crlfDelay: Number.POSITIVE_INFINITY,
|
|
69
|
+
});
|
|
70
|
+
this.rl.on("line", (line) => this.onLine(line));
|
|
71
|
+
}
|
|
72
|
+
request(method, params) {
|
|
73
|
+
if (this.closed || !this.child) {
|
|
74
|
+
return Promise.reject(new Error(`Cannot send ${method}: app-server is not running`));
|
|
75
|
+
}
|
|
76
|
+
const id = this.nextId++;
|
|
77
|
+
const message = { jsonrpc: "2.0", id, method, params };
|
|
78
|
+
const timeoutMs = this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const entry = {
|
|
81
|
+
resolve: resolve,
|
|
82
|
+
reject,
|
|
83
|
+
method,
|
|
84
|
+
};
|
|
85
|
+
if (timeoutMs > 0) {
|
|
86
|
+
entry.timer = setTimeout(() => {
|
|
87
|
+
if (this.pending.delete(id)) {
|
|
88
|
+
reject(new Error(`${method} timed out after ${timeoutMs}ms`));
|
|
89
|
+
}
|
|
90
|
+
}, timeoutMs);
|
|
91
|
+
// Don't keep the event loop alive solely for this timer.
|
|
92
|
+
entry.timer.unref?.();
|
|
93
|
+
}
|
|
94
|
+
this.pending.set(id, entry);
|
|
95
|
+
this.write(message);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** Clear a pending request's timeout (called when settled). */
|
|
99
|
+
settlePending(id) {
|
|
100
|
+
const entry = this.pending.get(id);
|
|
101
|
+
if (entry) {
|
|
102
|
+
this.pending.delete(id);
|
|
103
|
+
if (entry.timer) {
|
|
104
|
+
clearTimeout(entry.timer);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return entry;
|
|
108
|
+
}
|
|
109
|
+
notify(method, params) {
|
|
110
|
+
if (this.closed || !this.child) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.write({ jsonrpc: "2.0", method, params });
|
|
114
|
+
}
|
|
115
|
+
async close() {
|
|
116
|
+
this.closed = true;
|
|
117
|
+
this.rl?.close();
|
|
118
|
+
this.rl = null;
|
|
119
|
+
const child = this.child;
|
|
120
|
+
this.child = null;
|
|
121
|
+
if (child && !child.killed) {
|
|
122
|
+
this.terminateChild(child);
|
|
123
|
+
}
|
|
124
|
+
this.failAllPending(new Error("app-server client closed"));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Terminate the child and any grandchildren. On POSIX the child was spawned
|
|
128
|
+
* `detached`, so it leads its own process group; signalling the negative pid
|
|
129
|
+
* reaps the whole group (the Node bin shim + the native codex binary) at
|
|
130
|
+
* once. Falls back to a direct kill if the group is already gone or on
|
|
131
|
+
* Windows (no process groups).
|
|
132
|
+
*/
|
|
133
|
+
terminateChild(child) {
|
|
134
|
+
try {
|
|
135
|
+
if (process.platform !== "win32" && typeof child.pid === "number") {
|
|
136
|
+
process.kill(-child.pid, "SIGTERM");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Group already dead or unavailable — fall through to a direct kill.
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
child.kill();
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// best-effort
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
write(message) {
|
|
151
|
+
try {
|
|
152
|
+
this.child?.stdin.write(`${JSON.stringify(message)}\n`);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
this.logger.error("[AppServerClient] write failed", err);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
onLine(line) {
|
|
159
|
+
const trimmed = line.trim();
|
|
160
|
+
if (!trimmed) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
let message;
|
|
164
|
+
try {
|
|
165
|
+
message = JSON.parse(trimmed);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
this.logger.warn(`[AppServerClient] non-JSON line: ${trimmed.slice(0, 200)}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Response to a client request.
|
|
172
|
+
if (message.id !== undefined &&
|
|
173
|
+
(message.result !== undefined || message.error !== undefined)) {
|
|
174
|
+
const entry = this.settlePending(message.id);
|
|
175
|
+
if (!entry) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (message.error) {
|
|
179
|
+
entry.reject(new Error(`${entry.method} failed: ${message.error.message ?? "unknown error"}`));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
entry.resolve(message.result);
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Server→client request (expects a response).
|
|
187
|
+
if (message.id !== undefined && message.method) {
|
|
188
|
+
void this.handleServerRequest(message.id, message.method, message.params);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Notification.
|
|
192
|
+
if (message.method) {
|
|
193
|
+
try {
|
|
194
|
+
this.notificationHandler?.(message.method, message.params);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
this.logger.error(`[AppServerClient] notification handler threw for ${message.method}`, err);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async handleServerRequest(id, method, params) {
|
|
202
|
+
let result = {};
|
|
203
|
+
try {
|
|
204
|
+
if (this.serverRequestHandler) {
|
|
205
|
+
result = (await this.serverRequestHandler(method, params)) ?? {};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
this.logger.error(`[AppServerClient] server-request handler threw for ${method}`, err);
|
|
210
|
+
}
|
|
211
|
+
this.write({ jsonrpc: "2.0", id, result });
|
|
212
|
+
}
|
|
213
|
+
failAllPending(reason) {
|
|
214
|
+
for (const [, entry] of this.pending) {
|
|
215
|
+
if (entry.timer) {
|
|
216
|
+
clearTimeout(entry.timer);
|
|
217
|
+
}
|
|
218
|
+
entry.reject(reason);
|
|
219
|
+
}
|
|
220
|
+
this.pending.clear();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=appServerClient.js.map
|