@jait/gateway 0.1.412 → 0.1.415

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.
Files changed (110) hide show
  1. package/dist/cli/doctor.d.ts.map +1 -1
  2. package/dist/cli/doctor.js +0 -3
  3. package/dist/cli/doctor.js.map +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +4 -10
  6. package/dist/index.js.map +1 -1
  7. package/dist/providers/acp-provider.d.ts +41 -0
  8. package/dist/providers/acp-provider.d.ts.map +1 -0
  9. package/dist/providers/acp-provider.js +507 -0
  10. package/dist/providers/acp-provider.js.map +1 -0
  11. package/dist/providers/codex-event-mapper.d.ts +2 -3
  12. package/dist/providers/codex-event-mapper.d.ts.map +1 -1
  13. package/dist/providers/codex-event-mapper.js +2 -3
  14. package/dist/providers/codex-event-mapper.js.map +1 -1
  15. package/dist/providers/contracts.d.ts +3 -8
  16. package/dist/providers/contracts.d.ts.map +1 -1
  17. package/dist/providers/contracts.js +2 -7
  18. package/dist/providers/contracts.js.map +1 -1
  19. package/dist/providers/index.d.ts +1 -2
  20. package/dist/providers/index.d.ts.map +1 -1
  21. package/dist/providers/index.js +1 -2
  22. package/dist/providers/index.js.map +1 -1
  23. package/dist/providers/registry.d.ts +1 -5
  24. package/dist/providers/registry.d.ts.map +1 -1
  25. package/dist/providers/registry.js +1 -5
  26. package/dist/providers/registry.js.map +1 -1
  27. package/dist/routes/threads.d.ts.map +1 -1
  28. package/dist/routes/threads.js +6 -32
  29. package/dist/routes/threads.js.map +1 -1
  30. package/dist/tools/thread-tools.d.ts.map +1 -1
  31. package/dist/tools/thread-tools.js +3 -13
  32. package/dist/tools/thread-tools.js.map +1 -1
  33. package/dist/voice-assistant/tools.js +4 -4
  34. package/dist/voice-assistant/tools.js.map +1 -1
  35. package/package.json +4 -2
  36. package/web-dist/assets/{_basePickBy-vrNV92m2.js → _basePickBy-Vej0n7ts.js} +1 -1
  37. package/web-dist/assets/{_baseUniq-CFWWzP80.js → _baseUniq-IJ0sJU1m.js} +1 -1
  38. package/web-dist/assets/{arc-Dp57lhjd.js → arc-BeHu1aOb.js} +1 -1
  39. package/web-dist/assets/{architectureDiagram-2XIMDMQ5-c-KUrgA1.js → architectureDiagram-2XIMDMQ5-Bp6z5ps0.js} +1 -1
  40. package/web-dist/assets/{blockDiagram-WCTKOSBZ-DLacZ0pV.js → blockDiagram-WCTKOSBZ-Cx_zzsRg.js} +1 -1
  41. package/web-dist/assets/{c4Diagram-IC4MRINW-D2ys7lmI.js → c4Diagram-IC4MRINW-DUa0YpGC.js} +1 -1
  42. package/web-dist/assets/channel-Dd_6YMtj.js +1 -0
  43. package/web-dist/assets/{chunk-4BX2VUAB-B-ycNUnR.js → chunk-4BX2VUAB-tpj87VmD.js} +1 -1
  44. package/web-dist/assets/{chunk-55IACEB6-Dw-KtT4V.js → chunk-55IACEB6-B_ZFHP09.js} +1 -1
  45. package/web-dist/assets/{chunk-FMBD7UC4-DWWi0UwB.js → chunk-FMBD7UC4-CTvcgmSZ.js} +1 -1
  46. package/web-dist/assets/{chunk-JSJVCQXG-ZjMMrgJX.js → chunk-JSJVCQXG-B7h9KpNR.js} +1 -1
  47. package/web-dist/assets/{chunk-KX2RTZJC-BRAPfCgZ.js → chunk-KX2RTZJC-D9Vq1pQV.js} +1 -1
  48. package/web-dist/assets/{chunk-NQ4KR5QH-Dqnz-534.js → chunk-NQ4KR5QH-C7aaLWfv.js} +1 -1
  49. package/web-dist/assets/{chunk-QZHKN3VN-BkyOnb_M.js → chunk-QZHKN3VN-DL14VDvc.js} +1 -1
  50. package/web-dist/assets/{chunk-WL4C6EOR-BvqOQsfx.js → chunk-WL4C6EOR-Dj9yLTN_.js} +1 -1
  51. package/web-dist/assets/classDiagram-VBA2DB6C-DGa9Vmq1.js +1 -0
  52. package/web-dist/assets/classDiagram-v2-RAHNMMFH-DGa9Vmq1.js +1 -0
  53. package/web-dist/assets/clone-BZHgrnhF.js +1 -0
  54. package/web-dist/assets/{cose-bilkent-S5V4N54A-DGhKdjQk.js → cose-bilkent-S5V4N54A-BWtWPOzl.js} +1 -1
  55. package/web-dist/assets/{dagre-KLK3FWXG-Dh6YUKKx.js → dagre-KLK3FWXG-BGfWBJVo.js} +1 -1
  56. package/web-dist/assets/{diagram-E7M64L7V-Cz1VaSVP.js → diagram-E7M64L7V-Cke9c3vx.js} +1 -1
  57. package/web-dist/assets/{diagram-IFDJBPK2-IZGndCa1.js → diagram-IFDJBPK2-BRRl8D9K.js} +1 -1
  58. package/web-dist/assets/{diagram-P4PSJMXO-Bgduoq5-.js → diagram-P4PSJMXO-BuZo-ISM.js} +1 -1
  59. package/web-dist/assets/{erDiagram-INFDFZHY-DRP1Ild4.js → erDiagram-INFDFZHY-Plok75TS.js} +1 -1
  60. package/web-dist/assets/{flowDiagram-PKNHOUZH-DYcuIBGl.js → flowDiagram-PKNHOUZH-DrTFnv1o.js} +1 -1
  61. package/web-dist/assets/{ganttDiagram-A5KZAMGK-BZEY_FhZ.js → ganttDiagram-A5KZAMGK-lbd5t50L.js} +1 -1
  62. package/web-dist/assets/{gitGraphDiagram-K3NZZRJ6-CSg7FdSY.js → gitGraphDiagram-K3NZZRJ6-4bqi_Grw.js} +1 -1
  63. package/web-dist/assets/{graph-WkvwRpUo.js → graph-BJxBQW7G.js} +1 -1
  64. package/web-dist/assets/{index-CBkuguaU.js → index-BGe0OrqB.js} +1 -1
  65. package/web-dist/assets/{index-C9iPdIgA.js → index-D-Q3NBqs.js} +1 -1
  66. package/web-dist/assets/{index-CHGwkS9I.js → index-DT-yEdLN.js} +338 -339
  67. package/web-dist/assets/{infoDiagram-LFFYTUFH-U1Qp2g6I.js → infoDiagram-LFFYTUFH-mOUWUKTg.js} +1 -1
  68. package/web-dist/assets/{ishikawaDiagram-PHBUUO56-KBxYliiv.js → ishikawaDiagram-PHBUUO56-C-ISusUx.js} +1 -1
  69. package/web-dist/assets/{journeyDiagram-4ABVD52K-CyvhyjNY.js → journeyDiagram-4ABVD52K-znHcSODe.js} +1 -1
  70. package/web-dist/assets/{kanban-definition-K7BYSVSG-Dw4vKnUe.js → kanban-definition-K7BYSVSG-B9ekOJxL.js} +1 -1
  71. package/web-dist/assets/{layout-Dz0qWf1K.js → layout-B_RPzsC2.js} +1 -1
  72. package/web-dist/assets/{linear-yehYuoNX.js → linear-DZLBRrsE.js} +1 -1
  73. package/web-dist/assets/{mindmap-definition-YRQLILUH-CEDrRHc1.js → mindmap-definition-YRQLILUH-D3XCkmFm.js} +1 -1
  74. package/web-dist/assets/{pieDiagram-SKSYHLDU-CJIbwnNl.js → pieDiagram-SKSYHLDU-B2rKp15l.js} +1 -1
  75. package/web-dist/assets/{quadrantDiagram-337W2JSQ-CMxVw8jy.js → quadrantDiagram-337W2JSQ-B3DzAIDT.js} +1 -1
  76. package/web-dist/assets/{requirementDiagram-Z7DCOOCP-CxRfDTgU.js → requirementDiagram-Z7DCOOCP-C4aT5lvg.js} +1 -1
  77. package/web-dist/assets/{sankeyDiagram-WA2Y5GQK-VofN_JQG.js → sankeyDiagram-WA2Y5GQK-CwdIKXWh.js} +1 -1
  78. package/web-dist/assets/{sequenceDiagram-2WXFIKYE-CT0EOHuV.js → sequenceDiagram-2WXFIKYE-C85lQ8Bt.js} +1 -1
  79. package/web-dist/assets/{stateDiagram-RAJIS63D-6bds6OVn.js → stateDiagram-RAJIS63D-C6gFkSrw.js} +1 -1
  80. package/web-dist/assets/stateDiagram-v2-FVOUBMTO-BsX0KLYS.js +1 -0
  81. package/web-dist/assets/{timeline-definition-YZTLITO2-BYWBgw6A.js → timeline-definition-YZTLITO2-B6sQ4e6y.js} +1 -1
  82. package/web-dist/assets/{treemap-KZPCXAKY-Bk9nmj4d.js → treemap-KZPCXAKY-DqwxJSZi.js} +1 -1
  83. package/web-dist/assets/{vennDiagram-LZ73GAT5-iGD5Go-o.js → vennDiagram-LZ73GAT5-BkJxDniY.js} +1 -1
  84. package/web-dist/assets/{xychartDiagram-JWTSCODW-Bl5sio3d.js → xychartDiagram-JWTSCODW-BbHdBYjX.js} +1 -1
  85. package/web-dist/index.html +1 -1
  86. package/dist/providers/claude-code-provider.d.ts +0 -69
  87. package/dist/providers/claude-code-provider.d.ts.map +0 -1
  88. package/dist/providers/claude-code-provider.js +0 -801
  89. package/dist/providers/claude-code-provider.js.map +0 -1
  90. package/dist/providers/codex-provider.d.ts +0 -63
  91. package/dist/providers/codex-provider.d.ts.map +0 -1
  92. package/dist/providers/codex-provider.js +0 -660
  93. package/dist/providers/codex-provider.js.map +0 -1
  94. package/dist/providers/copilot-provider.d.ts +0 -45
  95. package/dist/providers/copilot-provider.d.ts.map +0 -1
  96. package/dist/providers/copilot-provider.js +0 -591
  97. package/dist/providers/copilot-provider.js.map +0 -1
  98. package/dist/providers/gemini-provider.d.ts +0 -53
  99. package/dist/providers/gemini-provider.d.ts.map +0 -1
  100. package/dist/providers/gemini-provider.js +0 -545
  101. package/dist/providers/gemini-provider.js.map +0 -1
  102. package/dist/providers/opencode-provider.d.ts +0 -44
  103. package/dist/providers/opencode-provider.d.ts.map +0 -1
  104. package/dist/providers/opencode-provider.js +0 -505
  105. package/dist/providers/opencode-provider.js.map +0 -1
  106. package/web-dist/assets/channel-BRR_7HV5.js +0 -1
  107. package/web-dist/assets/classDiagram-VBA2DB6C-D98xw_nz.js +0 -1
  108. package/web-dist/assets/classDiagram-v2-RAHNMMFH-D98xw_nz.js +0 -1
  109. package/web-dist/assets/clone-BfGo-v3P.js +0 -1
  110. package/web-dist/assets/stateDiagram-v2-FVOUBMTO-Bds93qud.js +0 -1
@@ -1,660 +0,0 @@
1
- /**
2
- * Codex CLI Provider — wraps OpenAI Codex CLI as a Jait provider.
3
- *
4
- * Spawns `codex app-server` as a child process and communicates
5
- * via JSON-RPC 2.0 over NDJSON on stdin/stdout.
6
- *
7
- * Protocol (codex-cli ≥ 0.111.0):
8
- * 1. spawn `codex app-server` with piped stdio
9
- * 2. send `initialize` request → get response
10
- * 3. send `initialized` notification (no response)
11
- * 4. send `thread/start` request → get threadId
12
- * 5. send `turn/start` request per user message
13
- * 6. listen for notifications: item/agentMessage/delta, turn/completed, etc.
14
- */
15
- import { spawn, spawnSync } from "node:child_process";
16
- import { EventEmitter } from "node:events";
17
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
18
- import { homedir } from "node:os";
19
- import { join } from "node:path";
20
- import readline from "node:readline";
21
- import { uuidv7 } from "../db/uuidv7.js";
22
- import { mapCodexNotification } from "./codex-event-mapper.js";
23
- import { DEVICE_PROVIDER_AUTH, killChildTree as killAuthChildTree, runAuthCommand, startDeviceLoginCommand, } from "./provider-auth.js";
24
- // ── Provider implementation ──────────────────────────────────────────
25
- export class CodexProvider {
26
- id = "codex";
27
- info = {
28
- id: "codex",
29
- name: "OpenAI Codex",
30
- description: "OpenAI Codex CLI agent with sandboxed execution and MCP support",
31
- available: false,
32
- modes: ["full-access", "supervised"],
33
- auth: DEVICE_PROVIDER_AUTH,
34
- };
35
- sessions = new Map();
36
- emitter = new EventEmitter();
37
- codexPath = null;
38
- authLoginProcess = null;
39
- async checkAvailability() {
40
- try {
41
- // ── Step 1: Check if binary is installed ──
42
- const paths = ["codex", "npx codex"];
43
- let found = false;
44
- for (const cmd of paths) {
45
- const available = await this.testCommand(cmd);
46
- if (available) {
47
- this.codexPath = cmd;
48
- found = true;
49
- break;
50
- }
51
- }
52
- if (!found) {
53
- this.info.available = false;
54
- this.info.unavailableReason = "Codex CLI not installed. Install with: npm install -g @openai/codex";
55
- return false;
56
- }
57
- // ── Step 2: Check if authenticated ──
58
- const hasApiKey = !!process.env.OPENAI_API_KEY;
59
- const hasOAuthTokens = this.checkCodexAuthFile();
60
- if (!hasApiKey && !hasOAuthTokens) {
61
- this.info.available = false;
62
- this.info.unavailableReason = "Codex not authenticated. Run: codex login";
63
- return false;
64
- }
65
- this.info.available = true;
66
- this.info.unavailableReason = undefined;
67
- return true;
68
- }
69
- catch {
70
- this.info.available = false;
71
- this.info.unavailableReason = "Failed to check Codex CLI availability";
72
- return false;
73
- }
74
- }
75
- getCodexAuthPath() {
76
- const codexHome = process.env.CODEX_HOME ?? join(homedir(), ".codex");
77
- return join(codexHome, "auth.json");
78
- }
79
- readCodexAuthFile() {
80
- try {
81
- const authPath = this.getCodexAuthPath();
82
- if (!existsSync(authPath))
83
- return null;
84
- const raw = readFileSync(authPath, "utf-8");
85
- return JSON.parse(raw);
86
- }
87
- catch {
88
- return null;
89
- }
90
- }
91
- /**
92
- * Check if ~/.codex/auth.json (or CODEX_HOME/auth.json) contains Codex CLI credentials.
93
- */
94
- checkCodexAuthFile() {
95
- const auth = this.readCodexAuthFile();
96
- if (!auth)
97
- return false;
98
- try {
99
- if (auth.OPENAI_API_KEY)
100
- return true;
101
- if (auth.tokens?.access_token)
102
- return true;
103
- return false;
104
- }
105
- catch {
106
- return false;
107
- }
108
- }
109
- clearCodexAuthFile() {
110
- const auth = this.readCodexAuthFile();
111
- if (!auth)
112
- return true;
113
- delete auth.OPENAI_API_KEY;
114
- delete auth.tokens;
115
- const remaining = Object.entries(auth).filter(([, value]) => value !== undefined && value !== null);
116
- try {
117
- if (remaining.length === 0) {
118
- unlinkSync(this.getCodexAuthPath());
119
- }
120
- else {
121
- writeFileSync(this.getCodexAuthPath(), `${JSON.stringify(Object.fromEntries(remaining), null, 2)}\n`, "utf-8");
122
- }
123
- return true;
124
- }
125
- catch {
126
- return false;
127
- }
128
- }
129
- async getAuthStatus() {
130
- const authenticated = this.checkCodexAuthFile();
131
- return {
132
- ...DEVICE_PROVIDER_AUTH,
133
- authenticated,
134
- detail: authenticated
135
- ? "Codex CLI credentials are configured."
136
- : "Codex CLI is not authenticated.",
137
- };
138
- }
139
- async startLogin() {
140
- if (this.authLoginProcess) {
141
- killAuthChildTree(this.authLoginProcess);
142
- this.authLoginProcess = null;
143
- }
144
- const { result, child } = await startDeviceLoginCommand({
145
- providerId: this.id,
146
- label: "Codex",
147
- commandLine: this.codexPath ?? "codex",
148
- args: ["login", "--device-auth"],
149
- timeoutMs: 30_000,
150
- });
151
- if (child) {
152
- this.authLoginProcess = child;
153
- child.on("exit", () => {
154
- if (this.authLoginProcess === child)
155
- this.authLoginProcess = null;
156
- void this.checkAvailability();
157
- });
158
- }
159
- return result;
160
- }
161
- async logout() {
162
- if (this.authLoginProcess) {
163
- killAuthChildTree(this.authLoginProcess);
164
- this.authLoginProcess = null;
165
- }
166
- const result = await runAuthCommand(this.id, this.codexPath ?? "codex", ["logout"]);
167
- const cleared = result.ok ? this.clearCodexAuthFile() : true;
168
- await this.checkAvailability().catch(() => false);
169
- return {
170
- ...result,
171
- ok: result.ok && cleared,
172
- status: result.ok && cleared ? result.status : "error",
173
- message: result.ok
174
- ? cleared
175
- ? "Codex logout completed."
176
- : "Codex logout ran, but stored credentials could not be removed."
177
- : result.message,
178
- };
179
- }
180
- /**
181
- * List available models by spawning a short-lived `codex app-server`,
182
- * performing the initialize handshake, then calling `model/list`.
183
- */
184
- async listModels() {
185
- const spawnSpec = parseCommand(this.codexPath ?? "codex");
186
- let child;
187
- try {
188
- child = spawn(spawnSpec.command, [...spawnSpec.args, "app-server"], {
189
- cwd: process.cwd(),
190
- env: process.env,
191
- stdio: ["pipe", "pipe", "pipe"],
192
- windowsHide: true,
193
- shell: process.platform === "win32",
194
- });
195
- }
196
- catch {
197
- return [];
198
- }
199
- // Suppress EPIPE errors when child exits before we finish writing
200
- child.stdin?.on("error", () => { });
201
- // Handle spawn errors (e.g. ENOENT when codex is not installed)
202
- const spawnError = new Promise((_, reject) => {
203
- child.on("error", (err) => reject(err));
204
- });
205
- const rl = readline.createInterface({ input: child.stdout });
206
- const pending = new Map();
207
- let nextId = 1;
208
- const writeMsg = (msg) => {
209
- if (child.stdin?.writable)
210
- child.stdin.write(JSON.stringify(msg) + "\n");
211
- };
212
- const sendReq = (method, params, timeoutMs = 15_000) => {
213
- const id = nextId++;
214
- return new Promise((resolve, reject) => {
215
- const timeout = setTimeout(() => {
216
- pending.delete(String(id));
217
- reject(new Error(`Timed out waiting for ${method}`));
218
- }, timeoutMs);
219
- pending.set(String(id), { method, timeout, resolve, reject });
220
- writeMsg({ method, id, params });
221
- });
222
- };
223
- // Listen for JSON-RPC responses
224
- rl.on("line", (line) => {
225
- try {
226
- const msg = JSON.parse(line);
227
- if (msg.id != null) {
228
- const p = pending.get(String(msg.id));
229
- if (p) {
230
- pending.delete(String(msg.id));
231
- clearTimeout(p.timeout);
232
- if (msg.error) {
233
- p.reject(new Error(msg.error.message ?? "RPC error"));
234
- }
235
- else {
236
- p.resolve(msg.result);
237
- }
238
- }
239
- }
240
- }
241
- catch { /* ignore non-JSON lines */ }
242
- });
243
- try {
244
- // Handshake (race against spawn errors like ENOENT)
245
- await Promise.race([
246
- sendReq("initialize", {
247
- clientInfo: { name: "jait", title: "Jait Gateway", version: "1.0.0" },
248
- capabilities: { experimentalApi: true },
249
- }),
250
- spawnError,
251
- ]);
252
- writeMsg({ method: "initialized" });
253
- // Fetch model list — response shape: { data: [{id, model, displayName, description, isDefault, ...}], nextCursor }
254
- const result = await sendReq("model/list", {});
255
- const models = [];
256
- const items = result?.data ?? result?.models;
257
- if (Array.isArray(items)) {
258
- for (const m of items) {
259
- const id = String(m.id ?? m.model ?? m.name ?? "");
260
- const name = String(m.displayName ?? m.name ?? m.id ?? "");
261
- if (!id)
262
- continue;
263
- models.push({
264
- id,
265
- name,
266
- ...(m.description ? { description: String(m.description) } : {}),
267
- ...(m.isDefault ? { isDefault: true } : {}),
268
- });
269
- }
270
- }
271
- return models;
272
- }
273
- catch (err) {
274
- const msg = err instanceof Error ? err.message : String(err);
275
- console.error(`[codex] model/list failed: ${msg}`);
276
- return [];
277
- }
278
- finally {
279
- // Clean up
280
- for (const p of pending.values()) {
281
- clearTimeout(p.timeout);
282
- p.reject(new Error("Session closed"));
283
- }
284
- pending.clear();
285
- rl.close();
286
- child.kill("SIGTERM");
287
- }
288
- }
289
- async startSession(options) {
290
- const sessionId = uuidv7();
291
- const spawnSpec = parseCommand(this.codexPath ?? "codex");
292
- const child = spawn(spawnSpec.command, [...spawnSpec.args, "app-server", ...buildCodexMcpConfigArgs(options.mcpServers)], {
293
- cwd: options.workingDirectory,
294
- env: {
295
- ...process.env,
296
- ...options.env,
297
- },
298
- stdio: ["pipe", "pipe", "pipe"],
299
- windowsHide: true,
300
- shell: process.platform === "win32",
301
- });
302
- // Suppress EPIPE errors when child exits before we finish writing
303
- child.stdin?.on("error", () => { });
304
- const rl = readline.createInterface({ input: child.stdout });
305
- // Collect stderr for diagnostics if startup fails
306
- const stderrChunks = [];
307
- child.stderr?.on("data", (data) => {
308
- stderrChunks.push(data.toString());
309
- });
310
- const session = {
311
- id: sessionId,
312
- providerId: "codex",
313
- threadId: options.threadId,
314
- status: "starting",
315
- runtimeMode: options.mode,
316
- startedAt: new Date().toISOString(),
317
- };
318
- const state = {
319
- session,
320
- child,
321
- rl,
322
- pending: new Map(),
323
- nextId: 1,
324
- providerThreadId: null,
325
- stopping: false,
326
- };
327
- this.sessions.set(sessionId, state);
328
- this.attachListeners(state);
329
- try {
330
- // ── Step 1: initialize handshake (45s — codex can be slow to start) ──
331
- await this.sendRequest(state, "initialize", {
332
- clientInfo: { name: "jait", title: "Jait Gateway", version: "1.0.0" },
333
- capabilities: { experimentalApi: true },
334
- }, 45_000);
335
- // ── Step 2: initialized notification (no response expected) ──
336
- this.writeMessage(state, { method: "initialized" });
337
- // ── Step 3: thread/start ──
338
- const { approvalPolicy, sandbox } = mapRuntimeMode(options.mode);
339
- const threadResponse = await this.sendRequest(state, "thread/start", {
340
- model: options.model ?? null,
341
- cwd: options.workingDirectory,
342
- approvalPolicy,
343
- sandbox,
344
- experimentalRawEvents: false,
345
- });
346
- const providerThreadId = threadResponse?.thread?.id ?? threadResponse?.threadId;
347
- if (!providerThreadId) {
348
- throw new Error("thread/start response did not include a thread id");
349
- }
350
- state.providerThreadId = providerThreadId;
351
- state.session.status = "running";
352
- this.emit({ type: "session.started", sessionId });
353
- return session;
354
- }
355
- catch (error) {
356
- state.session.status = "error";
357
- const baseMsg = error instanceof Error ? error.message : "Failed to start Codex session";
358
- const stderr = stderrChunks.join("").trim();
359
- state.session.error = stderr
360
- ? `${baseMsg}\n--- stderr ---\n${stderr.slice(0, 2000)}`
361
- : baseMsg;
362
- console.error(`[codex:${sessionId}] startSession failed: ${state.session.error}`);
363
- this.emit({ type: "session.error", sessionId, error: state.session.error });
364
- this.stopSession(sessionId);
365
- throw error;
366
- }
367
- }
368
- async sendTurn(sessionId, message, _attachments) {
369
- const state = this.getState(sessionId);
370
- if (!state.providerThreadId) {
371
- throw new Error("Session has no active thread");
372
- }
373
- await this.sendRequest(state, "turn/start", {
374
- threadId: state.providerThreadId,
375
- input: [{ type: "text", text: message, text_elements: [] }],
376
- });
377
- }
378
- async interruptTurn(sessionId) {
379
- const state = this.sessions.get(sessionId);
380
- if (!state?.providerThreadId)
381
- return;
382
- try {
383
- await this.sendRequest(state, "turn/interrupt", {
384
- threadId: state.providerThreadId,
385
- });
386
- }
387
- catch {
388
- // best effort
389
- }
390
- }
391
- async respondToApproval(sessionId, requestId, approved) {
392
- const state = this.getState(sessionId);
393
- // Respond to the server request with a JSON-RPC response using the original request id
394
- this.writeMessage(state, {
395
- id: requestId,
396
- result: { decision: approved ? "approve" : "deny" },
397
- });
398
- }
399
- async stopSession(sessionId) {
400
- const state = this.sessions.get(sessionId);
401
- if (!state)
402
- return;
403
- state.stopping = true;
404
- // Clear pending requests
405
- for (const [, pending] of state.pending) {
406
- clearTimeout(pending.timeout);
407
- pending.reject(new Error("Session stopped"));
408
- }
409
- state.pending.clear();
410
- // Close readline interface
411
- state.rl.close();
412
- // Kill the process tree
413
- if (!state.child.killed) {
414
- killChildTree(state.child);
415
- }
416
- this.sessions.delete(sessionId);
417
- // Emit session.completed so the onEvent handler in /start can unsubscribe
418
- this.emit({ type: "session.completed", sessionId });
419
- }
420
- onEvent(handler) {
421
- this.emitter.on("event", handler);
422
- return () => this.emitter.off("event", handler);
423
- }
424
- // ── Private helpers ────────────────────────────────────────────────
425
- emit(event) {
426
- this.emitter.emit("event", event);
427
- }
428
- getState(sessionId) {
429
- const state = this.sessions.get(sessionId);
430
- if (!state)
431
- throw new Error(`No Codex session found: ${sessionId}`);
432
- return state;
433
- }
434
- attachListeners(state) {
435
- state.rl.on("line", (line) => {
436
- this.handleStdoutLine(state, line);
437
- });
438
- state.child.stderr?.on("data", (data) => {
439
- const text = data.toString().trim();
440
- if (text) {
441
- // Filter out noisy info/debug/trace log lines from codex
442
- const isLogLine = /^\d{4}-\d{2}-\d{2}T\S+\s+(TRACE|DEBUG|INFO|WARN)\s+/.test(text);
443
- if (!isLogLine) {
444
- console.error(`[codex:${state.session.id}] stderr: ${text}`);
445
- }
446
- }
447
- });
448
- state.child.on("error", (err) => {
449
- state.session.status = "error";
450
- state.session.error = err.message;
451
- this.emit({ type: "session.error", sessionId: state.session.id, error: err.message });
452
- });
453
- state.child.on("exit", (code, signal) => {
454
- if (state.stopping)
455
- return;
456
- const message = `codex app-server exited (code=${code}, signal=${signal})`;
457
- state.session.status = code === 0 ? "completed" : "error";
458
- state.session.completedAt = new Date().toISOString();
459
- if (code !== 0) {
460
- state.session.error = message;
461
- }
462
- this.emit({ type: "session.completed", sessionId: state.session.id });
463
- // Reject pending requests
464
- for (const [, pending] of state.pending) {
465
- clearTimeout(pending.timeout);
466
- pending.reject(new Error("Codex process exited"));
467
- }
468
- state.pending.clear();
469
- this.sessions.delete(state.session.id);
470
- });
471
- }
472
- handleStdoutLine(state, line) {
473
- let parsed;
474
- try {
475
- parsed = JSON.parse(line);
476
- }
477
- catch {
478
- return;
479
- }
480
- if (!parsed || typeof parsed !== "object")
481
- return;
482
- if (this.isResponse(parsed)) {
483
- this.handleResponse(state, parsed);
484
- }
485
- else if (this.isServerRequest(parsed)) {
486
- this.handleServerRequest(state, parsed);
487
- }
488
- else if (this.isServerNotification(parsed)) {
489
- this.handleServerNotification(state, parsed);
490
- }
491
- }
492
- /** Response: has id, no method */
493
- isResponse(value) {
494
- const obj = value;
495
- const hasId = typeof obj.id === "string" || typeof obj.id === "number";
496
- const hasMethod = typeof obj.method === "string";
497
- return hasId && !hasMethod;
498
- }
499
- /** Server request: has both method and id */
500
- isServerRequest(value) {
501
- const obj = value;
502
- return (typeof obj.method === "string" &&
503
- (typeof obj.id === "string" || typeof obj.id === "number"));
504
- }
505
- /** Server notification: has method, no id */
506
- isServerNotification(value) {
507
- const obj = value;
508
- return typeof obj.method === "string" && !("id" in obj);
509
- }
510
- handleResponse(state, response) {
511
- const key = String(response.id);
512
- const pending = state.pending.get(key);
513
- if (!pending)
514
- return;
515
- clearTimeout(pending.timeout);
516
- state.pending.delete(key);
517
- if (response.error?.message) {
518
- pending.reject(new Error(`${pending.method} failed: ${response.error.message}`));
519
- }
520
- else {
521
- pending.resolve(response.result);
522
- }
523
- }
524
- handleServerRequest(state, request) {
525
- const params = (request.params ?? {});
526
- // Approval requests from codex
527
- if (request.method === "item/commandExecution/requestApproval" ||
528
- request.method === "item/fileChange/requestApproval" ||
529
- request.method === "item/fileRead/requestApproval") {
530
- const tool = String(params.command ?? params.tool ?? request.method);
531
- this.emit({
532
- type: "tool.approval-required",
533
- sessionId: state.session.id,
534
- tool,
535
- args: params,
536
- requestId: String(request.id),
537
- });
538
- return;
539
- }
540
- // Unknown server request — respond with error
541
- this.writeMessage(state, {
542
- id: request.id,
543
- error: { code: -32601, message: `Unsupported server request: ${request.method}` },
544
- });
545
- }
546
- handleServerNotification(state, notification) {
547
- const params = (notification.params ?? {});
548
- const sessionId = state.session.id;
549
- // Local state mutations that the shared mapper can't handle
550
- if (notification.method === "turn/started") {
551
- state.session.status = "running";
552
- }
553
- if (notification.method === "turn/completed") {
554
- state.session.status = "idle";
555
- }
556
- const events = mapCodexNotification(notification.method, params, sessionId);
557
- for (const evt of events) {
558
- this.emit(evt);
559
- }
560
- }
561
- sendRequest(state, method, params, timeoutMs = 20_000) {
562
- const id = state.nextId++;
563
- return new Promise((resolve, reject) => {
564
- const timeout = setTimeout(() => {
565
- state.pending.delete(String(id));
566
- reject(new Error(`Timed out waiting for ${method}`));
567
- }, timeoutMs);
568
- state.pending.set(String(id), { method, timeout, resolve, reject });
569
- this.writeMessage(state, { method, id, params });
570
- });
571
- }
572
- writeMessage(state, message) {
573
- if (!state.child.stdin?.writable) {
574
- throw new Error("Cannot write to codex app-server stdin");
575
- }
576
- state.child.stdin.write(JSON.stringify(message) + "\n");
577
- }
578
- testCommand(cmd) {
579
- return new Promise((resolve) => {
580
- const spawnSpec = parseCommand(cmd);
581
- if (!spawnSpec.command) {
582
- resolve(false);
583
- return;
584
- }
585
- const child = spawn(spawnSpec.command, [...spawnSpec.args, "--version"], {
586
- stdio: "pipe",
587
- windowsHide: true,
588
- shell: process.platform === "win32",
589
- });
590
- const timer = setTimeout(() => {
591
- child.kill();
592
- resolve(false);
593
- }, 5000);
594
- child.on("exit", (code) => {
595
- clearTimeout(timer);
596
- resolve(code === 0);
597
- });
598
- child.on("error", () => {
599
- clearTimeout(timer);
600
- resolve(false);
601
- });
602
- });
603
- }
604
- }
605
- // ── Helpers ──────────────────────────────────────────────────────────
606
- function mapRuntimeMode(mode) {
607
- if (mode === "supervised") {
608
- return { approvalPolicy: "on-request", sandbox: "workspace-write" };
609
- }
610
- return { approvalPolicy: "never", sandbox: "danger-full-access" };
611
- }
612
- export function buildCodexMcpConfigArgs(servers) {
613
- if (!servers?.length)
614
- return [];
615
- const args = [];
616
- for (const server of servers) {
617
- const prefix = `mcp_servers.${server.name}`;
618
- if (server.transport === "sse" && server.url) {
619
- args.push("-c", `${prefix}.url=${toTomlString(server.url)}`);
620
- continue;
621
- }
622
- if (server.transport === "stdio" && server.command) {
623
- args.push("-c", `${prefix}.command=${toTomlString(server.command)}`);
624
- args.push("-c", `${prefix}.args=${toTomlArray(server.args ?? [])}`);
625
- for (const [key, value] of Object.entries(server.env ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
626
- args.push("-c", `${prefix}.env.${key}=${toTomlString(value)}`);
627
- }
628
- }
629
- }
630
- return args;
631
- }
632
- function toTomlString(value) {
633
- return JSON.stringify(value);
634
- }
635
- function toTomlArray(values) {
636
- return `[${values.map((value) => toTomlString(value)).join(", ")}]`;
637
- }
638
- function parseCommand(commandLine) {
639
- const parts = commandLine.trim().split(/\s+/).filter(Boolean);
640
- return {
641
- command: parts[0] ?? "",
642
- args: parts.slice(1),
643
- };
644
- }
645
- /**
646
- * On Windows, use `taskkill /T` to kill the entire process tree.
647
- */
648
- function killChildTree(child) {
649
- if (process.platform === "win32" && child.pid !== undefined) {
650
- try {
651
- spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
652
- return;
653
- }
654
- catch {
655
- // fallback
656
- }
657
- }
658
- child.kill();
659
- }
660
- //# sourceMappingURL=codex-provider.js.map