lunel-cli 0.1.114 → 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 +16 -2
- package/dist/ai/codex.js +446 -88
- package/dist/ai/index.d.ts +3 -0
- package/dist/ai/index.js +1 -0
- package/dist/ai/interface.d.ts +4 -0
- package/dist/ai/opencode.d.ts +5 -1
- package/dist/ai/opencode.js +317 -34
- package/dist/index.js +125 -6
- package/package.json +1 -1
package/dist/ai/codex.js
CHANGED
|
@@ -6,6 +6,18 @@ 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) {
|
|
@@ -29,12 +41,14 @@ export class CodexProvider {
|
|
|
29
41
|
proc = null;
|
|
30
42
|
shuttingDown = false;
|
|
31
43
|
emitter = null;
|
|
44
|
+
defaultModelContextWindow = null;
|
|
32
45
|
nextId = 1;
|
|
33
46
|
pending = new Map();
|
|
34
47
|
sessions = new Map();
|
|
35
48
|
deletedThreadIds = new Set();
|
|
36
49
|
resumedThreadIds = new Set();
|
|
37
50
|
pendingPermissionRequestIds = new Map();
|
|
51
|
+
pendingQuestionRequestIds = new Map();
|
|
38
52
|
assistantMessageIdByTurnId = new Map();
|
|
39
53
|
partTextById = new Map();
|
|
40
54
|
async init() {
|
|
@@ -61,7 +75,15 @@ export class CodexProvider {
|
|
|
61
75
|
this.emitter?.({ type: "sse_dead", properties: { error: msg } });
|
|
62
76
|
}
|
|
63
77
|
});
|
|
64
|
-
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
|
+
});
|
|
65
87
|
if (DEBUG_MODE)
|
|
66
88
|
console.log("Codex ready.\n");
|
|
67
89
|
}
|
|
@@ -143,6 +165,46 @@ export class CodexProvider {
|
|
|
143
165
|
}
|
|
144
166
|
return { deleted: true };
|
|
145
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
|
+
}
|
|
146
208
|
async getMessages(sessionId) {
|
|
147
209
|
const session = this.ensureLocalSession(sessionId);
|
|
148
210
|
const result = await this.call("thread/read", {
|
|
@@ -165,6 +227,17 @@ export class CodexProvider {
|
|
|
165
227
|
archived: false,
|
|
166
228
|
cwd: this.extractThreadCwd(threadObject) ?? session.cwd,
|
|
167
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
|
+
}
|
|
168
241
|
return { messages: session.messages };
|
|
169
242
|
}
|
|
170
243
|
async prompt(sessionId, text, model, agent, files = [], codexOptions) {
|
|
@@ -172,26 +245,31 @@ export class CodexProvider {
|
|
|
172
245
|
session.updatedAt = Date.now();
|
|
173
246
|
(async () => {
|
|
174
247
|
try {
|
|
175
|
-
|
|
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);
|
|
176
264
|
let imageUrlKey = "url";
|
|
177
265
|
while (true) {
|
|
178
266
|
try {
|
|
179
|
-
const effortLevels = ["low", "medium", "high"];
|
|
180
|
-
const speedDelta = {
|
|
181
|
-
fast: -1,
|
|
182
|
-
balanced: 0,
|
|
183
|
-
quality: 1,
|
|
184
|
-
};
|
|
185
|
-
const baseEffort = codexOptions?.reasoningEffort ?? "medium";
|
|
186
|
-
const baseIndex = effortLevels.indexOf(baseEffort);
|
|
187
|
-
const adjustedIndex = Math.max(0, Math.min(effortLevels.length - 1, baseIndex + (codexOptions?.speed ? speedDelta[codexOptions.speed] : 0)));
|
|
188
|
-
const reasoningEffort = effortLevels[adjustedIndex];
|
|
189
267
|
await this.call("turn/start", {
|
|
190
268
|
threadId: session.id,
|
|
191
269
|
input: this.makeTurnInputPayload(text, files, imageUrlKey),
|
|
192
|
-
...(
|
|
193
|
-
...(agent ? { agent } : {}),
|
|
270
|
+
...(modelId ? { model: modelId } : {}),
|
|
194
271
|
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
272
|
+
...(collaborationMode ? { collaborationMode } : {}),
|
|
195
273
|
});
|
|
196
274
|
break;
|
|
197
275
|
}
|
|
@@ -225,7 +303,7 @@ export class CodexProvider {
|
|
|
225
303
|
return {};
|
|
226
304
|
}
|
|
227
305
|
async agents() {
|
|
228
|
-
return { agents: [] };
|
|
306
|
+
return { agents: [...CODEX_AGENTS] };
|
|
229
307
|
}
|
|
230
308
|
async providers() {
|
|
231
309
|
const items = await this.fetchModels();
|
|
@@ -283,11 +361,53 @@ export class CodexProvider {
|
|
|
283
361
|
});
|
|
284
362
|
return {};
|
|
285
363
|
}
|
|
286
|
-
async questionReply() {
|
|
287
|
-
|
|
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 {};
|
|
288
390
|
}
|
|
289
|
-
async questionReject() {
|
|
290
|
-
|
|
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 {};
|
|
291
411
|
}
|
|
292
412
|
send(req) {
|
|
293
413
|
if (!this.proc?.stdin?.writable)
|
|
@@ -344,10 +464,28 @@ export class CodexProvider {
|
|
|
344
464
|
handleServerRequest(method, requestId, params) {
|
|
345
465
|
const session = this.resolveSessionFromPayload(params);
|
|
346
466
|
if (method === "item/tool/requestUserInput") {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
+
},
|
|
351
489
|
});
|
|
352
490
|
return;
|
|
353
491
|
}
|
|
@@ -427,6 +565,17 @@ export class CodexProvider {
|
|
|
427
565
|
});
|
|
428
566
|
}
|
|
429
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;
|
|
430
579
|
case "turn/completed":
|
|
431
580
|
case "turn/failed":
|
|
432
581
|
if (session) {
|
|
@@ -503,6 +652,9 @@ export class CodexProvider {
|
|
|
503
652
|
this.pendingPermissionRequestIds.delete(permissionId);
|
|
504
653
|
this.emitter?.({ type: "permission.replied", properties: { permissionId } });
|
|
505
654
|
}
|
|
655
|
+
if (permissionId && this.pendingQuestionRequestIds.has(permissionId)) {
|
|
656
|
+
this.pendingQuestionRequestIds.delete(permissionId);
|
|
657
|
+
}
|
|
506
658
|
return;
|
|
507
659
|
}
|
|
508
660
|
default:
|
|
@@ -621,7 +773,7 @@ export class CodexProvider {
|
|
|
621
773
|
const itemId = this.extractItemId(params) ?? this.readString(item.id) ?? normalizedType ?? "tool";
|
|
622
774
|
const fileChangeLike = this.isFileChangeStructuredItem(normalizedType, item, params);
|
|
623
775
|
const emittedPartType = normalizedType === "plan"
|
|
624
|
-
? "
|
|
776
|
+
? "plan"
|
|
625
777
|
: (fileChangeLike ? "file-change" : "tool");
|
|
626
778
|
const partId = `${messageId}:${emittedPartType}:${itemId}`;
|
|
627
779
|
const nextText = this.extractStructuredOutput(params, item, normalizedType);
|
|
@@ -644,8 +796,8 @@ export class CodexProvider {
|
|
|
644
796
|
sessionID: session.id,
|
|
645
797
|
messageID: messageId,
|
|
646
798
|
type: emittedPartType,
|
|
647
|
-
...(emittedPartType === "
|
|
648
|
-
? { text: outputValue ?? "Planning..." }
|
|
799
|
+
...(emittedPartType === "plan"
|
|
800
|
+
? { text: outputValue ?? (emittedPartType === "plan" ? "Planning..." : "") }
|
|
649
801
|
: { name, toolName: name, input, output: outputValue, state, ...(patch ? { patch } : {}) }),
|
|
650
802
|
};
|
|
651
803
|
this.upsertLocalMessagePart(session, messageId, part);
|
|
@@ -720,6 +872,22 @@ export class CodexProvider {
|
|
|
720
872
|
async fetchServerThreads() {
|
|
721
873
|
return this.fetchServerThreadsByArchiveState(false);
|
|
722
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
|
+
}
|
|
723
891
|
async refreshSessionMetadata(sessionId) {
|
|
724
892
|
const session = this.sessions.get(sessionId);
|
|
725
893
|
const result = await this.call("thread/read", {
|
|
@@ -800,6 +968,30 @@ export class CodexProvider {
|
|
|
800
968
|
})
|
|
801
969
|
.filter((value) => Boolean(value));
|
|
802
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
|
+
}
|
|
803
995
|
parseThreadListEntry(value, archived = false) {
|
|
804
996
|
const obj = this.asRecord(value);
|
|
805
997
|
const id = this.extractThreadId(obj);
|
|
@@ -909,7 +1101,7 @@ export class CodexProvider {
|
|
|
909
1101
|
return existing;
|
|
910
1102
|
return this.upsertSession({ id: sessionId, title: "Conversation", createdAt: Date.now(), updatedAt: Date.now() });
|
|
911
1103
|
}
|
|
912
|
-
async ensureThreadResumed(threadId, force = false) {
|
|
1104
|
+
async ensureThreadResumed(threadId, force = false, codexOptions, collaborationMode) {
|
|
913
1105
|
if (!threadId || this.resumedThreadIds.has(threadId)) {
|
|
914
1106
|
if (!force)
|
|
915
1107
|
return;
|
|
@@ -919,6 +1111,14 @@ export class CodexProvider {
|
|
|
919
1111
|
if (session?.cwd) {
|
|
920
1112
|
params.cwd = session.cwd;
|
|
921
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
|
+
}
|
|
922
1122
|
const result = await this.call("thread/resume", params);
|
|
923
1123
|
const payload = this.asRecord(result);
|
|
924
1124
|
const threadValue = payload.thread;
|
|
@@ -971,6 +1171,9 @@ export class CodexProvider {
|
|
|
971
1171
|
const turnId = this.readString(turnObject.id);
|
|
972
1172
|
const turnTime = this.extractUpdatedAt(turnObject) ?? this.extractCreatedAt(turnObject) ?? Date.now();
|
|
973
1173
|
const items = this.readArray(turnObject.items);
|
|
1174
|
+
const assistantParts = [];
|
|
1175
|
+
let assistantMessageId;
|
|
1176
|
+
let assistantTimestamp = turnTime;
|
|
974
1177
|
for (const item of items) {
|
|
975
1178
|
const itemObject = this.asRecord(item);
|
|
976
1179
|
const type = this.normalizedItemType(this.readString(itemObject.type) ?? "");
|
|
@@ -992,88 +1195,208 @@ export class CodexProvider {
|
|
|
992
1195
|
const text = this.decodeItemText(itemObject);
|
|
993
1196
|
if (!text)
|
|
994
1197
|
continue;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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,
|
|
1000
1206
|
});
|
|
1001
|
-
if (turnId)
|
|
1002
|
-
this.assistantMessageIdByTurnId.set(turnId, itemId);
|
|
1003
1207
|
continue;
|
|
1004
1208
|
}
|
|
1005
1209
|
if (type === "reasoning") {
|
|
1006
1210
|
const text = this.decodeReasoningItemText(itemObject);
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
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,
|
|
1012
1219
|
});
|
|
1013
1220
|
continue;
|
|
1014
1221
|
}
|
|
1015
1222
|
if (type === "plan") {
|
|
1016
1223
|
const text = this.decodePlanItemText(itemObject);
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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,
|
|
1022
1232
|
});
|
|
1023
1233
|
continue;
|
|
1024
1234
|
}
|
|
1025
|
-
if (type === "commandexecution"
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
output,
|
|
1038
|
-
state: "completed",
|
|
1039
|
-
sessionID: threadId,
|
|
1040
|
-
messageID: itemId,
|
|
1041
|
-
}],
|
|
1042
|
-
time: timestamp,
|
|
1043
|
-
});
|
|
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));
|
|
1044
1247
|
continue;
|
|
1045
1248
|
}
|
|
1046
1249
|
if (type === "filechange" || type === "toolcall" || type === "diff") {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
...(patch ? { patch } : {}),
|
|
1067
|
-
sessionID: threadId,
|
|
1068
|
-
messageID: itemId,
|
|
1069
|
-
}],
|
|
1070
|
-
time: timestamp,
|
|
1071
|
-
});
|
|
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);
|
|
1072
1269
|
}
|
|
1073
1270
|
}
|
|
1074
1271
|
}
|
|
1075
1272
|
return messages;
|
|
1076
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
|
+
}
|
|
1077
1400
|
decodeUserMessageParts(itemObject, threadId, itemId) {
|
|
1078
1401
|
const parts = [];
|
|
1079
1402
|
const content = this.readArray(itemObject.content);
|
|
@@ -1306,6 +1629,41 @@ export class CodexProvider {
|
|
|
1306
1629
|
?? this.readRawString(this.asRecord(payload.event).delta)
|
|
1307
1630
|
?? this.readRawString(this.asRecord(payload.event).message));
|
|
1308
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;
|
|
1666
|
+
}
|
|
1309
1667
|
extractThreadId(payload) {
|
|
1310
1668
|
if (!payload || typeof payload !== "object")
|
|
1311
1669
|
return undefined;
|