lunel-cli 0.1.46 → 0.1.47

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.
@@ -6,6 +6,7 @@ export declare class CodexProvider implements AIProvider {
6
6
  private nextId;
7
7
  private pending;
8
8
  private sessions;
9
+ private sessionIdByThreadId;
9
10
  init(): Promise<void>;
10
11
  destroy(): Promise<void>;
11
12
  subscribe(emitter: AiEventEmitter): () => void;
@@ -43,4 +44,8 @@ export declare class CodexProvider implements AIProvider {
43
44
  private send;
44
45
  private call;
45
46
  private handleLine;
47
+ private ensureThread;
48
+ private ingestThreadMetadata;
49
+ private extractThreadId;
50
+ private readString;
46
51
  }
package/dist/ai/codex.js CHANGED
@@ -28,6 +28,7 @@ export class CodexProvider {
28
28
  nextId = 1;
29
29
  pending = new Map();
30
30
  sessions = new Map();
31
+ sessionIdByThreadId = new Map();
31
32
  // -------------------------------------------------------------------------
32
33
  // Lifecycle
33
34
  // -------------------------------------------------------------------------
@@ -76,10 +77,7 @@ export class CodexProvider {
76
77
  this.sessions.set(id, session);
77
78
  // Start a new Codex thread for this session.
78
79
  try {
79
- const result = await this.call("thread/start", { title });
80
- if (result?.threadId) {
81
- session.threadId = result.threadId;
82
- }
80
+ await this.ensureThread(session);
83
81
  }
84
82
  catch (err) {
85
83
  console.warn("[codex] thread/start failed:", err.message);
@@ -102,6 +100,10 @@ export class CodexProvider {
102
100
  return { session: { id: s.id, title: s.title ?? "", created: s.createdAt } };
103
101
  }
104
102
  async deleteSession(id) {
103
+ const session = this.sessions.get(id);
104
+ if (session?.threadId) {
105
+ this.sessionIdByThreadId.delete(session.threadId);
106
+ }
105
107
  this.sessions.delete(id);
106
108
  return {};
107
109
  }
@@ -117,7 +119,6 @@ export class CodexProvider {
117
119
  // -------------------------------------------------------------------------
118
120
  async prompt(sessionId, text, model, agent) {
119
121
  const session = this.sessions.get(sessionId);
120
- const threadId = session?.threadId;
121
122
  // Append the user message to local history immediately.
122
123
  if (session) {
123
124
  session.messages.push({
@@ -128,20 +129,33 @@ export class CodexProvider {
128
129
  });
129
130
  }
130
131
  // Fire-and-forget — the response streams back as JSON-RPC notifications.
131
- this.call("turn/start", {
132
- ...(threadId ? { threadId } : { sessionId }),
133
- message: { role: "user", content: text },
134
- ...(model ? { model: `${model.providerID}/${model.modelID}` } : {}),
135
- ...(agent ? { agent } : {}),
136
- }).catch((err) => {
137
- console.error("[codex] turn/start error:", err.message);
138
- this.emitter?.({ type: "prompt_error", properties: { sessionId, error: err.message } });
139
- });
132
+ (async () => {
133
+ try {
134
+ if (!session) {
135
+ throw new Error(`Session ${sessionId} not found`);
136
+ }
137
+ const threadId = await this.ensureThread(session);
138
+ await this.call("turn/start", {
139
+ threadId,
140
+ input: [{ type: "message", role: "user", content: [{ type: "input_text", text }] }],
141
+ ...(model ? { model: `${model.providerID}/${model.modelID}` } : {}),
142
+ ...(agent ? { agent } : {}),
143
+ });
144
+ }
145
+ catch (err) {
146
+ const message = err.message;
147
+ console.error("[codex] turn/start error:", message);
148
+ this.emitter?.({ type: "prompt_error", properties: { sessionId, error: message } });
149
+ }
150
+ })();
140
151
  return { ack: true };
141
152
  }
142
153
  async abort(sessionId) {
143
154
  const session = this.sessions.get(sessionId);
144
- await this.call("turn/abort", { ...(session?.threadId ? { threadId: session.threadId } : { sessionId }) });
155
+ if (!session?.threadId) {
156
+ throw new Error(`Session ${sessionId} has no active Codex thread`);
157
+ }
158
+ await this.call("turn/abort", { threadId: session.threadId });
145
159
  return {};
146
160
  }
147
161
  // -------------------------------------------------------------------------
@@ -181,8 +195,11 @@ export class CodexProvider {
181
195
  // -------------------------------------------------------------------------
182
196
  async command(sessionId, command, args) {
183
197
  const session = this.sessions.get(sessionId);
198
+ if (!session?.threadId) {
199
+ throw new Error(`Session ${sessionId} has no active Codex thread`);
200
+ }
184
201
  const result = await this.call("session/command", {
185
- ...(session?.threadId ? { threadId: session.threadId } : { sessionId }),
202
+ threadId: session.threadId,
186
203
  command,
187
204
  arguments: args,
188
205
  });
@@ -190,16 +207,22 @@ export class CodexProvider {
190
207
  }
191
208
  async revert(sessionId, messageId) {
192
209
  const session = this.sessions.get(sessionId);
210
+ if (!session?.threadId) {
211
+ throw new Error(`Session ${sessionId} has no active Codex thread`);
212
+ }
193
213
  await this.call("session/revert", {
194
- ...(session?.threadId ? { threadId: session.threadId } : { sessionId }),
214
+ threadId: session.threadId,
195
215
  messageId,
196
216
  });
197
217
  return {};
198
218
  }
199
219
  async unrevert(sessionId) {
200
220
  const session = this.sessions.get(sessionId);
221
+ if (!session?.threadId) {
222
+ throw new Error(`Session ${sessionId} has no active Codex thread`);
223
+ }
201
224
  await this.call("session/unrevert", {
202
- ...(session?.threadId ? { threadId: session.threadId } : { sessionId }),
225
+ threadId: session.threadId,
203
226
  });
204
227
  return {};
205
228
  }
@@ -209,8 +232,11 @@ export class CodexProvider {
209
232
  }
210
233
  async permissionReply(sessionId, permissionId, response) {
211
234
  const session = this.sessions.get(sessionId);
235
+ if (!session?.threadId) {
236
+ throw new Error(`Session ${sessionId} has no active Codex thread`);
237
+ }
212
238
  await this.call("session/permission", {
213
- ...(session?.threadId ? { threadId: session.threadId } : { sessionId }),
239
+ threadId: session.threadId,
214
240
  permissionId,
215
241
  response,
216
242
  });
@@ -265,7 +291,62 @@ export class CodexProvider {
265
291
  const notif = msg;
266
292
  const params = (notif.params ?? {});
267
293
  const mappedType = NOTIFICATION_TYPE_MAP[notif.method] ?? notif.method;
294
+ this.ingestThreadMetadata(params);
268
295
  console.log("[codex]", notif.method);
269
296
  this.emitter?.({ type: mappedType, properties: params });
270
297
  }
298
+ async ensureThread(session) {
299
+ if (session.threadId)
300
+ return session.threadId;
301
+ const result = await this.call("thread/start", {
302
+ ...(session.title ? { title: session.title } : {}),
303
+ });
304
+ const threadId = this.extractThreadId(result);
305
+ if (!threadId) {
306
+ throw new Error("thread/start response missing threadId");
307
+ }
308
+ session.threadId = threadId;
309
+ this.sessionIdByThreadId.set(threadId, session.id);
310
+ return threadId;
311
+ }
312
+ ingestThreadMetadata(payload) {
313
+ const threadId = this.extractThreadId(payload);
314
+ if (!threadId)
315
+ return;
316
+ const sessionId = this.sessionIdByThreadId.get(threadId);
317
+ if (sessionId) {
318
+ const session = this.sessions.get(sessionId);
319
+ if (session) {
320
+ session.threadId = threadId;
321
+ }
322
+ return;
323
+ }
324
+ for (const session of this.sessions.values()) {
325
+ if (!session.threadId) {
326
+ session.threadId = threadId;
327
+ this.sessionIdByThreadId.set(threadId, session.id);
328
+ return;
329
+ }
330
+ }
331
+ }
332
+ extractThreadId(payload) {
333
+ if (!payload || typeof payload !== "object")
334
+ return undefined;
335
+ const obj = payload;
336
+ const direct = this.readString(obj.threadId) ??
337
+ this.readString(obj.thread_id);
338
+ if (direct)
339
+ return direct;
340
+ const thread = obj.thread;
341
+ if (thread && typeof thread === "object") {
342
+ const threadObj = thread;
343
+ return (this.readString(threadObj.id) ??
344
+ this.readString(threadObj.threadId) ??
345
+ this.readString(threadObj.thread_id));
346
+ }
347
+ return undefined;
348
+ }
349
+ readString(value) {
350
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
351
+ }
271
352
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",