lunel-cli 0.1.113 → 0.1.115
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 +19 -4
- package/dist/ai/codex.js +470 -95
- package/dist/ai/index.d.ts +5 -2
- package/dist/ai/index.js +3 -2
- package/dist/ai/interface.d.ts +9 -1
- package/dist/ai/opencode.d.ts +7 -3
- package/dist/ai/opencode.js +319 -35
- package/dist/index.js +126 -7
- package/package.json +1 -1
package/dist/ai/codex.js
CHANGED
|
@@ -6,32 +6,49 @@ import * as path from "path";
|
|
|
6
6
|
import { spawn } from "child_process";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
8
|
const THREAD_LIST_SOURCE_KINDS = ["cli", "vscode", "appServer", "exec", "unknown"];
|
|
9
|
+
const CODEX_AGENTS = [
|
|
10
|
+
{
|
|
11
|
+
name: "Build",
|
|
12
|
+
mode: "default",
|
|
13
|
+
description: "Default Codex collaboration mode.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "plan",
|
|
17
|
+
mode: "plan",
|
|
18
|
+
description: "Plan-first Codex collaboration mode.",
|
|
19
|
+
},
|
|
20
|
+
];
|
|
9
21
|
const DEBUG_MODE = process.env.LUNEL_DEBUG === "1" || process.env.LUNEL_DEBUG_AI === "1";
|
|
10
22
|
function joinStreamingText(previousText, nextChunk) {
|
|
11
23
|
if (!previousText) {
|
|
12
24
|
return nextChunk;
|
|
13
25
|
}
|
|
14
|
-
if (
|
|
15
|
-
return
|
|
26
|
+
if (nextChunk.startsWith(previousText)) {
|
|
27
|
+
return nextChunk;
|
|
16
28
|
}
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
29
|
+
if (previousText.endsWith(nextChunk)) {
|
|
30
|
+
return previousText;
|
|
19
31
|
}
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
const maxOverlap = Math.min(previousText.length, nextChunk.length);
|
|
33
|
+
for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
|
|
34
|
+
if (previousText.slice(-overlap) === nextChunk.slice(0, overlap)) {
|
|
35
|
+
return previousText + nextChunk.slice(overlap);
|
|
36
|
+
}
|
|
22
37
|
}
|
|
23
|
-
return
|
|
38
|
+
return previousText + nextChunk;
|
|
24
39
|
}
|
|
25
40
|
export class CodexProvider {
|
|
26
41
|
proc = null;
|
|
27
42
|
shuttingDown = false;
|
|
28
43
|
emitter = null;
|
|
44
|
+
defaultModelContextWindow = null;
|
|
29
45
|
nextId = 1;
|
|
30
46
|
pending = new Map();
|
|
31
47
|
sessions = new Map();
|
|
32
48
|
deletedThreadIds = new Set();
|
|
33
49
|
resumedThreadIds = new Set();
|
|
34
50
|
pendingPermissionRequestIds = new Map();
|
|
51
|
+
pendingQuestionRequestIds = new Map();
|
|
35
52
|
assistantMessageIdByTurnId = new Map();
|
|
36
53
|
partTextById = new Map();
|
|
37
54
|
async init() {
|
|
@@ -58,7 +75,15 @@ export class CodexProvider {
|
|
|
58
75
|
this.emitter?.({ type: "sse_dead", properties: { error: msg } });
|
|
59
76
|
}
|
|
60
77
|
});
|
|
61
|
-
await this.call("initialize", {
|
|
78
|
+
await this.call("initialize", {
|
|
79
|
+
clientInfo: { name: "lunel", version: "1.0" },
|
|
80
|
+
capabilities: {
|
|
81
|
+
experimentalApi: true,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
await this.refreshConfigDefaults().catch(() => {
|
|
85
|
+
// Config defaults are best-effort metadata only.
|
|
86
|
+
});
|
|
62
87
|
if (DEBUG_MODE)
|
|
63
88
|
console.log("Codex ready.\n");
|
|
64
89
|
}
|
|
@@ -140,6 +165,46 @@ export class CodexProvider {
|
|
|
140
165
|
}
|
|
141
166
|
return { deleted: true };
|
|
142
167
|
}
|
|
168
|
+
async renameSession(id, title) {
|
|
169
|
+
const trimmed = title.trim();
|
|
170
|
+
if (!trimmed) {
|
|
171
|
+
throw new Error("Session title cannot be empty");
|
|
172
|
+
}
|
|
173
|
+
const methodsToTry = [
|
|
174
|
+
{ method: "thread/name/set", params: { threadId: id, name: trimmed } },
|
|
175
|
+
{ method: "thread/name/set", params: { threadId: id, title: trimmed } },
|
|
176
|
+
{ method: "thread/name/update", params: { threadId: id, name: trimmed } },
|
|
177
|
+
{ method: "thread/name/update", params: { threadId: id, title: trimmed } },
|
|
178
|
+
{ method: "thread/metadata/update", params: { threadId: id, title: trimmed } },
|
|
179
|
+
{ method: "thread/update", params: { threadId: id, title: trimmed } },
|
|
180
|
+
{ method: "thread/rename", params: { threadId: id, title: trimmed } },
|
|
181
|
+
];
|
|
182
|
+
let renamed = false;
|
|
183
|
+
let lastError = null;
|
|
184
|
+
for (const entry of methodsToTry) {
|
|
185
|
+
try {
|
|
186
|
+
await this.call(entry.method, entry.params);
|
|
187
|
+
renamed = true;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
lastError = err;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!renamed && lastError) {
|
|
195
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
196
|
+
}
|
|
197
|
+
const existing = this.sessions.get(id) ?? this.ensureLocalSession(id);
|
|
198
|
+
const session = this.upsertSession({
|
|
199
|
+
id,
|
|
200
|
+
title: trimmed,
|
|
201
|
+
createdAt: existing.createdAt,
|
|
202
|
+
updatedAt: Date.now(),
|
|
203
|
+
archived: existing.archived,
|
|
204
|
+
cwd: existing.cwd,
|
|
205
|
+
}, true);
|
|
206
|
+
return { session: this.toSessionInfo(session) };
|
|
207
|
+
}
|
|
143
208
|
async getMessages(sessionId) {
|
|
144
209
|
const session = this.ensureLocalSession(sessionId);
|
|
145
210
|
const result = await this.call("thread/read", {
|
|
@@ -162,22 +227,49 @@ export class CodexProvider {
|
|
|
162
227
|
archived: false,
|
|
163
228
|
cwd: this.extractThreadCwd(threadObject) ?? session.cwd,
|
|
164
229
|
});
|
|
230
|
+
if (this.defaultModelContextWindow && this.defaultModelContextWindow > 0) {
|
|
231
|
+
this.emitter?.({
|
|
232
|
+
type: "session.usage",
|
|
233
|
+
properties: {
|
|
234
|
+
sessionID: sessionId,
|
|
235
|
+
tokenUsage: {
|
|
236
|
+
modelContextWindow: this.defaultModelContextWindow,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
165
241
|
return { messages: session.messages };
|
|
166
242
|
}
|
|
167
|
-
async prompt(sessionId, text, model, agent, files = []) {
|
|
243
|
+
async prompt(sessionId, text, model, agent, files = [], codexOptions) {
|
|
168
244
|
const session = this.ensureLocalSession(sessionId);
|
|
169
245
|
session.updatedAt = Date.now();
|
|
170
246
|
(async () => {
|
|
171
247
|
try {
|
|
172
|
-
|
|
248
|
+
const effortLevels = ["low", "medium", "high"];
|
|
249
|
+
const speedDelta = {
|
|
250
|
+
fast: -1,
|
|
251
|
+
balanced: 0,
|
|
252
|
+
quality: 1,
|
|
253
|
+
};
|
|
254
|
+
const baseEffort = codexOptions?.reasoningEffort ?? "medium";
|
|
255
|
+
const baseIndex = effortLevels.indexOf(baseEffort);
|
|
256
|
+
const adjustedIndex = Math.max(0, Math.min(effortLevels.length - 1, baseIndex + (codexOptions?.speed ? speedDelta[codexOptions.speed] : 0)));
|
|
257
|
+
const reasoningEffort = effortLevels[adjustedIndex];
|
|
258
|
+
const modelId = await this.resolveModelId(model);
|
|
259
|
+
const collaborationMode = this.buildCollaborationMode(agent, modelId, reasoningEffort);
|
|
260
|
+
// Freshly created sessions already have a live backend thread from
|
|
261
|
+
// thread/start. Forcing thread/resume here can attach stale state to a
|
|
262
|
+
// brand-new session and trigger rollout lookup failures on turn/start.
|
|
263
|
+
await this.ensureThreadResumed(session.id, false, codexOptions, collaborationMode);
|
|
173
264
|
let imageUrlKey = "url";
|
|
174
265
|
while (true) {
|
|
175
266
|
try {
|
|
176
267
|
await this.call("turn/start", {
|
|
177
268
|
threadId: session.id,
|
|
178
269
|
input: this.makeTurnInputPayload(text, files, imageUrlKey),
|
|
179
|
-
...(
|
|
180
|
-
...(
|
|
270
|
+
...(modelId ? { model: modelId } : {}),
|
|
271
|
+
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
272
|
+
...(collaborationMode ? { collaborationMode } : {}),
|
|
181
273
|
});
|
|
182
274
|
break;
|
|
183
275
|
}
|
|
@@ -211,7 +303,7 @@ export class CodexProvider {
|
|
|
211
303
|
return {};
|
|
212
304
|
}
|
|
213
305
|
async agents() {
|
|
214
|
-
return { agents: [] };
|
|
306
|
+
return { agents: [...CODEX_AGENTS] };
|
|
215
307
|
}
|
|
216
308
|
async providers() {
|
|
217
309
|
const items = await this.fetchModels();
|
|
@@ -269,11 +361,53 @@ export class CodexProvider {
|
|
|
269
361
|
});
|
|
270
362
|
return {};
|
|
271
363
|
}
|
|
272
|
-
async questionReply() {
|
|
273
|
-
|
|
364
|
+
async questionReply(sessionId, questionId, answers) {
|
|
365
|
+
const pending = this.pendingQuestionRequestIds.get(questionId);
|
|
366
|
+
if (!pending) {
|
|
367
|
+
this.emitter?.({
|
|
368
|
+
type: "question.replied",
|
|
369
|
+
properties: { sessionID: sessionId, requestID: questionId, answers, skipped: true },
|
|
370
|
+
});
|
|
371
|
+
return {};
|
|
372
|
+
}
|
|
373
|
+
const responseAnswers = {};
|
|
374
|
+
pending.questionIds.forEach((id, index) => {
|
|
375
|
+
responseAnswers[id] = {
|
|
376
|
+
answers: Array.isArray(answers[index]) ? answers[index].filter((value) => typeof value === "string") : [],
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
this.send({
|
|
380
|
+
jsonrpc: "2.0",
|
|
381
|
+
id: pending.requestId,
|
|
382
|
+
result: { answers: responseAnswers },
|
|
383
|
+
});
|
|
384
|
+
this.pendingQuestionRequestIds.delete(questionId);
|
|
385
|
+
this.emitter?.({
|
|
386
|
+
type: "question.replied",
|
|
387
|
+
properties: { sessionID: sessionId, requestID: questionId, answers },
|
|
388
|
+
});
|
|
389
|
+
return {};
|
|
274
390
|
}
|
|
275
|
-
async questionReject() {
|
|
276
|
-
|
|
391
|
+
async questionReject(sessionId, questionId) {
|
|
392
|
+
const pending = this.pendingQuestionRequestIds.get(questionId);
|
|
393
|
+
if (!pending) {
|
|
394
|
+
this.emitter?.({
|
|
395
|
+
type: "question.rejected",
|
|
396
|
+
properties: { sessionID: sessionId, requestID: questionId, skipped: true },
|
|
397
|
+
});
|
|
398
|
+
return {};
|
|
399
|
+
}
|
|
400
|
+
this.send({
|
|
401
|
+
jsonrpc: "2.0",
|
|
402
|
+
id: pending.requestId,
|
|
403
|
+
result: { answers: {} },
|
|
404
|
+
});
|
|
405
|
+
this.pendingQuestionRequestIds.delete(questionId);
|
|
406
|
+
this.emitter?.({
|
|
407
|
+
type: "question.rejected",
|
|
408
|
+
properties: { sessionID: sessionId, requestID: questionId },
|
|
409
|
+
});
|
|
410
|
+
return {};
|
|
277
411
|
}
|
|
278
412
|
send(req) {
|
|
279
413
|
if (!this.proc?.stdin?.writable)
|
|
@@ -330,10 +464,28 @@ export class CodexProvider {
|
|
|
330
464
|
handleServerRequest(method, requestId, params) {
|
|
331
465
|
const session = this.resolveSessionFromPayload(params);
|
|
332
466
|
if (method === "item/tool/requestUserInput") {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
467
|
+
const questionRequestId = String(requestId);
|
|
468
|
+
const callId = this.extractItemId(params) ?? undefined;
|
|
469
|
+
const messageId = session ? this.ensureAssistantMessage(session, this.extractTurnId(params) ?? `session:${session.id}`) : undefined;
|
|
470
|
+
const questions = this.extractStructuredUserInputQuestions(params);
|
|
471
|
+
this.pendingQuestionRequestIds.set(questionRequestId, {
|
|
472
|
+
sessionId: session?.id ?? "",
|
|
473
|
+
requestId,
|
|
474
|
+
messageId,
|
|
475
|
+
callId,
|
|
476
|
+
questionIds: questions.map((question) => this.readString(this.asRecord(question).id)).filter((value) => Boolean(value)),
|
|
477
|
+
});
|
|
478
|
+
this.emitter?.({
|
|
479
|
+
type: "question.asked",
|
|
480
|
+
properties: {
|
|
481
|
+
id: questionRequestId,
|
|
482
|
+
sessionID: session?.id,
|
|
483
|
+
questions,
|
|
484
|
+
tool: {
|
|
485
|
+
...(messageId ? { messageID: messageId } : {}),
|
|
486
|
+
...(callId ? { callID: callId } : {}),
|
|
487
|
+
},
|
|
488
|
+
},
|
|
337
489
|
});
|
|
338
490
|
return;
|
|
339
491
|
}
|
|
@@ -413,6 +565,17 @@ export class CodexProvider {
|
|
|
413
565
|
});
|
|
414
566
|
}
|
|
415
567
|
return;
|
|
568
|
+
case "thread/tokenUsage/updated":
|
|
569
|
+
if (session) {
|
|
570
|
+
this.emitter?.({
|
|
571
|
+
type: "session.usage",
|
|
572
|
+
properties: {
|
|
573
|
+
sessionID: session.id,
|
|
574
|
+
tokenUsage: params.tokenUsage ?? null,
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
416
579
|
case "turn/completed":
|
|
417
580
|
case "turn/failed":
|
|
418
581
|
if (session) {
|
|
@@ -489,6 +652,9 @@ export class CodexProvider {
|
|
|
489
652
|
this.pendingPermissionRequestIds.delete(permissionId);
|
|
490
653
|
this.emitter?.({ type: "permission.replied", properties: { permissionId } });
|
|
491
654
|
}
|
|
655
|
+
if (permissionId && this.pendingQuestionRequestIds.has(permissionId)) {
|
|
656
|
+
this.pendingQuestionRequestIds.delete(permissionId);
|
|
657
|
+
}
|
|
492
658
|
return;
|
|
493
659
|
}
|
|
494
660
|
default:
|
|
@@ -607,7 +773,7 @@ export class CodexProvider {
|
|
|
607
773
|
const itemId = this.extractItemId(params) ?? this.readString(item.id) ?? normalizedType ?? "tool";
|
|
608
774
|
const fileChangeLike = this.isFileChangeStructuredItem(normalizedType, item, params);
|
|
609
775
|
const emittedPartType = normalizedType === "plan"
|
|
610
|
-
? "
|
|
776
|
+
? "plan"
|
|
611
777
|
: (fileChangeLike ? "file-change" : "tool");
|
|
612
778
|
const partId = `${messageId}:${emittedPartType}:${itemId}`;
|
|
613
779
|
const nextText = this.extractStructuredOutput(params, item, normalizedType);
|
|
@@ -630,8 +796,8 @@ export class CodexProvider {
|
|
|
630
796
|
sessionID: session.id,
|
|
631
797
|
messageID: messageId,
|
|
632
798
|
type: emittedPartType,
|
|
633
|
-
...(emittedPartType === "
|
|
634
|
-
? { text: outputValue ?? "Planning..." }
|
|
799
|
+
...(emittedPartType === "plan"
|
|
800
|
+
? { text: outputValue ?? (emittedPartType === "plan" ? "Planning..." : "") }
|
|
635
801
|
: { name, toolName: name, input, output: outputValue, state, ...(patch ? { patch } : {}) }),
|
|
636
802
|
};
|
|
637
803
|
this.upsertLocalMessagePart(session, messageId, part);
|
|
@@ -706,6 +872,22 @@ export class CodexProvider {
|
|
|
706
872
|
async fetchServerThreads() {
|
|
707
873
|
return this.fetchServerThreadsByArchiveState(false);
|
|
708
874
|
}
|
|
875
|
+
async refreshConfigDefaults() {
|
|
876
|
+
const result = await this.call("config/read", undefined);
|
|
877
|
+
const payload = this.asRecord(result);
|
|
878
|
+
const config = this.asRecord(payload.config ?? result);
|
|
879
|
+
const raw = config.model_context_window;
|
|
880
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) {
|
|
881
|
+
this.defaultModelContextWindow = raw;
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
885
|
+
const parsed = Number(raw);
|
|
886
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
887
|
+
this.defaultModelContextWindow = parsed;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
709
891
|
async refreshSessionMetadata(sessionId) {
|
|
710
892
|
const session = this.sessions.get(sessionId);
|
|
711
893
|
const result = await this.call("thread/read", {
|
|
@@ -786,6 +968,30 @@ export class CodexProvider {
|
|
|
786
968
|
})
|
|
787
969
|
.filter((value) => Boolean(value));
|
|
788
970
|
}
|
|
971
|
+
async resolveModelId(model) {
|
|
972
|
+
if (model) {
|
|
973
|
+
return model.providerID === "codex" ? model.modelID : `${model.providerID}/${model.modelID}`;
|
|
974
|
+
}
|
|
975
|
+
const items = await this.fetchModels();
|
|
976
|
+
return items.find((item) => item.isDefault)?.model ?? items[0]?.model;
|
|
977
|
+
}
|
|
978
|
+
buildCollaborationMode(agent, modelId, reasoningEffort) {
|
|
979
|
+
const normalizedAgent = (agent ?? "").trim().toLowerCase();
|
|
980
|
+
const mode = normalizedAgent === "build" ? "default" : normalizedAgent;
|
|
981
|
+
if (mode !== "default" && mode !== "plan") {
|
|
982
|
+
return undefined;
|
|
983
|
+
}
|
|
984
|
+
if (!modelId) {
|
|
985
|
+
return undefined;
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
mode,
|
|
989
|
+
settings: {
|
|
990
|
+
model: modelId,
|
|
991
|
+
reasoning_effort: reasoningEffort,
|
|
992
|
+
},
|
|
993
|
+
};
|
|
994
|
+
}
|
|
789
995
|
parseThreadListEntry(value, archived = false) {
|
|
790
996
|
const obj = this.asRecord(value);
|
|
791
997
|
const id = this.extractThreadId(obj);
|
|
@@ -895,7 +1101,7 @@ export class CodexProvider {
|
|
|
895
1101
|
return existing;
|
|
896
1102
|
return this.upsertSession({ id: sessionId, title: "Conversation", createdAt: Date.now(), updatedAt: Date.now() });
|
|
897
1103
|
}
|
|
898
|
-
async ensureThreadResumed(threadId, force = false) {
|
|
1104
|
+
async ensureThreadResumed(threadId, force = false, codexOptions, collaborationMode) {
|
|
899
1105
|
if (!threadId || this.resumedThreadIds.has(threadId)) {
|
|
900
1106
|
if (!force)
|
|
901
1107
|
return;
|
|
@@ -905,6 +1111,14 @@ export class CodexProvider {
|
|
|
905
1111
|
if (session?.cwd) {
|
|
906
1112
|
params.cwd = session.cwd;
|
|
907
1113
|
}
|
|
1114
|
+
const permissionMode = codexOptions?.permissionMode ?? "default";
|
|
1115
|
+
if (permissionMode === "full-access") {
|
|
1116
|
+
params.approvalPolicy = "never";
|
|
1117
|
+
params.sandbox = "danger-full-access";
|
|
1118
|
+
}
|
|
1119
|
+
if (collaborationMode) {
|
|
1120
|
+
params.collaborationMode = collaborationMode;
|
|
1121
|
+
}
|
|
908
1122
|
const result = await this.call("thread/resume", params);
|
|
909
1123
|
const payload = this.asRecord(result);
|
|
910
1124
|
const threadValue = payload.thread;
|
|
@@ -957,6 +1171,9 @@ export class CodexProvider {
|
|
|
957
1171
|
const turnId = this.readString(turnObject.id);
|
|
958
1172
|
const turnTime = this.extractUpdatedAt(turnObject) ?? this.extractCreatedAt(turnObject) ?? Date.now();
|
|
959
1173
|
const items = this.readArray(turnObject.items);
|
|
1174
|
+
const assistantParts = [];
|
|
1175
|
+
let assistantMessageId;
|
|
1176
|
+
let assistantTimestamp = turnTime;
|
|
960
1177
|
for (const item of items) {
|
|
961
1178
|
const itemObject = this.asRecord(item);
|
|
962
1179
|
const type = this.normalizedItemType(this.readString(itemObject.type) ?? "");
|
|
@@ -978,88 +1195,208 @@ export class CodexProvider {
|
|
|
978
1195
|
const text = this.decodeItemText(itemObject);
|
|
979
1196
|
if (!text)
|
|
980
1197
|
continue;
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1198
|
+
assistantMessageId = assistantMessageId ?? itemId;
|
|
1199
|
+
assistantTimestamp = Math.max(assistantTimestamp, timestamp);
|
|
1200
|
+
assistantParts.push({
|
|
1201
|
+
id: `${assistantMessageId}:text:${itemId}`,
|
|
1202
|
+
type: "text",
|
|
1203
|
+
text,
|
|
1204
|
+
sessionID: threadId,
|
|
1205
|
+
messageID: assistantMessageId,
|
|
986
1206
|
});
|
|
987
|
-
if (turnId)
|
|
988
|
-
this.assistantMessageIdByTurnId.set(turnId, itemId);
|
|
989
1207
|
continue;
|
|
990
1208
|
}
|
|
991
1209
|
if (type === "reasoning") {
|
|
992
1210
|
const text = this.decodeReasoningItemText(itemObject);
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1211
|
+
assistantMessageId = assistantMessageId ?? (turnId ? `assistant:${turnId}` : itemId);
|
|
1212
|
+
assistantTimestamp = Math.max(assistantTimestamp, timestamp);
|
|
1213
|
+
assistantParts.push({
|
|
1214
|
+
id: `${assistantMessageId}:reasoning:${itemId}`,
|
|
1215
|
+
type: "reasoning",
|
|
1216
|
+
text,
|
|
1217
|
+
sessionID: threadId,
|
|
1218
|
+
messageID: assistantMessageId,
|
|
998
1219
|
});
|
|
999
1220
|
continue;
|
|
1000
1221
|
}
|
|
1001
1222
|
if (type === "plan") {
|
|
1002
1223
|
const text = this.decodePlanItemText(itemObject);
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1224
|
+
assistantMessageId = assistantMessageId ?? (turnId ? `assistant:${turnId}` : itemId);
|
|
1225
|
+
assistantTimestamp = Math.max(assistantTimestamp, timestamp);
|
|
1226
|
+
assistantParts.push({
|
|
1227
|
+
id: `${assistantMessageId}:plan:${itemId}`,
|
|
1228
|
+
type: "plan",
|
|
1229
|
+
text,
|
|
1230
|
+
sessionID: threadId,
|
|
1231
|
+
messageID: assistantMessageId,
|
|
1008
1232
|
});
|
|
1009
1233
|
continue;
|
|
1010
1234
|
}
|
|
1011
|
-
if (type === "commandexecution"
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
output,
|
|
1024
|
-
state: "completed",
|
|
1025
|
-
sessionID: threadId,
|
|
1026
|
-
messageID: itemId,
|
|
1027
|
-
}],
|
|
1028
|
-
time: timestamp,
|
|
1029
|
-
});
|
|
1235
|
+
if (type === "commandexecution"
|
|
1236
|
+
|| type === "enteredreviewmode"
|
|
1237
|
+
|| type === "exitedreviewmode"
|
|
1238
|
+
|| type === "contextcompaction"
|
|
1239
|
+
|| type === "mcptoolcall"
|
|
1240
|
+
|| type === "dynamictoolcall"
|
|
1241
|
+
|| type === "collabtoolcall"
|
|
1242
|
+
|| type === "websearch"
|
|
1243
|
+
|| type === "imageview") {
|
|
1244
|
+
assistantMessageId = assistantMessageId ?? (turnId ? `assistant:${turnId}` : itemId);
|
|
1245
|
+
assistantTimestamp = Math.max(assistantTimestamp, timestamp);
|
|
1246
|
+
assistantParts.push(this.decodeStoredToolLikePart(type, itemObject, threadId, assistantMessageId, itemId));
|
|
1030
1247
|
continue;
|
|
1031
1248
|
}
|
|
1032
1249
|
if (type === "filechange" || type === "toolcall" || type === "diff") {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
...(patch ? { patch } : {}),
|
|
1053
|
-
sessionID: threadId,
|
|
1054
|
-
messageID: itemId,
|
|
1055
|
-
}],
|
|
1056
|
-
time: timestamp,
|
|
1057
|
-
});
|
|
1250
|
+
assistantMessageId = assistantMessageId ?? (turnId ? `assistant:${turnId}` : itemId);
|
|
1251
|
+
assistantTimestamp = Math.max(assistantTimestamp, timestamp);
|
|
1252
|
+
assistantParts.push(this.decodeStoredToolLikePart(type, itemObject, threadId, assistantMessageId, itemId));
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
if (assistantParts.length > 0) {
|
|
1256
|
+
const resolvedAssistantMessageId = assistantMessageId ?? (turnId ? `assistant:${turnId}` : crypto.randomUUID());
|
|
1257
|
+
messages.push({
|
|
1258
|
+
id: resolvedAssistantMessageId,
|
|
1259
|
+
role: "assistant",
|
|
1260
|
+
parts: assistantParts.map((part) => ({
|
|
1261
|
+
...part,
|
|
1262
|
+
sessionID: threadId,
|
|
1263
|
+
messageID: resolvedAssistantMessageId,
|
|
1264
|
+
})),
|
|
1265
|
+
time: assistantTimestamp,
|
|
1266
|
+
});
|
|
1267
|
+
if (turnId) {
|
|
1268
|
+
this.assistantMessageIdByTurnId.set(turnId, resolvedAssistantMessageId);
|
|
1058
1269
|
}
|
|
1059
1270
|
}
|
|
1060
1271
|
}
|
|
1061
1272
|
return messages;
|
|
1062
1273
|
}
|
|
1274
|
+
decodeStoredToolLikePart(type, itemObject, threadId, messageId, itemId) {
|
|
1275
|
+
if (type === "filechange" || type === "diff" || this.isFileChangeStructuredItem(type, itemObject)) {
|
|
1276
|
+
const output = this.decodeFileLikeItemText(itemObject) ?? this.describeCompletedItemOutput(itemObject, itemObject, type) ?? "File changes";
|
|
1277
|
+
const patch = this.extractCanonicalPatch(itemObject, itemObject);
|
|
1278
|
+
return {
|
|
1279
|
+
id: `${messageId}:file-change:${itemId}`,
|
|
1280
|
+
type: "file-change",
|
|
1281
|
+
name: this.describeToolPart(type, type, itemObject),
|
|
1282
|
+
toolName: this.describeToolPart(type, type, itemObject),
|
|
1283
|
+
output,
|
|
1284
|
+
state: "completed",
|
|
1285
|
+
...(patch ? { patch } : {}),
|
|
1286
|
+
sessionID: threadId,
|
|
1287
|
+
messageID: messageId,
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
const name = this.describeStoredToolName(type, itemObject);
|
|
1291
|
+
const input = this.extractStoredToolInput(type, itemObject);
|
|
1292
|
+
const output = this.extractStoredToolOutput(type, itemObject);
|
|
1293
|
+
return {
|
|
1294
|
+
id: `${messageId}:tool:${itemId}`,
|
|
1295
|
+
type: "tool",
|
|
1296
|
+
name,
|
|
1297
|
+
toolName: name,
|
|
1298
|
+
...(input !== undefined ? { input } : {}),
|
|
1299
|
+
...(output !== undefined ? { output } : {}),
|
|
1300
|
+
state: "completed",
|
|
1301
|
+
sessionID: threadId,
|
|
1302
|
+
messageID: messageId,
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
describeStoredToolName(type, itemObject) {
|
|
1306
|
+
if (type === "commandexecution") {
|
|
1307
|
+
return "command";
|
|
1308
|
+
}
|
|
1309
|
+
if (type === "websearch") {
|
|
1310
|
+
return "web-search";
|
|
1311
|
+
}
|
|
1312
|
+
if (type === "imageview") {
|
|
1313
|
+
return "image-view";
|
|
1314
|
+
}
|
|
1315
|
+
if (type === "enteredreviewmode") {
|
|
1316
|
+
return "review";
|
|
1317
|
+
}
|
|
1318
|
+
if (type === "exitedreviewmode") {
|
|
1319
|
+
return "review";
|
|
1320
|
+
}
|
|
1321
|
+
if (type === "contextcompaction") {
|
|
1322
|
+
return "context";
|
|
1323
|
+
}
|
|
1324
|
+
return this.firstString(itemObject, [
|
|
1325
|
+
"name",
|
|
1326
|
+
"toolName",
|
|
1327
|
+
"tool_name",
|
|
1328
|
+
"title",
|
|
1329
|
+
"serverToolName",
|
|
1330
|
+
"server_tool_name",
|
|
1331
|
+
"kind",
|
|
1332
|
+
]) ?? type;
|
|
1333
|
+
}
|
|
1334
|
+
extractStoredToolInput(type, itemObject) {
|
|
1335
|
+
if (type === "commandexecution") {
|
|
1336
|
+
return this.extractCommandExecutionInput(itemObject, itemObject);
|
|
1337
|
+
}
|
|
1338
|
+
return itemObject.input
|
|
1339
|
+
?? itemObject.arguments
|
|
1340
|
+
?? itemObject.args
|
|
1341
|
+
?? itemObject.query
|
|
1342
|
+
?? itemObject.path
|
|
1343
|
+
?? itemObject.url
|
|
1344
|
+
?? itemObject.command
|
|
1345
|
+
?? itemObject.pattern
|
|
1346
|
+
?? undefined;
|
|
1347
|
+
}
|
|
1348
|
+
extractStoredToolOutput(type, itemObject) {
|
|
1349
|
+
if (type === "commandexecution") {
|
|
1350
|
+
return this.decodeStoredCommandExecutionOutput(itemObject);
|
|
1351
|
+
}
|
|
1352
|
+
if (type === "enteredreviewmode") {
|
|
1353
|
+
return `Reviewing ${this.readString(itemObject.review) ?? "changes"}...`;
|
|
1354
|
+
}
|
|
1355
|
+
if (type === "exitedreviewmode") {
|
|
1356
|
+
return this.firstString(itemObject, ["summary", "text", "message"]) ?? "Exited review mode";
|
|
1357
|
+
}
|
|
1358
|
+
if (type === "contextcompaction") {
|
|
1359
|
+
return "Context compacted";
|
|
1360
|
+
}
|
|
1361
|
+
if (type === "imageview") {
|
|
1362
|
+
const path = this.firstString(itemObject, ["path", "file", "filePath", "file_path", "url"]);
|
|
1363
|
+
return path ? `Viewed image ${path}` : "Viewed image";
|
|
1364
|
+
}
|
|
1365
|
+
const direct = this.firstString(itemObject, [
|
|
1366
|
+
"aggregatedOutput",
|
|
1367
|
+
"output",
|
|
1368
|
+
"outputText",
|
|
1369
|
+
"output_text",
|
|
1370
|
+
"text",
|
|
1371
|
+
"message",
|
|
1372
|
+
"summary",
|
|
1373
|
+
"result",
|
|
1374
|
+
"content",
|
|
1375
|
+
]);
|
|
1376
|
+
if (direct?.trim()) {
|
|
1377
|
+
return direct.trim();
|
|
1378
|
+
}
|
|
1379
|
+
const flattened = this.flattenTextValue(itemObject.output ?? itemObject.result ?? itemObject.content).trim();
|
|
1380
|
+
return flattened || undefined;
|
|
1381
|
+
}
|
|
1382
|
+
decodeStoredCommandExecutionOutput(itemObject) {
|
|
1383
|
+
const aggregatedOutput = this.firstString(itemObject, [
|
|
1384
|
+
"aggregatedOutput",
|
|
1385
|
+
"aggregated_output",
|
|
1386
|
+
"output",
|
|
1387
|
+
"outputText",
|
|
1388
|
+
"output_text",
|
|
1389
|
+
"stdout",
|
|
1390
|
+
"stderr",
|
|
1391
|
+
"text",
|
|
1392
|
+
"message",
|
|
1393
|
+
"summary",
|
|
1394
|
+
]);
|
|
1395
|
+
if (aggregatedOutput?.trim()) {
|
|
1396
|
+
return aggregatedOutput.trim();
|
|
1397
|
+
}
|
|
1398
|
+
return this.decodeCommandExecutionItemText(itemObject, "commandexecution");
|
|
1399
|
+
}
|
|
1063
1400
|
decodeUserMessageParts(itemObject, threadId, itemId) {
|
|
1064
1401
|
const parts = [];
|
|
1065
1402
|
const content = this.readArray(itemObject.content);
|
|
@@ -1282,15 +1619,50 @@ export class CodexProvider {
|
|
|
1282
1619
|
?? this.readString(this.asRecord(this.asRecord(payload.event).item).id));
|
|
1283
1620
|
}
|
|
1284
1621
|
extractTextPayload(payload) {
|
|
1285
|
-
return (this.
|
|
1286
|
-
?? this.
|
|
1287
|
-
?? this.
|
|
1288
|
-
?? this.
|
|
1289
|
-
?? this.
|
|
1290
|
-
?? this.
|
|
1291
|
-
?? this.
|
|
1292
|
-
?? this.
|
|
1293
|
-
?? this.
|
|
1622
|
+
return (this.readRawString(payload.delta)
|
|
1623
|
+
?? this.readRawString(payload.text)
|
|
1624
|
+
?? this.readRawString(payload.message)
|
|
1625
|
+
?? this.readRawString(this.asRecord(payload.item).text)
|
|
1626
|
+
?? this.readRawString(this.asRecord(payload.item).delta)
|
|
1627
|
+
?? this.readRawString(this.asRecord(payload.item).message)
|
|
1628
|
+
?? this.readRawString(this.asRecord(payload.event).text)
|
|
1629
|
+
?? this.readRawString(this.asRecord(payload.event).delta)
|
|
1630
|
+
?? this.readRawString(this.asRecord(payload.event).message));
|
|
1631
|
+
}
|
|
1632
|
+
extractStructuredUserInputQuestions(payload) {
|
|
1633
|
+
const rawQuestions = this.readArray(payload.questions);
|
|
1634
|
+
const questions = [];
|
|
1635
|
+
for (const value of rawQuestions) {
|
|
1636
|
+
const question = this.asRecord(value);
|
|
1637
|
+
const options = this.readArray(question.options)
|
|
1638
|
+
.map((option) => {
|
|
1639
|
+
const optionObject = this.asRecord(option);
|
|
1640
|
+
const label = this.readString(optionObject.label);
|
|
1641
|
+
const description = this.readString(optionObject.description);
|
|
1642
|
+
if (!label)
|
|
1643
|
+
return undefined;
|
|
1644
|
+
return {
|
|
1645
|
+
label,
|
|
1646
|
+
...(description ? { description } : {}),
|
|
1647
|
+
};
|
|
1648
|
+
})
|
|
1649
|
+
.filter((value) => Boolean(value));
|
|
1650
|
+
const id = this.readString(question.id);
|
|
1651
|
+
const header = this.readString(question.header);
|
|
1652
|
+
const prompt = this.readString(question.question);
|
|
1653
|
+
if (!id || !header || !prompt) {
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
questions.push({
|
|
1657
|
+
id,
|
|
1658
|
+
header,
|
|
1659
|
+
question: prompt,
|
|
1660
|
+
...(options.length > 0 ? { options } : {}),
|
|
1661
|
+
...(typeof question.isOther === "boolean" ? { isOther: question.isOther } : {}),
|
|
1662
|
+
...(typeof question.isSecret === "boolean" ? { isSecret: question.isSecret } : {}),
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
return questions;
|
|
1294
1666
|
}
|
|
1295
1667
|
extractThreadId(payload) {
|
|
1296
1668
|
if (!payload || typeof payload !== "object")
|
|
@@ -1362,6 +1734,9 @@ export class CodexProvider {
|
|
|
1362
1734
|
readArray(value) {
|
|
1363
1735
|
return Array.isArray(value) ? value : [];
|
|
1364
1736
|
}
|
|
1737
|
+
readRawString(value) {
|
|
1738
|
+
return typeof value === "string" ? value : undefined;
|
|
1739
|
+
}
|
|
1365
1740
|
readString(value) {
|
|
1366
1741
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1367
1742
|
}
|