@agentknight/sdk 0.9.42 → 0.9.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1249 @@
1
+ import {
2
+ latestPreviewItems
3
+ } from "./chunk-DB3EAMYS.js";
4
+ import {
5
+ UnsupportedAgentOperationError
6
+ } from "./chunk-ULM5KUJ7.js";
7
+ import {
8
+ ConversationItemSchema,
9
+ InteractionRequestSchema,
10
+ SESSION_TIMELINE_PAGE_SIZE,
11
+ SessionSummarySchema
12
+ } from "./chunk-7QOBDG6R.js";
13
+
14
+ // src/codex-adapter.ts
15
+ import { randomUUID } from "node:crypto";
16
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
17
+ import { isAbsolute, join as join2, resolve } from "node:path";
18
+ import { z as z4 } from "zod";
19
+
20
+ // src/adapters/codex/rpc.ts
21
+ import { spawn } from "node:child_process";
22
+ import { z as z2 } from "zod";
23
+
24
+ // src/adapters/codex/executable.ts
25
+ import { readFileSync, realpathSync, statSync } from "node:fs";
26
+ import { createRequire } from "node:module";
27
+ import { dirname, join, win32 } from "node:path";
28
+ import { z } from "zod";
29
+ function isFile(path) {
30
+ try {
31
+ return statSync(path).isFile();
32
+ } catch (error) {
33
+ if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) return false;
34
+ throw new Error("Cannot inspect the configured Codex installation", { cause: error });
35
+ }
36
+ }
37
+ var Package = z.object({
38
+ name: z.literal("@openai/codex"),
39
+ bin: z.object({ codex: z.literal("bin/codex.js") })
40
+ });
41
+ var unsupportedShim = () => new Error(
42
+ "Codex was found as a Windows script shim, but its installed native executable could not be resolved. Set AGENTKNIGHT_CODEX_COMMAND to the full path of codex.exe. Script wrappers are not executed through a shell."
43
+ );
44
+ function nativeFromNpmShim(shim, architecture) {
45
+ const filename = win32.basename(shim).toLowerCase();
46
+ if (!["codex", "codex.cmd", "codex.bat", "codex.ps1"].includes(filename)) throw unsupportedShim();
47
+ const triple = architecture === "x64" ? "x86_64-pc-windows-msvc" : architecture === "arm64" ? "aarch64-pc-windows-msvc" : void 0;
48
+ if (!triple) throw unsupportedShim();
49
+ const shimDirectory = dirname(shim);
50
+ const roots = [
51
+ join(shimDirectory, "node_modules", "@openai", "codex"),
52
+ ...win32.basename(shimDirectory).toLowerCase() === ".bin" ? [join(shimDirectory, "..", "@openai", "codex")] : []
53
+ ];
54
+ for (const root of roots) {
55
+ const manifest = join(root, "package.json");
56
+ if (!isFile(manifest)) continue;
57
+ let raw;
58
+ try {
59
+ raw = JSON.parse(readFileSync(manifest, "utf8"));
60
+ } catch {
61
+ throw unsupportedShim();
62
+ }
63
+ if (!Package.safeParse(raw).success) throw unsupportedShim();
64
+ const packageRoot = realpathSync(root);
65
+ const vendorRoots = [join(packageRoot, "vendor")];
66
+ try {
67
+ const require2 = createRequire(join(packageRoot, "package.json"));
68
+ const platformManifest = require2.resolve(`@openai/codex-win32-${architecture}/package.json`);
69
+ vendorRoots.unshift(join(dirname(platformManifest), "vendor"));
70
+ } catch (error) {
71
+ if (!(error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND")) {
72
+ throw new Error("Cannot resolve the installed Codex native package", { cause: error });
73
+ }
74
+ }
75
+ for (const vendor of vendorRoots) {
76
+ const executable = join(vendor, triple, "bin", "codex.exe");
77
+ if (isFile(executable)) return realpathSync(executable);
78
+ }
79
+ }
80
+ throw unsupportedShim();
81
+ }
82
+ function resolveCodexExecutable(command, options = {}) {
83
+ if ((options.platform ?? process.platform) !== "win32") return command;
84
+ const environment = options.environment ?? process.env;
85
+ const architecture = options.architecture ?? process.arch;
86
+ const explicit = win32.isAbsolute(command) || command.includes("/") || command.includes("\\");
87
+ const pathValue = Object.entries(environment).find(([key]) => key.toLowerCase() === "path")?.[1] ?? "";
88
+ const bases = explicit ? [command] : pathValue.split(";").filter(Boolean).map((entry) => join(entry.replace(/^"(.*)"$/, "$1"), command));
89
+ const extensions = win32.extname(command) ? [""] : [".exe", ".com", ".cmd", ".bat", ".ps1", ""];
90
+ for (const base2 of bases) {
91
+ for (const extension of extensions) {
92
+ const path = `${base2}${extension}`;
93
+ if (!isFile(path)) continue;
94
+ if ([".exe", ".com"].includes(win32.extname(path).toLowerCase())) return realpathSync(path);
95
+ return nativeFromNpmShim(path, architecture);
96
+ }
97
+ }
98
+ throw new Error(
99
+ "Codex executable was not found. Install Codex or set AGENTKNIGHT_CODEX_COMMAND to the full path of codex.exe."
100
+ );
101
+ }
102
+
103
+ // src/adapters/codex/rpc.ts
104
+ var spawnCodex = (command, args) => spawn(resolveCodexExecutable(command), [...args], { stdio: "pipe", windowsHide: true, shell: false });
105
+ var Id = z2.union([z2.string(), z2.number().int()]);
106
+ var Envelope = z2.object({
107
+ id: Id.optional(),
108
+ method: z2.string().optional(),
109
+ params: z2.unknown().optional(),
110
+ result: z2.unknown().optional(),
111
+ error: z2.object({ code: z2.number(), message: z2.string() }).optional()
112
+ });
113
+ var CodexRpcError = class extends Error {
114
+ constructor(code) {
115
+ super(`Codex rejected the request (RPC ${code})`);
116
+ this.code = code;
117
+ }
118
+ code;
119
+ };
120
+ var CodexRpc = class {
121
+ constructor(child, options) {
122
+ this.child = child;
123
+ this.options = options;
124
+ child.stdout.on("data", this.#data);
125
+ child.stdout.on("end", this.#end);
126
+ child.stdout.on("error", this.#streamError);
127
+ child.stdin.on("error", this.#streamError);
128
+ child.stderr.on("error", this.#streamError);
129
+ child.stderr.on("data", this.#stderr);
130
+ child.on("error", this.#streamError);
131
+ child.on("exit", this.#exit);
132
+ child.once("close", this.#processClosed);
133
+ }
134
+ child;
135
+ options;
136
+ #pending = /* @__PURE__ */ new Map();
137
+ #buffer = Buffer.alloc(0);
138
+ #sequence = 0;
139
+ #closed = false;
140
+ #exited = false;
141
+ #exitWaiters = /* @__PURE__ */ new Set();
142
+ #stderrReported = false;
143
+ get pendingCount() {
144
+ return this.#pending.size;
145
+ }
146
+ get closed() {
147
+ return this.#closed;
148
+ }
149
+ waitForExit(timeoutMs) {
150
+ if (this.#exited) return Promise.resolve();
151
+ return new Promise((resolve2, reject) => {
152
+ const finish = () => {
153
+ clearTimeout(timer);
154
+ this.#exitWaiters.delete(finish);
155
+ resolve2();
156
+ };
157
+ const timer = setTimeout(() => {
158
+ this.#exitWaiters.delete(finish);
159
+ reject(new Error("Codex process termination was not confirmed"));
160
+ }, timeoutMs);
161
+ this.#exitWaiters.add(finish);
162
+ });
163
+ }
164
+ request(method, params) {
165
+ if (this.#closed) return Promise.reject(new Error("Codex transport is closed"));
166
+ const id = ++this.#sequence;
167
+ return new Promise((resolve2, reject) => {
168
+ const timer = setTimeout(() => this.fail(new Error("Codex request timed out; outcome unknown")), this.options.timeoutMs);
169
+ this.#pending.set(id, { resolve: resolve2, reject, timer });
170
+ try {
171
+ this.#write({ id, method, params });
172
+ } catch {
173
+ this.fail(new Error("Codex request write failed; outcome unknown"));
174
+ }
175
+ });
176
+ }
177
+ notify(method, params) {
178
+ this.#write({ method, ...params === void 0 ? {} : { params } });
179
+ }
180
+ respond(id, result) {
181
+ this.#write({ id, result });
182
+ }
183
+ reject(id, code = -32601) {
184
+ this.#write({ id, error: { code, message: "Request unsupported or unavailable" } });
185
+ }
186
+ #write(value) {
187
+ if (this.#closed) throw new Error("Codex transport is closed");
188
+ const line = JSON.stringify(value) + "\n";
189
+ if (Buffer.byteLength(line) > this.options.maxFrameBytes) throw new Error("Codex outgoing frame exceeds limit");
190
+ this.child.stdin.write(line);
191
+ }
192
+ #data = (chunk) => {
193
+ if (this.#closed) return;
194
+ if (!Buffer.isBuffer(chunk) && typeof chunk !== "string") {
195
+ this.fail(new Error("Invalid Codex stream chunk"));
196
+ return;
197
+ }
198
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
199
+ let start = 0;
200
+ while (start < bytes.length && !this.#closed) {
201
+ const newline = bytes.indexOf(10, start);
202
+ const end = newline < 0 ? bytes.length : newline;
203
+ if (this.#buffer.length + end - start > this.options.maxFrameBytes) {
204
+ this.fail(new Error("Codex JSONL frame exceeds limit"));
205
+ return;
206
+ }
207
+ this.#buffer = Buffer.concat([this.#buffer, bytes.subarray(start, end)]);
208
+ if (newline < 0) return;
209
+ const line = this.#buffer.toString("utf8").trim();
210
+ this.#buffer = Buffer.alloc(0);
211
+ start = newline + 1;
212
+ if (!line) continue;
213
+ try {
214
+ const raw = JSON.parse(line);
215
+ const message = Envelope.parse(raw);
216
+ if (message.method !== void 0) {
217
+ this.options.onMessage(message.method, message.params, message.id);
218
+ } else if (message.id !== void 0) {
219
+ const pending = this.#pending.get(message.id);
220
+ if (!pending) continue;
221
+ if (message.error === void 0 && !Object.prototype.hasOwnProperty.call(raw, "result")) {
222
+ throw new Error("Missing RPC result");
223
+ }
224
+ this.#pending.delete(message.id);
225
+ clearTimeout(pending.timer);
226
+ if (message.error) pending.reject(new CodexRpcError(message.error.code));
227
+ else pending.resolve(message.result);
228
+ } else throw new Error("Invalid RPC envelope");
229
+ } catch {
230
+ this.fail(new Error("Invalid Codex JSONL protocol frame"));
231
+ }
232
+ }
233
+ };
234
+ #end = () => {
235
+ this.fail(new Error("Codex stdout closed; outcome unknown"));
236
+ };
237
+ #observeExit() {
238
+ this.#exited = true;
239
+ for (const finish of this.#exitWaiters) finish();
240
+ }
241
+ #exit = () => {
242
+ this.#observeExit();
243
+ this.fail(new Error("Codex process exited; outcome unknown"));
244
+ };
245
+ #processClosed = () => {
246
+ this.#observeExit();
247
+ this.fail(new Error("Codex process closed; outcome unknown"));
248
+ this.child.stdout.off("error", this.#streamError);
249
+ this.child.stdin.off("error", this.#streamError);
250
+ this.child.stderr.off("error", this.#streamError);
251
+ this.child.off("error", this.#streamError);
252
+ this.child.off("exit", this.#exit);
253
+ };
254
+ #streamError = () => {
255
+ this.fail(new Error("Codex process I/O failed; outcome unknown"));
256
+ };
257
+ #stderr = () => {
258
+ if (!this.#closed && !this.#stderrReported) {
259
+ this.#stderrReported = true;
260
+ this.options.onDiagnostic?.("Codex emitted stderr (content withheld)");
261
+ }
262
+ };
263
+ fail(error) {
264
+ if (this.#closed) return;
265
+ this.close();
266
+ this.options.onFailure(error);
267
+ }
268
+ close() {
269
+ if (this.#closed) return;
270
+ this.#closed = true;
271
+ this.#buffer = Buffer.alloc(0);
272
+ for (const pending of this.#pending.values()) {
273
+ clearTimeout(pending.timer);
274
+ pending.reject(new Error("Codex transport closed; outcome unknown"));
275
+ }
276
+ this.#pending.clear();
277
+ this.child.stdout.off("data", this.#data);
278
+ this.child.stdout.off("end", this.#end);
279
+ this.child.stderr.off("data", this.#stderr);
280
+ this.child.stdin.end();
281
+ this.child.kill();
282
+ }
283
+ };
284
+
285
+ // src/adapters/codex/protocol.ts
286
+ import { z as z3 } from "zod";
287
+ var NativeId = z3.string().min(1).max(256);
288
+ var NativeItem = z3.object({
289
+ id: NativeId,
290
+ type: z3.string(),
291
+ clientId: z3.string().nullable().optional(),
292
+ text: z3.string().optional(),
293
+ content: z3.array(z3.object({ type: z3.string(), text: z3.string().optional() })).optional(),
294
+ command: z3.string().optional(),
295
+ tool: z3.string().optional(),
296
+ status: z3.string().optional(),
297
+ aggregatedOutput: z3.string().nullable().optional(),
298
+ changes: z3.array(z3.object({ path: z3.string(), diff: z3.string() })).optional()
299
+ });
300
+ var NativeTurn = z3.object({
301
+ id: NativeId,
302
+ status: z3.enum(["inProgress", "completed", "interrupted", "failed"]),
303
+ items: z3.array(NativeItem)
304
+ });
305
+ var ThreadResult = z3.object({
306
+ thread: z3.object({
307
+ id: NativeId,
308
+ status: z3.object({ type: z3.enum(["notLoaded", "idle", "active", "systemError"]) }),
309
+ turns: z3.array(NativeTurn)
310
+ })
311
+ });
312
+ var EventContext = z3.object({ threadId: NativeId, turnId: NativeId });
313
+ var ItemEvent = EventContext.extend({ item: NativeItem });
314
+ var DeltaEvent = EventContext.extend({ itemId: NativeId, delta: z3.string() });
315
+ var TurnEvent = z3.object({ threadId: NativeId, turn: NativeTurn });
316
+ var Approval = EventContext.extend({
317
+ itemId: NativeId,
318
+ reason: z3.string().nullable().optional(),
319
+ command: z3.string().nullable().optional()
320
+ });
321
+ var Questions = EventContext.extend({
322
+ itemId: NativeId,
323
+ questions: z3.array(z3.object({
324
+ id: NativeId,
325
+ question: z3.string().min(1).max(4096),
326
+ isOther: z3.boolean(),
327
+ isSecret: z3.boolean(),
328
+ options: z3.array(z3.object({ label: z3.string().max(1024) })).max(100).nullable()
329
+ })).min(1).max(20)
330
+ });
331
+
332
+ // src/adapters/codex/timeline.ts
333
+ import { createHash } from "node:crypto";
334
+ var hash = (value) => createHash("sha256").update(value).digest("hex");
335
+ var itemId = (threadId, nativeId) => `codex:${hash(`${threadId}:${nativeId}`)}`;
336
+ function base(threadId, turnId, nativeId) {
337
+ return {
338
+ id: itemId(threadId, nativeId),
339
+ turnId,
340
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
341
+ revision: 1,
342
+ sourceEventIds: [nativeId]
343
+ };
344
+ }
345
+ function projectItem(threadId, turnId, item, completed) {
346
+ const common = base(threadId, turnId, item.id);
347
+ switch (item.type) {
348
+ case "userMessage":
349
+ return ConversationItemSchema.parse({
350
+ ...common,
351
+ kind: "human-message",
352
+ actor: "human",
353
+ content: (item.content ?? []).filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n"),
354
+ attachments: [],
355
+ sourceMessageIds: item.clientId ? [item.id, item.clientId] : [item.id]
356
+ });
357
+ case "agentMessage":
358
+ return ConversationItemSchema.parse({
359
+ ...common,
360
+ kind: "assistant-message",
361
+ actor: "assistant",
362
+ content: item.text ?? "",
363
+ final: completed
364
+ });
365
+ case "commandExecution":
366
+ case "fileChange":
367
+ case "mcpToolCall":
368
+ case "dynamicToolCall":
369
+ case "collabAgentToolCall": {
370
+ const failed = item.status === "failed" || item.status === "declined";
371
+ const status = failed ? "failed" : completed ? "completed" : "running";
372
+ return ConversationItemSchema.parse({
373
+ ...common,
374
+ kind: "activity-group",
375
+ actor: "assistant",
376
+ status,
377
+ steps: [{
378
+ id: item.id,
379
+ kind: "tool",
380
+ name: (item.command ?? item.tool ?? item.type).slice(0, 256) || item.type,
381
+ status,
382
+ timestamp: common.timestamp
383
+ }]
384
+ });
385
+ }
386
+ default:
387
+ return void 0;
388
+ }
389
+ }
390
+
391
+ // src/codex-adapter.ts
392
+ var CODEX_DEFAULT_MODEL_ID = "codex:agent-default";
393
+ var StoredSession = z4.object({
394
+ summary: SessionSummarySchema,
395
+ modelId: z4.string(),
396
+ items: z4.array(ConversationItemSchema),
397
+ uncertain: z4.boolean()
398
+ });
399
+ var Inventory = z4.object({
400
+ version: z4.literal(1),
401
+ identity: z4.string(),
402
+ sessions: z4.array(StoredSession)
403
+ });
404
+ var CodexCodingAgentAdapter = class {
405
+ agentId = "codex";
406
+ sdkVersion = "app-server-v2 (rust-v0.160.0)";
407
+ agentModes = ["interactive"];
408
+ supportsReasoningEffort = false;
409
+ supportsFileAttachments = false;
410
+ supportsSessionDelete = false;
411
+ supportsSessionListUpdates = true;
412
+ supportsAssistantPreview = true;
413
+ groupChatEnabled = false;
414
+ #options;
415
+ #identity;
416
+ #directory;
417
+ #sessions = /* @__PURE__ */ new Map();
418
+ #pending = /* @__PURE__ */ new Map();
419
+ #callbacks;
420
+ #rpc;
421
+ #opening;
422
+ #starting;
423
+ #stopping;
424
+ #lifecycle = 0;
425
+ #started = false;
426
+ #fatal;
427
+ #saveRunning;
428
+ #saveVersion = 0;
429
+ #operations = 0;
430
+ constructor(options) {
431
+ for (const limit of [options.requestTimeoutMs, options.interactionTimeoutMs, options.maxFrameBytes]) {
432
+ if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1)) throw new RangeError("Codex limits must be positive integers");
433
+ }
434
+ const command = options.command ?? "codex";
435
+ if (!command.trim() || command.includes("\0")) throw new Error("Invalid Codex executable");
436
+ this.#options = { ...options, command, args: [...options.args ?? ["app-server"]] };
437
+ this.#identity = hash(JSON.stringify([options.ownerId ?? "host", command, this.#options.args]));
438
+ this.#directory = join2(resolve(options.dataDirectory), "codex", this.#identity);
439
+ }
440
+ async start() {
441
+ if (this.#started) return;
442
+ if (this.#starting) return this.#starting;
443
+ if (this.#stopping) throw new Error("Codex adapter is stopping");
444
+ if (this.#fatal) throw new Error("Create a new Codex adapter after a transport failure");
445
+ const work = this.#start(++this.#lifecycle);
446
+ this.#starting = work;
447
+ try {
448
+ await work;
449
+ } finally {
450
+ this.#starting = void 0;
451
+ }
452
+ }
453
+ async #start(generation) {
454
+ await mkdir(this.#directory, { recursive: true, mode: 448 });
455
+ let stored;
456
+ try {
457
+ stored = JSON.parse(await readFile(join2(this.#directory, "inventory.json"), "utf8"));
458
+ } catch (error) {
459
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
460
+ }
461
+ if (generation !== this.#lifecycle) throw new Error("Codex startup cancelled");
462
+ if (stored !== void 0) {
463
+ const inventory = Inventory.parse(stored);
464
+ if (inventory.identity !== this.#identity) throw new Error("Codex inventory owner mismatch");
465
+ const restored = /* @__PURE__ */ new Map();
466
+ for (const data of inventory.sessions) {
467
+ if (restored.has(data.summary.sessionId)) throw new Error("Duplicate Codex owned thread");
468
+ restored.set(data.summary.sessionId, this.#runtime(data));
469
+ }
470
+ for (const [id, session] of restored) this.#sessions.set(id, session);
471
+ }
472
+ this.#started = true;
473
+ }
474
+ #runtime(data) {
475
+ return {
476
+ ...data,
477
+ loaded: false,
478
+ active: void 0,
479
+ sending: false,
480
+ aborting: false,
481
+ queue: [],
482
+ epoch: 0,
483
+ lock: Promise.resolve(),
484
+ completed: /* @__PURE__ */ new Set(),
485
+ settled: /* @__PURE__ */ new Set(),
486
+ pumpScheduled: false
487
+ };
488
+ }
489
+ async stop() {
490
+ if (this.#stopping) return this.#stopping;
491
+ const work = this.#stop();
492
+ this.#stopping = work;
493
+ try {
494
+ await work;
495
+ } finally {
496
+ this.#stopping = void 0;
497
+ }
498
+ }
499
+ async #stop() {
500
+ this.#lifecycle++;
501
+ if (!this.#started && !this.#rpc) {
502
+ if (this.#starting) await Promise.allSettled([this.#starting]);
503
+ return;
504
+ }
505
+ this.#started = false;
506
+ for (const session of this.#sessions.values()) {
507
+ session.epoch++;
508
+ session.queue = [];
509
+ if (session.active || session.sending) session.uncertain = true;
510
+ }
511
+ this.#dismiss();
512
+ const rpc = this.#rpc;
513
+ rpc?.close();
514
+ await Promise.allSettled([
515
+ ...this.#opening ? [this.#opening] : [],
516
+ ...[...this.#sessions.values()].map((session) => session.lock)
517
+ ]);
518
+ await this.#save();
519
+ try {
520
+ await rpc?.waitForExit(this.#options.requestTimeoutMs ?? 3e4);
521
+ } catch (error) {
522
+ this.#fatal = new Error("Codex shutdown could not confirm native process termination", { cause: error });
523
+ for (const session of this.#sessions.values()) session.uncertain = true;
524
+ await this.#save();
525
+ throw this.#fatal;
526
+ }
527
+ this.#rpc = void 0;
528
+ this.#sessions.clear();
529
+ }
530
+ setSessionCallbacks(callbacks) {
531
+ this.#callbacks = callbacks;
532
+ }
533
+ get #events() {
534
+ return this.#started ? this.#callbacks : void 0;
535
+ }
536
+ #ready() {
537
+ if (!this.#started) throw new Error("Codex adapter is not started");
538
+ }
539
+ #known(id, requireShared = true) {
540
+ this.#ready();
541
+ const session = this.#sessions.get(id);
542
+ if (!session) throw new Error("Unknown or unowned Codex session");
543
+ if (requireShared && !session.summary.isRemote) throw new Error("Codex session is not shared");
544
+ return session;
545
+ }
546
+ isSessionVisible(id) {
547
+ return this.#started && this.#sessions.get(id)?.summary.isRemote === true;
548
+ }
549
+ async #native() {
550
+ this.#ready();
551
+ if (this.#fatal) throw this.#fatal;
552
+ if (this.#opening) return this.#opening;
553
+ if (this.#rpc) return this.#rpc;
554
+ const rpc = new CodexRpc(
555
+ (this.#options.spawn ?? spawnCodex)(this.#options.command ?? "codex", this.#options.args ?? ["app-server"]),
556
+ {
557
+ timeoutMs: this.#options.requestTimeoutMs ?? 3e4,
558
+ maxFrameBytes: this.#options.maxFrameBytes ?? 4 * 1024 * 1024,
559
+ onMessage: (method, params, id) => this.#message(method, params, id),
560
+ onFailure: (error) => this.#fail(error),
561
+ ...this.#options.onDiagnostic ? { onDiagnostic: this.#options.onDiagnostic } : {}
562
+ }
563
+ );
564
+ this.#rpc = rpc;
565
+ this.#opening = (async () => {
566
+ const response = await rpc.request("initialize", {
567
+ clientInfo: { name: "agentknight", title: "AgentKnight", version: "1" },
568
+ capabilities: { experimentalApi: true, requestAttestation: false, explicitGatewayOauth: true }
569
+ });
570
+ z4.object({ userAgent: z4.string() }).parse(response);
571
+ this.#ready();
572
+ rpc.notify("initialized");
573
+ return rpc;
574
+ })();
575
+ try {
576
+ return await this.#opening;
577
+ } catch (error) {
578
+ rpc.fail(new Error("Codex initialization failed"));
579
+ throw error;
580
+ } finally {
581
+ this.#opening = void 0;
582
+ }
583
+ }
584
+ #fail(error) {
585
+ this.#fatal ??= error;
586
+ this.#rpc?.close();
587
+ this.#dismiss();
588
+ for (const session of this.#sessions.values()) {
589
+ if (session.active || session.sending) session.uncertain = true;
590
+ session.queue = [];
591
+ for (const resolve2 of session.settled) resolve2();
592
+ session.settled.clear();
593
+ this.#events?.onSyncError(session.summary.sessionId, error);
594
+ }
595
+ void this.#save().catch(() => {
596
+ this.#options.onDiagnostic?.("Codex host inventory could not be persisted");
597
+ });
598
+ }
599
+ #save() {
600
+ this.#saveVersion++;
601
+ if (this.#saveRunning) return this.#saveRunning;
602
+ const work = (async () => {
603
+ let persisted = -1;
604
+ while (persisted !== this.#saveVersion) {
605
+ const version = this.#saveVersion;
606
+ const snapshot = JSON.stringify({
607
+ version: 1,
608
+ identity: this.#identity,
609
+ sessions: [...this.#sessions.values()].map(({ summary, modelId, items, uncertain }) => ({
610
+ summary,
611
+ modelId,
612
+ items,
613
+ uncertain
614
+ }))
615
+ });
616
+ const staging = join2(this.#directory, `inventory-${randomUUID()}.tmp`);
617
+ try {
618
+ await writeFile(staging, snapshot, { mode: 384, flag: "wx" });
619
+ await rename(staging, join2(this.#directory, "inventory.json"));
620
+ persisted = version;
621
+ } finally {
622
+ await rm(staging, { force: true });
623
+ }
624
+ }
625
+ })();
626
+ this.#saveRunning = work;
627
+ const saving = work.then(() => {
628
+ this.#saveRunning = void 0;
629
+ }, (error) => {
630
+ this.#saveRunning = void 0;
631
+ const failure = new Error("Codex host inventory persistence failed", { cause: error });
632
+ this.#fatal = failure;
633
+ this.#rpc?.close();
634
+ this.#dismiss();
635
+ for (const session of this.#sessions.values()) {
636
+ if (session.active || session.sending) session.uncertain = true;
637
+ session.queue = [];
638
+ for (const resolve2 of session.settled) resolve2();
639
+ session.settled.clear();
640
+ this.#events?.onSyncError(session.summary.sessionId, failure);
641
+ }
642
+ });
643
+ return saving.then(() => work);
644
+ }
645
+ async canUpgrade() {
646
+ return this.#started && !this.#fatal && !this.#opening && !this.#saveRunning && this.#operations === 0 && this.#pending.size === 0 && (this.#rpc?.pendingCount ?? 0) === 0 && [...this.#sessions.values()].every((s) => !s.uncertain && !s.active && !s.sending && !s.aborting && !s.queue.length);
647
+ }
648
+ async listSessions(userId, machineId) {
649
+ this.#ready();
650
+ const owned = [...this.#sessions.values()].filter((s) => s.summary.userId === userId && s.summary.machineId === machineId && s.summary.isRemote);
651
+ const rpc = this.#rpc;
652
+ if (owned.length && rpc && !rpc.closed && !this.#opening && !this.#fatal) {
653
+ this.#operations++;
654
+ try {
655
+ let cursor = null;
656
+ const seen = /* @__PURE__ */ new Set();
657
+ let changed = false;
658
+ do {
659
+ const result = z4.object({
660
+ data: z4.array(z4.object({ id: NativeId, preview: z4.string().optional(), name: z4.string().nullable().optional() })),
661
+ nextCursor: z4.string().nullable()
662
+ }).parse(await rpc.request("thread/list", {
663
+ cursor,
664
+ limit: 100,
665
+ useStateDbOnly: true,
666
+ cwd: [...new Set(owned.flatMap((session) => session.summary.workingDirectory ? [session.summary.workingDirectory] : []))]
667
+ }));
668
+ this.#ready();
669
+ for (const entry of result.data) {
670
+ const session = owned.find((candidate) => candidate.summary.sessionId === entry.id);
671
+ if (!session || !session.summary.isRemote) continue;
672
+ const summary = (entry.name ?? entry.preview)?.slice(0, 4096);
673
+ if (summary && summary !== session.summary.summary) {
674
+ session.summary.summary = summary;
675
+ changed = true;
676
+ }
677
+ }
678
+ cursor = result.nextCursor;
679
+ if (cursor !== null) {
680
+ if (seen.has(cursor) || seen.size >= 100) throw new Error("Codex thread pagination did not terminate");
681
+ seen.add(cursor);
682
+ }
683
+ } while (cursor !== null);
684
+ if (changed) await this.#save();
685
+ } finally {
686
+ this.#operations--;
687
+ }
688
+ }
689
+ return [...this.#sessions.values()].filter(
690
+ (s) => s.summary.userId === userId && s.summary.machineId === machineId && s.summary.isRemote
691
+ ).map((s) => ({ ...s.summary })).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt));
692
+ }
693
+ async listModels(sessionId) {
694
+ if (sessionId) this.#known(sessionId);
695
+ this.#operations++;
696
+ try {
697
+ const rpc = await this.#native();
698
+ const models = /* @__PURE__ */ new Map([
699
+ [CODEX_DEFAULT_MODEL_ID, { id: CODEX_DEFAULT_MODEL_ID, name: "Codex default", isDefault: true }]
700
+ ]);
701
+ let cursor = null;
702
+ const seen = /* @__PURE__ */ new Set();
703
+ do {
704
+ const result = z4.object({
705
+ data: z4.array(z4.object({
706
+ model: z4.string().min(1).max(256),
707
+ displayName: z4.string().min(1).max(260),
708
+ hidden: z4.boolean().optional()
709
+ })),
710
+ nextCursor: z4.string().nullable()
711
+ }).parse(await rpc.request("model/list", { cursor, limit: 100 }));
712
+ this.#ready();
713
+ for (const model of result.data) {
714
+ if (!model.hidden) models.set(model.model, { id: model.model, name: model.displayName, isDefault: false });
715
+ }
716
+ cursor = result.nextCursor;
717
+ if (cursor !== null) {
718
+ if (seen.has(cursor) || seen.size >= 100) throw new Error("Codex model pagination did not terminate");
719
+ seen.add(cursor);
720
+ }
721
+ } while (cursor !== null);
722
+ return [...models.values()];
723
+ } finally {
724
+ this.#operations--;
725
+ }
726
+ }
727
+ #validate(options) {
728
+ if (options.agentMode !== void 0 && options.agentMode !== "interactive") this.#unsupported("agent modes");
729
+ if (options.reasoningEffort !== void 0 && options.reasoningEffort !== "default") this.#unsupported("reasoning effort");
730
+ if (options.configOption) this.#unsupported("native configuration options");
731
+ }
732
+ #unsupported(operation) {
733
+ throw new UnsupportedAgentOperationError("codex", operation);
734
+ }
735
+ async createSession(options) {
736
+ this.#validate(options);
737
+ if (!isAbsolute(options.workingDirectory)) throw new Error("Codex working directory must be absolute");
738
+ this.#operations++;
739
+ try {
740
+ const rpc = await this.#native();
741
+ const result = ThreadResult.parse(await rpc.request("thread/start", {
742
+ cwd: options.workingDirectory,
743
+ ...options.modelId === CODEX_DEFAULT_MODEL_ID ? {} : { model: options.modelId },
744
+ approvalPolicy: "on-request",
745
+ approvalsReviewer: "user",
746
+ sandbox: "workspace-write",
747
+ ephemeral: false
748
+ }));
749
+ this.#ready();
750
+ if (this.#sessions.has(result.thread.id)) throw new Error("Codex reused an owned thread ID");
751
+ const now = (/* @__PURE__ */ new Date()).toISOString();
752
+ const summary = SessionSummarySchema.parse({
753
+ sessionId: result.thread.id,
754
+ userId: options.userId,
755
+ machineId: options.machineId,
756
+ workingDirectory: options.workingDirectory,
757
+ createdAt: now,
758
+ modifiedAt: now,
759
+ isRemote: true,
760
+ renameSupported: false
761
+ });
762
+ const session = this.#runtime({
763
+ summary,
764
+ modelId: options.modelId,
765
+ items: [],
766
+ uncertain: result.thread.status.type !== "idle" || result.thread.turns.some((turn) => turn.status === "inProgress")
767
+ });
768
+ session.loaded = true;
769
+ this.#sessions.set(summary.sessionId, session);
770
+ await this.#save();
771
+ this.#events?.onSessionInventoryChanged(summary.sessionId);
772
+ return { ...summary };
773
+ } finally {
774
+ this.#operations--;
775
+ }
776
+ }
777
+ async getSessionConfiguration(id) {
778
+ return { modelId: this.#known(id).modelId, agentMode: "interactive", reasoningEffort: "default" };
779
+ }
780
+ async configureSession(id, options) {
781
+ const session = this.#known(id);
782
+ this.#validate(options);
783
+ if (options.modelId !== void 0 && options.modelId !== session.modelId) this.#unsupported("changing a session model");
784
+ return this.getSessionConfiguration(id);
785
+ }
786
+ async getSessionDirectoryMetadata(id) {
787
+ const cwd = this.#known(id).summary.workingDirectory;
788
+ return cwd === void 0 ? {} : { workingDirectory: cwd };
789
+ }
790
+ async getSessionEnvironment(id) {
791
+ this.#known(id);
792
+ return { tools: [], skills: [], slashCommands: [] };
793
+ }
794
+ async getSessionRemoteStatus(id) {
795
+ const session = this.#known(id, false);
796
+ return { state: session.summary.isRemote ? "active" : "off", steerable: session.summary.isRemote && !session.uncertain && !this.#fatal };
797
+ }
798
+ async setSessionRemote(id, enabled) {
799
+ const session = this.#known(id, false);
800
+ session.summary.isRemote = enabled;
801
+ if (!enabled) await this.#abort(session);
802
+ await this.#save();
803
+ this.#events?.onSessionInventoryChanged(id);
804
+ return this.getSessionRemoteStatus(id);
805
+ }
806
+ async watchSession(id, _watched) {
807
+ this.#known(id);
808
+ }
809
+ async readSessionPlan(_id) {
810
+ return this.#unsupported("plan documents");
811
+ }
812
+ async setGroupChatEnabled(enabled) {
813
+ if (enabled) this.#unsupported("group chat");
814
+ }
815
+ async setPeerChatEnabled(enabled) {
816
+ if (enabled) this.#unsupported("peer chat");
817
+ }
818
+ async readSessionTimelinePage(id, options = {}) {
819
+ const session = this.#known(id);
820
+ const size = options.pageSize ?? SESSION_TIMELINE_PAGE_SIZE;
821
+ if (!Number.isInteger(size) || size < 1 || size > SESSION_TIMELINE_PAGE_SIZE) throw new RangeError("Invalid timeline page size");
822
+ let end = session.items.length;
823
+ let cursorStatus = "ok";
824
+ if (options.cursor !== void 0) {
825
+ const index = session.items.findIndex((item) => `${hash(id)}:${item.id}` === options.cursor);
826
+ if (index < 0) cursorStatus = "expired";
827
+ else end = index;
828
+ }
829
+ const start = Math.max(0, end - size);
830
+ const items = options.metadataOnly ? options.assistantPreview ? latestPreviewItems(session.items).slice(-size) : [] : session.items.slice(start, end);
831
+ return {
832
+ items: structuredClone(items),
833
+ cursor: `${hash(id)}:${session.items[start]?.id ?? "empty"}`,
834
+ hasMore: options.metadataOnly ? false : start > 0,
835
+ cursorStatus
836
+ };
837
+ }
838
+ #locked(session, run) {
839
+ this.#operations++;
840
+ const work = session.lock.then(run);
841
+ session.lock = work.then(() => {
842
+ this.#operations--;
843
+ }, () => {
844
+ this.#operations--;
845
+ });
846
+ return work;
847
+ }
848
+ async #load(session) {
849
+ if (session.loaded) return;
850
+ const rpc = await this.#native();
851
+ const threadId = session.summary.sessionId;
852
+ const read = ThreadResult.parse(await rpc.request("thread/read", { threadId, includeTurns: true }));
853
+ if (read.thread.id !== threadId || !["idle", "notLoaded"].includes(read.thread.status.type) || read.thread.turns.some((turn) => turn.status === "inProgress")) {
854
+ session.uncertain = true;
855
+ await this.#save();
856
+ throw new Error("Codex owned thread is not verifiably idle; refusing to attach");
857
+ }
858
+ this.#known(threadId);
859
+ if (session.aborting) throw new Error("Codex resume cancelled");
860
+ const resumed = ThreadResult.parse(await rpc.request("thread/resume", {
861
+ threadId,
862
+ cwd: session.summary.workingDirectory,
863
+ approvalPolicy: "on-request",
864
+ approvalsReviewer: "user",
865
+ sandbox: "workspace-write"
866
+ }));
867
+ if (resumed.thread.id !== threadId || resumed.thread.status.type !== "idle") {
868
+ session.uncertain = true;
869
+ await this.#save();
870
+ throw new Error("Codex resume did not confirm an idle owned thread");
871
+ }
872
+ for (const turn of resumed.thread.turns) {
873
+ for (const item of turn.items) {
874
+ const projected = projectItem(threadId, turn.id, item, turn.status !== "inProgress");
875
+ if (projected) this.#upsert(session, projected);
876
+ }
877
+ }
878
+ session.loaded = true;
879
+ }
880
+ async sendPrompt(id, prompt, mode, attachments = [], _persistInteractions = true, files = [], _operationId, persona) {
881
+ const session = this.#known(id);
882
+ if (attachments.length || files.length) this.#unsupported("attachments");
883
+ if (persona !== void 0) this.#unsupported("avatar personas");
884
+ if (mode !== "enqueue" && mode !== "immediate") this.#unsupported("delivery mode");
885
+ if (!prompt.trim() || prompt.length > 1e5) throw new Error("Invalid Codex prompt length");
886
+ const job = { id: randomUUID(), text: prompt, epoch: session.epoch };
887
+ return this.#locked(session, async () => {
888
+ this.#checkSend(session, job, mode === "enqueue" && session.active !== void 0);
889
+ if (mode === "enqueue" && (session.active || session.queue.length)) {
890
+ if (session.queue.length >= 100) throw new Error("Codex host queue is full");
891
+ session.queue.push(job);
892
+ return { messageId: job.id, deliveryState: "queued" };
893
+ }
894
+ return this.#dispatch(session, job);
895
+ });
896
+ }
897
+ #checkSend(session, job, allowPending = false) {
898
+ this.#known(session.summary.sessionId);
899
+ if (this.#fatal) throw this.#fatal;
900
+ if (session.aborting || session.epoch !== job.epoch) throw new Error("Codex prompt cancelled");
901
+ if (session.uncertain && !session.active && !session.sending) throw new Error("Codex outcome unknown; refusing automatic replay or new work");
902
+ if (!allowPending && [...this.#pending.values()].some((p) => p.session === session)) {
903
+ throw new Error("Codex session awaits an interaction");
904
+ }
905
+ }
906
+ async #dispatch(session, job) {
907
+ this.#checkSend(session, job);
908
+ await this.#load(session);
909
+ this.#checkSend(session, job);
910
+ const rpc = await this.#native();
911
+ this.#checkSend(session, job);
912
+ const expectedTurnId = session.active;
913
+ session.sending = true;
914
+ session.uncertain = true;
915
+ let transmitted = false;
916
+ try {
917
+ await this.#save();
918
+ this.#checkSend(session, job);
919
+ const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
920
+ const input = [{ type: "text", text: job.text, text_elements: [] }];
921
+ transmitted = true;
922
+ const response = expectedTurnId ? await rpc.request("turn/steer", { threadId: session.summary.sessionId, expectedTurnId, clientUserMessageId: job.id, input }) : await rpc.request("turn/start", { threadId: session.summary.sessionId, clientUserMessageId: job.id, input });
923
+ this.#ready();
924
+ const startedTurn = expectedTurnId ? void 0 : z4.object({ turn: NativeTurn }).parse(response).turn;
925
+ const turnId = startedTurn?.id ?? z4.object({ turnId: NativeId }).parse(response).turnId;
926
+ if (expectedTurnId && turnId !== expectedTurnId) throw new Error("Codex steer changed the expected turn");
927
+ if (startedTurn && startedTurn.status !== "inProgress") this.#complete(session, startedTurn);
928
+ else if (!session.completed.has(turnId)) session.active = turnId;
929
+ session.uncertain = session.active !== void 0;
930
+ return { messageId: job.id, deliveryState: "processing", submittedAt };
931
+ } catch (error) {
932
+ if (!transmitted || error instanceof CodexRpcError) {
933
+ session.uncertain = session.active !== void 0;
934
+ } else {
935
+ session.uncertain = true;
936
+ if (this.#started) this.#fail(new Error("Codex submission outcome unknown; no automatic replay"));
937
+ }
938
+ throw error;
939
+ } finally {
940
+ session.sending = false;
941
+ await this.#save();
942
+ this.#pump(session);
943
+ }
944
+ }
945
+ #pump(session) {
946
+ if (!this.#started || this.#fatal || session.active || session.sending || session.aborting || session.uncertain || session.pumpScheduled || !session.queue.length) return;
947
+ session.pumpScheduled = true;
948
+ void this.#locked(session, async () => {
949
+ if (session.active || session.aborting || session.uncertain) return;
950
+ const job = session.queue.shift();
951
+ if (job) await this.#dispatch(session, job);
952
+ }).catch((error) => {
953
+ session.queue = [];
954
+ this.#events?.onSyncError(session.summary.sessionId, error);
955
+ }).finally(() => {
956
+ session.pumpScheduled = false;
957
+ this.#pump(session);
958
+ });
959
+ }
960
+ async abortSession(id) {
961
+ await this.#abort(this.#known(id));
962
+ }
963
+ async #abort(session) {
964
+ session.aborting = true;
965
+ session.epoch++;
966
+ session.queue = [];
967
+ this.#dismiss(session);
968
+ try {
969
+ await session.lock;
970
+ if (session.active) {
971
+ const rpc = this.#rpc;
972
+ if (!rpc || rpc.closed) throw new Error("Codex interrupt cannot verify native settlement");
973
+ const turnId = session.active;
974
+ await rpc.request("turn/interrupt", { threadId: session.summary.sessionId, turnId });
975
+ if (session.active === turnId) {
976
+ await new Promise((resolve2, reject) => {
977
+ const finish = () => {
978
+ clearTimeout(timer);
979
+ session.settled.delete(finish);
980
+ resolve2();
981
+ };
982
+ const timer = setTimeout(() => {
983
+ session.settled.delete(finish);
984
+ reject(new Error("Codex interrupt settlement timed out"));
985
+ }, this.#options.requestTimeoutMs ?? 3e4);
986
+ session.settled.add(finish);
987
+ });
988
+ }
989
+ }
990
+ if (session.active || session.uncertain || this.#fatal) throw new Error("Codex cancellation outcome is unknown");
991
+ } catch (error) {
992
+ session.uncertain = true;
993
+ throw error;
994
+ } finally {
995
+ session.aborting = false;
996
+ await this.#save();
997
+ }
998
+ }
999
+ #upsert(session, value) {
1000
+ const index = session.items.findIndex((item2) => item2.id === value.id);
1001
+ const previous = session.items[index];
1002
+ const item = ConversationItemSchema.parse({
1003
+ ...value,
1004
+ revision: previous ? previous.revision + 1 : 1,
1005
+ timestamp: previous?.timestamp ?? value.timestamp
1006
+ });
1007
+ if (index < 0) session.items.push(item);
1008
+ else session.items[index] = item;
1009
+ session.summary.modifiedAt = (/* @__PURE__ */ new Date()).toISOString();
1010
+ if (session.summary.isRemote) this.#events?.onConversationItem(session.summary.sessionId, structuredClone(item));
1011
+ void this.#save().catch(() => {
1012
+ });
1013
+ }
1014
+ #message(method, params, rpcId) {
1015
+ if (!this.#started || this.#fatal) return;
1016
+ if (rpcId !== void 0) {
1017
+ this.#request(method, params, rpcId);
1018
+ return;
1019
+ }
1020
+ const context = z4.object({ threadId: NativeId }).safeParse(params);
1021
+ if (!context.success) return;
1022
+ const session = this.#sessions.get(context.data.threadId);
1023
+ if (session && method === "serverRequest/resolved") {
1024
+ const resolved = z4.object({ requestId: z4.union([z4.string(), z4.number().int()]) }).parse(params);
1025
+ for (const [id, pending] of this.#pending) {
1026
+ if (pending.session === session && pending.rpcId === resolved.requestId) this.#finish(id, void 0, false);
1027
+ }
1028
+ return;
1029
+ }
1030
+ if (session?.loaded && method === "thread/status/changed") {
1031
+ const status = z4.object({ status: z4.object({ type: z4.string() }) }).parse(params).status.type;
1032
+ if (status === "systemError" || status === "active" && !session.active && !session.sending) {
1033
+ session.uncertain = true;
1034
+ this.#fail(new Error("Codex owned thread has unverifiable native activity"));
1035
+ }
1036
+ return;
1037
+ }
1038
+ if (session?.loaded && method === "turn/started" && !session.active && !session.sending) {
1039
+ const { turn } = TurnEvent.parse(params);
1040
+ if (!session.completed.has(turn.id)) {
1041
+ session.uncertain = true;
1042
+ this.#fail(new Error("Codex started an unsolicited native turn"));
1043
+ }
1044
+ return;
1045
+ }
1046
+ if (!session || !session.loaded || !session.active && !session.sending) return;
1047
+ if (method === "turn/started" || method === "turn/completed") {
1048
+ const { turn } = TurnEvent.parse(params);
1049
+ if (session.active && session.active !== turn.id) return;
1050
+ if (method === "turn/started") {
1051
+ if (session.completed.has(turn.id)) return;
1052
+ session.active = turn.id;
1053
+ this.#events?.onSessionActivity?.(session.summary.sessionId, "working", (/* @__PURE__ */ new Date()).toISOString());
1054
+ } else {
1055
+ this.#complete(session, turn);
1056
+ void this.#save().then(() => this.#pump(session)).catch(() => {
1057
+ });
1058
+ }
1059
+ return;
1060
+ }
1061
+ const event = EventContext.safeParse(params);
1062
+ if (!event.success || session.completed.has(event.data.turnId) || session.active && event.data.turnId !== session.active) return;
1063
+ if (method === "item/started" || method === "item/completed") {
1064
+ const { turnId, item } = ItemEvent.parse(params);
1065
+ if (method === "item/started" && session.items.some((existing) => existing.id === itemId(session.summary.sessionId, item.id))) return;
1066
+ const projected = projectItem(session.summary.sessionId, turnId, item, method === "item/completed");
1067
+ if (projected) this.#upsert(session, projected);
1068
+ if (item.aggregatedOutput) this.#output(session, turnId, `${item.id}:output`, "Tool output", item.aggregatedOutput);
1069
+ if (item.changes) this.#output(
1070
+ session,
1071
+ turnId,
1072
+ `${item.id}:diff`,
1073
+ "File changes",
1074
+ item.changes.map((change) => `${change.path}
1075
+ ${change.diff}`).join("\n")
1076
+ );
1077
+ } else if (method === "item/agentMessage/delta") {
1078
+ const delta = DeltaEvent.parse(params);
1079
+ const old = session.items.find((item) => item.id === itemId(delta.threadId, delta.itemId));
1080
+ if (old?.kind === "assistant-message" && old.final) return;
1081
+ this.#upsert(session, ConversationItemSchema.parse({
1082
+ ...base(delta.threadId, delta.turnId, delta.itemId),
1083
+ kind: "assistant-message",
1084
+ actor: "assistant",
1085
+ content: ((old?.kind === "assistant-message" ? old.content : "") + delta.delta).slice(0, 5e5),
1086
+ final: false
1087
+ }));
1088
+ } else if (method === "item/commandExecution/outputDelta" || method === "item/fileChange/outputDelta") {
1089
+ const delta = DeltaEvent.parse(params);
1090
+ const nativeId = `${delta.itemId}:output`;
1091
+ const old = session.items.find((item) => item.id === itemId(delta.threadId, nativeId));
1092
+ this.#output(
1093
+ session,
1094
+ delta.turnId,
1095
+ nativeId,
1096
+ "Tool output",
1097
+ (old?.kind === "notice" ? old.content : "") + delta.delta
1098
+ );
1099
+ } else if (method === "turn/diff/updated") {
1100
+ const diff = EventContext.extend({ diff: z4.string() }).parse(params);
1101
+ this.#output(session, diff.turnId, `${diff.turnId}:diff`, "Turn diff", diff.diff);
1102
+ } else if (method === "item/fileChange/patchUpdated") {
1103
+ const patch = EventContext.extend({
1104
+ itemId: NativeId,
1105
+ changes: z4.array(z4.object({ path: z4.string(), diff: z4.string() }))
1106
+ }).parse(params);
1107
+ this.#output(
1108
+ session,
1109
+ patch.turnId,
1110
+ `${patch.itemId}:diff`,
1111
+ "File changes",
1112
+ patch.changes.map((change) => `${change.path}
1113
+ ${change.diff}`).join("\n")
1114
+ );
1115
+ } else if (method === "error") {
1116
+ const error = new Error("Codex reported a native turn error");
1117
+ this.#events?.onSyncError(session.summary.sessionId, error);
1118
+ }
1119
+ }
1120
+ #output(session, turnId, id, title, text) {
1121
+ this.#upsert(session, ConversationItemSchema.parse({
1122
+ ...base(session.summary.sessionId, turnId, id),
1123
+ kind: "notice",
1124
+ actor: "assistant",
1125
+ severity: "info",
1126
+ title,
1127
+ content: text.slice(-4096)
1128
+ }));
1129
+ }
1130
+ #complete(session, turn) {
1131
+ session.completed.add(turn.id);
1132
+ session.active = void 0;
1133
+ session.uncertain = false;
1134
+ for (const item of turn.items) {
1135
+ const projected = projectItem(session.summary.sessionId, turn.id, item, true);
1136
+ if (projected) this.#upsert(session, projected);
1137
+ }
1138
+ for (const item of [...session.items]) {
1139
+ if (item.turnId !== turn.id) continue;
1140
+ if (item.kind === "assistant-message" && !item.final) {
1141
+ this.#upsert(session, { ...item, final: true });
1142
+ } else if (item.kind === "activity-group" && item.status === "running") {
1143
+ const status = turn.status === "completed" ? "completed" : "failed";
1144
+ this.#upsert(session, { ...item, status, steps: item.steps.map((step) => step.kind !== "skill" && step.status === "running" ? { ...step, status } : step) });
1145
+ }
1146
+ }
1147
+ this.#dismiss(session);
1148
+ for (const resolve2 of session.settled) resolve2();
1149
+ session.settled.clear();
1150
+ this.#events?.onSessionActivity?.(session.summary.sessionId, "idle", (/* @__PURE__ */ new Date()).toISOString());
1151
+ }
1152
+ #request(method, params, rpcId) {
1153
+ const context = EventContext.safeParse(params);
1154
+ const session = context.success ? this.#sessions.get(context.data.threadId) : void 0;
1155
+ if (!session || !session.summary.isRemote || session.aborting || context.success && session.completed.has(context.data.turnId) || !session.active && !session.sending || session.active && context.success && session.active !== context.data.turnId) {
1156
+ this.#rpc?.reject(rpcId);
1157
+ return;
1158
+ }
1159
+ if ([...this.#pending.values()].some((pending2) => pending2.rpcId === rpcId)) return;
1160
+ let request;
1161
+ let questionId;
1162
+ if (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval") {
1163
+ const approval = Approval.parse(params);
1164
+ request = InteractionRequestSchema.parse({
1165
+ kind: "permission",
1166
+ permissionKind: method.includes("commandExecution") ? "shell" : "write",
1167
+ title: "Codex approval",
1168
+ description: (approval.reason ?? approval.command ?? "Approve this action?").slice(0, 4096),
1169
+ canApproveSession: false,
1170
+ canApproveAlways: false
1171
+ });
1172
+ } else if (method === "item/tool/requestUserInput") {
1173
+ const questions = Questions.parse(params);
1174
+ const question = questions.questions[0];
1175
+ if (questions.questions.length !== 1 || !question || question.isSecret) {
1176
+ this.#rpc?.reject(rpcId);
1177
+ return;
1178
+ }
1179
+ questionId = question.id;
1180
+ request = InteractionRequestSchema.parse({
1181
+ kind: "user_input",
1182
+ question: question.question,
1183
+ choices: question.options?.map((option) => option.label) ?? [],
1184
+ allowFreeform: question.isOther || !question.options?.length
1185
+ });
1186
+ } else {
1187
+ this.#rpc?.reject(rpcId);
1188
+ return;
1189
+ }
1190
+ const id = randomUUID();
1191
+ const pending = {
1192
+ session,
1193
+ rpcId,
1194
+ request,
1195
+ ...questionId ? { questionId } : {},
1196
+ timer: setTimeout(() => this.#finish(id), this.#options.interactionTimeoutMs ?? 12e4)
1197
+ };
1198
+ this.#pending.set(id, pending);
1199
+ this.#publish(id, pending);
1200
+ }
1201
+ #publish(id, pending) {
1202
+ if (!pending.session.summary.isRemote) return;
1203
+ if (this.#events?.onInteractionRequest) {
1204
+ this.#events.onInteractionRequest(pending.session.summary.sessionId, id, pending.request);
1205
+ } else if (pending.request.kind === "permission") {
1206
+ this.#events?.onPermissionRequest(pending.session.summary.sessionId, id, { kind: pending.request.permissionKind });
1207
+ }
1208
+ }
1209
+ #finish(id, result, respond = true) {
1210
+ const pending = this.#pending.get(id);
1211
+ if (!pending) return false;
1212
+ clearTimeout(pending.timer);
1213
+ this.#pending.delete(id);
1214
+ if (respond && this.#rpc && !this.#rpc.closed) {
1215
+ this.#rpc.respond(pending.rpcId, result ?? (pending.questionId ? { answers: {} } : { decision: "cancel" }));
1216
+ }
1217
+ this.#events?.onInteractionDismissed?.(pending.session.summary.sessionId, id);
1218
+ return true;
1219
+ }
1220
+ #dismiss(session) {
1221
+ for (const [id, pending] of this.#pending) if (!session || session === pending.session) this.#finish(id);
1222
+ }
1223
+ respondPermission(id, decision, _feedback) {
1224
+ const pending = this.#pending.get(id);
1225
+ if (!pending || pending.request.kind !== "permission" || !this.isSessionVisible(pending.session.summary.sessionId)) return false;
1226
+ return this.#finish(id, { decision: decision === "approve-once" ? "accept" : "decline" });
1227
+ }
1228
+ async respondInteraction(id, response) {
1229
+ const pending = this.#pending.get(id);
1230
+ if (!pending || !this.isSessionVisible(pending.session.summary.sessionId)) return false;
1231
+ if (response.kind === "permission" && pending.request.kind === "permission") {
1232
+ if (response.decision !== "approve-once" && response.decision !== "reject") this.#unsupported("persistent approval");
1233
+ return this.respondPermission(id, response.decision);
1234
+ }
1235
+ if (response.kind === "user_input" && pending.request.kind === "user_input" && pending.questionId) {
1236
+ if (!pending.request.allowFreeform && !pending.request.choices.includes(response.answer)) throw new Error("Invalid Codex question answer");
1237
+ return this.#finish(id, { answers: { [pending.questionId]: { answers: [response.answer] } } });
1238
+ }
1239
+ this.#unsupported("interaction response");
1240
+ }
1241
+ replayPendingInteractions() {
1242
+ if (!this.#started) return;
1243
+ for (const [id, pending] of this.#pending) this.#publish(id, pending);
1244
+ }
1245
+ };
1246
+ export {
1247
+ CODEX_DEFAULT_MODEL_ID,
1248
+ CodexCodingAgentAdapter
1249
+ };