lunel-cli 0.1.53 → 0.1.54
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/ai/codex.d.ts +6 -0
- package/dist/ai/codex.js +156 -33
- package/package.json +1 -1
package/dist/ai/codex.d.ts
CHANGED
|
@@ -58,10 +58,13 @@ export declare class CodexProvider implements AIProvider {
|
|
|
58
58
|
private ensureAssistantMessage;
|
|
59
59
|
private upsertLocalMessagePart;
|
|
60
60
|
private fetchServerThreads;
|
|
61
|
+
private fetchServerThreadsByArchiveState;
|
|
61
62
|
private fetchModels;
|
|
62
63
|
private parseThreadListEntry;
|
|
63
64
|
private hasNextCursor;
|
|
64
65
|
private ingestThreadMetadata;
|
|
66
|
+
private reconcileSessionsWithServer;
|
|
67
|
+
private mergeSession;
|
|
65
68
|
private upsertSession;
|
|
66
69
|
private ensureLocalSession;
|
|
67
70
|
private resolveSessionFromPayload;
|
|
@@ -87,6 +90,7 @@ export declare class CodexProvider implements AIProvider {
|
|
|
87
90
|
private extractUpdatedAt;
|
|
88
91
|
private readTimestamp;
|
|
89
92
|
private firstString;
|
|
93
|
+
private firstStringFromSources;
|
|
90
94
|
private readArray;
|
|
91
95
|
private readString;
|
|
92
96
|
private asRecord;
|
|
@@ -102,7 +106,9 @@ export declare class CodexProvider implements AIProvider {
|
|
|
102
106
|
private renderFileChangeEntriesBody;
|
|
103
107
|
private renderUnifiedDiffBody;
|
|
104
108
|
private extractCanonicalPatch;
|
|
109
|
+
private normalizeDisplayPath;
|
|
105
110
|
private normalizedFileChangeStatus;
|
|
106
111
|
private extractToolInput;
|
|
112
|
+
private extractCommandExecutionInput;
|
|
107
113
|
private describeCompletedItemOutput;
|
|
108
114
|
}
|
package/dist/ai/codex.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// over stdin/stdout. Maps Codex's thread/turn model onto Lunel's AIProvider
|
|
3
3
|
// contract using the same thread/list + thread/read flow used by Remodex.
|
|
4
4
|
import * as crypto from "crypto";
|
|
5
|
+
import * as path from "path";
|
|
5
6
|
import { spawn } from "child_process";
|
|
6
7
|
import { createInterface } from "readline";
|
|
7
8
|
const THREAD_LIST_SOURCE_KINDS = ["cli", "vscode", "appServer", "exec", "unknown"];
|
|
@@ -61,14 +62,20 @@ export class CodexProvider {
|
|
|
61
62
|
title: threadTitle,
|
|
62
63
|
createdAt: this.extractCreatedAt(result) ?? Date.now(),
|
|
63
64
|
updatedAt: this.extractUpdatedAt(result) ?? Date.now(),
|
|
65
|
+
archived: false,
|
|
64
66
|
});
|
|
65
67
|
return { session: this.toSessionInfo(session) };
|
|
66
68
|
}
|
|
67
69
|
async listSessions() {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
const activeThreads = await this.fetchServerThreads();
|
|
71
|
+
let archivedThreads = [];
|
|
72
|
+
try {
|
|
73
|
+
archivedThreads = await this.fetchServerThreadsByArchiveState(true);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Some Codex runtimes may not support archived thread listing.
|
|
71
77
|
}
|
|
78
|
+
this.reconcileSessionsWithServer(activeThreads, archivedThreads);
|
|
72
79
|
const sessions = Array.from(this.sessions.values())
|
|
73
80
|
.sort((a, b) => a.updatedAt - b.updatedAt)
|
|
74
81
|
.map((session) => this.toSessionInfo(session));
|
|
@@ -103,12 +110,14 @@ export class CodexProvider {
|
|
|
103
110
|
const historyMessages = this.decodeMessagesFromThreadRead(sessionId, threadObject);
|
|
104
111
|
if (historyMessages.length > 0) {
|
|
105
112
|
session.messages = historyMessages;
|
|
106
|
-
session.updatedAt = this.extractUpdatedAt(threadObject) ?? session.updatedAt;
|
|
107
|
-
session.createdAt = this.extractCreatedAt(threadObject) ?? session.createdAt;
|
|
108
|
-
const title = this.extractThreadTitle(threadObject);
|
|
109
|
-
if (title)
|
|
110
|
-
session.title = title;
|
|
111
113
|
}
|
|
114
|
+
this.upsertSession({
|
|
115
|
+
id: sessionId,
|
|
116
|
+
title: this.extractThreadTitle(threadObject),
|
|
117
|
+
createdAt: this.extractCreatedAt(threadObject) ?? session.createdAt,
|
|
118
|
+
updatedAt: this.extractUpdatedAt(threadObject) ?? session.updatedAt,
|
|
119
|
+
archived: false,
|
|
120
|
+
});
|
|
112
121
|
return { messages: session.messages };
|
|
113
122
|
}
|
|
114
123
|
async prompt(sessionId, text, model, agent) {
|
|
@@ -297,15 +306,24 @@ export class CodexProvider {
|
|
|
297
306
|
case "thread/started":
|
|
298
307
|
case "thread/name/updated":
|
|
299
308
|
if (session) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
309
|
+
this.upsertSession({
|
|
310
|
+
id: session.id,
|
|
311
|
+
title: this.extractThreadTitle(params),
|
|
312
|
+
createdAt: this.extractCreatedAt(params) ?? session.createdAt,
|
|
313
|
+
updatedAt: this.extractUpdatedAt(params) ?? Date.now(),
|
|
314
|
+
archived: false,
|
|
315
|
+
}, true);
|
|
305
316
|
}
|
|
306
317
|
return;
|
|
307
318
|
case "thread/status/changed":
|
|
308
319
|
if (session) {
|
|
320
|
+
this.upsertSession({
|
|
321
|
+
id: session.id,
|
|
322
|
+
title: this.extractThreadTitle(params),
|
|
323
|
+
createdAt: this.extractCreatedAt(params) ?? session.createdAt,
|
|
324
|
+
updatedAt: this.extractUpdatedAt(params) ?? Date.now(),
|
|
325
|
+
archived: session.archived,
|
|
326
|
+
}, true);
|
|
309
327
|
this.emitter?.({
|
|
310
328
|
type: "session.status",
|
|
311
329
|
properties: { sessionID: session.id, status: params.status ?? params },
|
|
@@ -605,12 +623,16 @@ export class CodexProvider {
|
|
|
605
623
|
message.time = Date.now();
|
|
606
624
|
}
|
|
607
625
|
async fetchServerThreads() {
|
|
626
|
+
return this.fetchServerThreadsByArchiveState(false);
|
|
627
|
+
}
|
|
628
|
+
async fetchServerThreadsByArchiveState(archived) {
|
|
608
629
|
const threads = [];
|
|
609
630
|
let nextCursor = null;
|
|
610
631
|
let hasRequestedFirstPage = false;
|
|
611
632
|
do {
|
|
612
633
|
const result = await this.call("thread/list", {
|
|
613
634
|
sourceKinds: THREAD_LIST_SOURCE_KINDS,
|
|
635
|
+
archived,
|
|
614
636
|
cursor: nextCursor,
|
|
615
637
|
});
|
|
616
638
|
const payload = this.asRecord(result);
|
|
@@ -622,7 +644,7 @@ export class CodexProvider {
|
|
|
622
644
|
? payload.threads
|
|
623
645
|
: [];
|
|
624
646
|
for (const entry of page) {
|
|
625
|
-
const parsed = this.parseThreadListEntry(entry);
|
|
647
|
+
const parsed = this.parseThreadListEntry(entry, archived);
|
|
626
648
|
if (parsed)
|
|
627
649
|
threads.push(parsed);
|
|
628
650
|
}
|
|
@@ -664,7 +686,7 @@ export class CodexProvider {
|
|
|
664
686
|
})
|
|
665
687
|
.filter((value) => Boolean(value));
|
|
666
688
|
}
|
|
667
|
-
parseThreadListEntry(value) {
|
|
689
|
+
parseThreadListEntry(value, archived = false) {
|
|
668
690
|
const obj = this.asRecord(value);
|
|
669
691
|
const id = this.extractThreadId(obj);
|
|
670
692
|
if (!id)
|
|
@@ -674,6 +696,7 @@ export class CodexProvider {
|
|
|
674
696
|
title: this.extractThreadTitle(obj),
|
|
675
697
|
createdAt: this.extractCreatedAt(obj),
|
|
676
698
|
updatedAt: this.extractUpdatedAt(obj),
|
|
699
|
+
archived,
|
|
677
700
|
};
|
|
678
701
|
}
|
|
679
702
|
hasNextCursor(value) {
|
|
@@ -687,7 +710,7 @@ export class CodexProvider {
|
|
|
687
710
|
const threadId = this.extractThreadId(payload);
|
|
688
711
|
if (!threadId)
|
|
689
712
|
return;
|
|
690
|
-
const title = this.extractThreadTitleFromUnknown(payload)
|
|
713
|
+
const title = this.extractThreadTitleFromUnknown(payload);
|
|
691
714
|
this.upsertSession({
|
|
692
715
|
id: threadId,
|
|
693
716
|
title,
|
|
@@ -695,22 +718,65 @@ export class CodexProvider {
|
|
|
695
718
|
updatedAt: this.extractUpdatedAt(payload) ?? Date.now(),
|
|
696
719
|
});
|
|
697
720
|
}
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
return existing;
|
|
721
|
+
reconcileSessionsWithServer(activeThreads, archivedThreads = []) {
|
|
722
|
+
const localSessions = this.sessions;
|
|
723
|
+
const merged = new Map();
|
|
724
|
+
for (const thread of activeThreads) {
|
|
725
|
+
const session = this.mergeSession(localSessions.get(thread.id), { ...thread, archived: false });
|
|
726
|
+
merged.set(thread.id, session);
|
|
705
727
|
}
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
728
|
+
for (const thread of archivedThreads) {
|
|
729
|
+
if (merged.has(thread.id))
|
|
730
|
+
continue;
|
|
731
|
+
const session = this.mergeSession(localSessions.get(thread.id), { ...thread, archived: true });
|
|
732
|
+
merged.set(thread.id, session);
|
|
733
|
+
}
|
|
734
|
+
for (const [id, session] of localSessions.entries()) {
|
|
735
|
+
if (!merged.has(id)) {
|
|
736
|
+
merged.set(id, session);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
this.sessions = merged;
|
|
740
|
+
}
|
|
741
|
+
mergeSession(existing, input) {
|
|
742
|
+
if (!existing) {
|
|
743
|
+
return {
|
|
744
|
+
id: input.id,
|
|
745
|
+
title: input.title ?? "Conversation",
|
|
746
|
+
createdAt: input.createdAt ?? Date.now(),
|
|
747
|
+
updatedAt: input.updatedAt ?? Date.now(),
|
|
748
|
+
archived: input.archived ?? false,
|
|
749
|
+
messages: [],
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
existing.title = input.title ?? existing.title;
|
|
753
|
+
existing.createdAt = input.createdAt ?? existing.createdAt;
|
|
754
|
+
existing.updatedAt = input.updatedAt != null
|
|
755
|
+
? Math.max(existing.updatedAt, input.updatedAt)
|
|
756
|
+
: existing.updatedAt;
|
|
757
|
+
existing.archived = input.archived ?? existing.archived ?? false;
|
|
758
|
+
return existing;
|
|
759
|
+
}
|
|
760
|
+
upsertSession(input, emitUpdated = false) {
|
|
761
|
+
const existing = this.sessions.get(input.id);
|
|
762
|
+
const before = existing
|
|
763
|
+
? {
|
|
764
|
+
title: existing.title,
|
|
765
|
+
createdAt: existing.createdAt,
|
|
766
|
+
updatedAt: existing.updatedAt,
|
|
767
|
+
archived: existing.archived ?? false,
|
|
768
|
+
}
|
|
769
|
+
: null;
|
|
770
|
+
const session = this.mergeSession(existing, input);
|
|
713
771
|
this.sessions.set(input.id, session);
|
|
772
|
+
const changed = !before
|
|
773
|
+
|| before.title !== session.title
|
|
774
|
+
|| before.createdAt !== session.createdAt
|
|
775
|
+
|| before.updatedAt !== session.updatedAt
|
|
776
|
+
|| before.archived !== (session.archived ?? false);
|
|
777
|
+
if (emitUpdated && changed) {
|
|
778
|
+
this.emitter?.({ type: "session.updated", properties: { info: this.toSessionInfo(session) } });
|
|
779
|
+
}
|
|
714
780
|
return session;
|
|
715
781
|
}
|
|
716
782
|
ensureLocalSession(sessionId) {
|
|
@@ -802,6 +868,7 @@ export class CodexProvider {
|
|
|
802
868
|
}
|
|
803
869
|
if (type === "commandexecution" || type === "enteredreviewmode" || type === "contextcompaction") {
|
|
804
870
|
const output = this.decodeCommandExecutionItemText(itemObject, type);
|
|
871
|
+
const input = this.extractCommandExecutionInput(itemObject);
|
|
805
872
|
messages.push({
|
|
806
873
|
id: itemId,
|
|
807
874
|
role: "assistant",
|
|
@@ -810,6 +877,7 @@ export class CodexProvider {
|
|
|
810
877
|
type: "tool",
|
|
811
878
|
name: type === "commandexecution" ? "command" : "system",
|
|
812
879
|
toolName: type === "commandexecution" ? "command" : "system",
|
|
880
|
+
...(input ? { input } : {}),
|
|
813
881
|
output,
|
|
814
882
|
state: "completed",
|
|
815
883
|
sessionID: threadId,
|
|
@@ -1054,6 +1122,14 @@ export class CodexProvider {
|
|
|
1054
1122
|
}
|
|
1055
1123
|
return undefined;
|
|
1056
1124
|
}
|
|
1125
|
+
firstStringFromSources(sources, keys) {
|
|
1126
|
+
for (const source of sources) {
|
|
1127
|
+
const value = this.firstString(source, keys);
|
|
1128
|
+
if (value)
|
|
1129
|
+
return value;
|
|
1130
|
+
}
|
|
1131
|
+
return undefined;
|
|
1132
|
+
}
|
|
1057
1133
|
readArray(value) {
|
|
1058
1134
|
return Array.isArray(value) ? value : [];
|
|
1059
1135
|
}
|
|
@@ -1190,12 +1266,13 @@ export class CodexProvider {
|
|
|
1190
1266
|
"old_path",
|
|
1191
1267
|
"oldPath",
|
|
1192
1268
|
"from",
|
|
1193
|
-
])
|
|
1269
|
+
]);
|
|
1270
|
+
const normalizedPath = this.normalizeDisplayPath(path) ?? "file";
|
|
1194
1271
|
const kind = this.firstString(obj, ["kind", "type", "action", "status"]) ?? "change";
|
|
1195
1272
|
const diff = this.firstString(obj, ["diff", "unified_diff", "unifiedDiff", "patch"]) ?? "";
|
|
1196
1273
|
return diff
|
|
1197
|
-
? `Path: ${
|
|
1198
|
-
: `Path: ${
|
|
1274
|
+
? `Path: ${normalizedPath}\nKind: ${kind}\n\n\`\`\`diff\n${diff}\n\`\`\``
|
|
1275
|
+
: `Path: ${normalizedPath}\nKind: ${kind}`;
|
|
1199
1276
|
})
|
|
1200
1277
|
.filter(Boolean);
|
|
1201
1278
|
return rendered.length > 0 ? rendered.join("\n\n---\n\n") : undefined;
|
|
@@ -1225,6 +1302,20 @@ export class CodexProvider {
|
|
|
1225
1302
|
.join("\n");
|
|
1226
1303
|
return patch.trim() || undefined;
|
|
1227
1304
|
}
|
|
1305
|
+
normalizeDisplayPath(rawPath) {
|
|
1306
|
+
if (!rawPath)
|
|
1307
|
+
return undefined;
|
|
1308
|
+
const trimmed = rawPath.trim();
|
|
1309
|
+
if (!trimmed)
|
|
1310
|
+
return undefined;
|
|
1311
|
+
if (!path.isAbsolute(trimmed))
|
|
1312
|
+
return trimmed;
|
|
1313
|
+
const relative = path.relative(process.cwd(), trimmed);
|
|
1314
|
+
if (!relative || relative.startsWith("..")) {
|
|
1315
|
+
return trimmed;
|
|
1316
|
+
}
|
|
1317
|
+
return relative;
|
|
1318
|
+
}
|
|
1228
1319
|
normalizedFileChangeStatus(itemObject, isCompleted) {
|
|
1229
1320
|
const status = this.readString(itemObject.status)
|
|
1230
1321
|
?? this.readString(this.asRecord(itemObject.status).type)
|
|
@@ -1235,8 +1326,40 @@ export class CodexProvider {
|
|
|
1235
1326
|
return isCompleted ? "completed" : "inProgress";
|
|
1236
1327
|
}
|
|
1237
1328
|
extractToolInput(item, params) {
|
|
1329
|
+
const normalizedType = this.normalizeStructuredType(this.readString(item.type) ?? "");
|
|
1330
|
+
if (normalizedType === "commandexecution") {
|
|
1331
|
+
return this.extractCommandExecutionInput(item, params);
|
|
1332
|
+
}
|
|
1238
1333
|
return item.input ?? item.command ?? item.path ?? item.args ?? params.command ?? params.path ?? undefined;
|
|
1239
1334
|
}
|
|
1335
|
+
extractCommandExecutionInput(item, params) {
|
|
1336
|
+
const event = this.asRecord(params?.event);
|
|
1337
|
+
const nestedItem = this.asRecord(event.item);
|
|
1338
|
+
const sources = [item, params ?? {}, event, nestedItem];
|
|
1339
|
+
const command = this.firstStringFromSources(sources, [
|
|
1340
|
+
"command",
|
|
1341
|
+
"cmd",
|
|
1342
|
+
"raw_command",
|
|
1343
|
+
"rawCommand",
|
|
1344
|
+
"invocation",
|
|
1345
|
+
"input",
|
|
1346
|
+
"fullCommand",
|
|
1347
|
+
"full_command",
|
|
1348
|
+
]);
|
|
1349
|
+
const cwd = this.firstStringFromSources(sources, [
|
|
1350
|
+
"cwd",
|
|
1351
|
+
"workdir",
|
|
1352
|
+
"workingDirectory",
|
|
1353
|
+
"working_directory",
|
|
1354
|
+
]);
|
|
1355
|
+
if (command && cwd)
|
|
1356
|
+
return { command, cwd };
|
|
1357
|
+
if (command)
|
|
1358
|
+
return { command };
|
|
1359
|
+
if (cwd)
|
|
1360
|
+
return { cwd };
|
|
1361
|
+
return undefined;
|
|
1362
|
+
}
|
|
1240
1363
|
describeCompletedItemOutput(item, params, normalizedType) {
|
|
1241
1364
|
if (normalizedType === "commandexecution") {
|
|
1242
1365
|
return this.firstString(item, ["stdout", "stderr", "text", "message", "summary"]);
|