lunel-cli 0.1.59 → 0.1.61
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 -2
- package/dist/ai/codex.js +93 -10
- package/dist/ai/index.d.ts +2 -2
- package/dist/ai/index.js +2 -2
- package/dist/ai/interface.d.ts +7 -1
- package/dist/ai/opencode.d.ts +2 -2
- package/dist/ai/opencode.js +7 -4
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/ai/codex.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIProvider, AiEventEmitter, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
|
|
1
|
+
import type { AIProvider, AiEventEmitter, FileAttachment, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
|
|
2
2
|
export declare class CodexProvider implements AIProvider {
|
|
3
3
|
private proc;
|
|
4
4
|
private shuttingDown;
|
|
@@ -25,7 +25,7 @@ export declare class CodexProvider implements AIProvider {
|
|
|
25
25
|
getMessages(sessionId: string): Promise<{
|
|
26
26
|
messages: MessageInfo[];
|
|
27
27
|
}>;
|
|
28
|
-
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string): Promise<{
|
|
28
|
+
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[]): Promise<{
|
|
29
29
|
ack: true;
|
|
30
30
|
}>;
|
|
31
31
|
abort(sessionId: string): Promise<Record<string, never>>;
|
|
@@ -74,7 +74,11 @@ export declare class CodexProvider implements AIProvider {
|
|
|
74
74
|
private resolveSessionFromPayload;
|
|
75
75
|
private resolveInFlightTurnId;
|
|
76
76
|
private decodeMessagesFromThreadRead;
|
|
77
|
+
private decodeUserMessageParts;
|
|
77
78
|
private decodeItemText;
|
|
79
|
+
private makeTurnInputPayload;
|
|
80
|
+
private shouldRetryTurnStartWithImageURLField;
|
|
81
|
+
private inferImageMimeFromDataUrl;
|
|
78
82
|
private decodeReasoningItemText;
|
|
79
83
|
private decodePlanItemText;
|
|
80
84
|
private decodeCommandExecutionItemText;
|
package/dist/ai/codex.js
CHANGED
|
@@ -120,17 +120,32 @@ export class CodexProvider {
|
|
|
120
120
|
});
|
|
121
121
|
return { messages: session.messages };
|
|
122
122
|
}
|
|
123
|
-
async prompt(sessionId, text, model, agent) {
|
|
123
|
+
async prompt(sessionId, text, model, agent, files = []) {
|
|
124
124
|
const session = this.ensureLocalSession(sessionId);
|
|
125
125
|
session.updatedAt = Date.now();
|
|
126
126
|
(async () => {
|
|
127
127
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
let imageUrlKey = "url";
|
|
129
|
+
while (true) {
|
|
130
|
+
try {
|
|
131
|
+
await this.call("turn/start", {
|
|
132
|
+
threadId: session.id,
|
|
133
|
+
input: this.makeTurnInputPayload(text, files, imageUrlKey),
|
|
134
|
+
...(model ? { model: model.providerID === "codex" ? model.modelID : `${model.providerID}/${model.modelID}` } : {}),
|
|
135
|
+
...(agent ? { agent } : {}),
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (imageUrlKey === "url"
|
|
141
|
+
&& files.length > 0
|
|
142
|
+
&& this.shouldRetryTurnStartWithImageURLField(err)) {
|
|
143
|
+
imageUrlKey = "image_url";
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
134
149
|
}
|
|
135
150
|
catch (err) {
|
|
136
151
|
const message = err.message;
|
|
@@ -857,13 +872,13 @@ export class CodexProvider {
|
|
|
857
872
|
const itemId = this.readString(itemObject.id) ?? crypto.randomUUID();
|
|
858
873
|
const timestamp = this.extractUpdatedAt(itemObject) ?? this.extractCreatedAt(itemObject) ?? (turnTime + orderOffset++);
|
|
859
874
|
if (type === "usermessage") {
|
|
860
|
-
const
|
|
861
|
-
if (
|
|
875
|
+
const parts = this.decodeUserMessageParts(itemObject, threadId, itemId);
|
|
876
|
+
if (parts.length === 0)
|
|
862
877
|
continue;
|
|
863
878
|
messages.push({
|
|
864
879
|
id: itemId,
|
|
865
880
|
role: "user",
|
|
866
|
-
parts
|
|
881
|
+
parts,
|
|
867
882
|
time: timestamp,
|
|
868
883
|
});
|
|
869
884
|
continue;
|
|
@@ -954,6 +969,40 @@ export class CodexProvider {
|
|
|
954
969
|
}
|
|
955
970
|
return messages;
|
|
956
971
|
}
|
|
972
|
+
decodeUserMessageParts(itemObject, threadId, itemId) {
|
|
973
|
+
const parts = [];
|
|
974
|
+
const content = this.readArray(itemObject.content);
|
|
975
|
+
let fileIndex = 0;
|
|
976
|
+
for (const entry of content) {
|
|
977
|
+
const obj = this.asRecord(entry);
|
|
978
|
+
const type = this.normalizedItemType(this.readString(obj.type) ?? "");
|
|
979
|
+
if (type === "image" || type === "localimage") {
|
|
980
|
+
const url = this.readString(obj.url) ?? this.readString(obj.image_url) ?? this.readString(obj.imageUrl);
|
|
981
|
+
if (!url)
|
|
982
|
+
continue;
|
|
983
|
+
parts.push({
|
|
984
|
+
id: `${itemId}:file:${fileIndex++}`,
|
|
985
|
+
type: "file",
|
|
986
|
+
mime: this.inferImageMimeFromDataUrl(url),
|
|
987
|
+
filename: this.readString(obj.filename) ?? this.readString(obj.name) ?? undefined,
|
|
988
|
+
url,
|
|
989
|
+
sessionID: threadId,
|
|
990
|
+
messageID: itemId,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
const text = this.decodeItemText(itemObject);
|
|
995
|
+
if (text) {
|
|
996
|
+
parts.push({
|
|
997
|
+
id: `${itemId}:text`,
|
|
998
|
+
type: "text",
|
|
999
|
+
text,
|
|
1000
|
+
sessionID: threadId,
|
|
1001
|
+
messageID: itemId,
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
return parts;
|
|
1005
|
+
}
|
|
957
1006
|
decodeItemText(itemObject) {
|
|
958
1007
|
const content = this.readArray(itemObject.content);
|
|
959
1008
|
const parts = [];
|
|
@@ -974,6 +1023,40 @@ export class CodexProvider {
|
|
|
974
1023
|
const joined = parts.join("\n").trim();
|
|
975
1024
|
return joined || this.readString(itemObject.text) || this.readString(itemObject.message) || "";
|
|
976
1025
|
}
|
|
1026
|
+
makeTurnInputPayload(text, files, imageUrlKey) {
|
|
1027
|
+
const items = [];
|
|
1028
|
+
for (const file of files) {
|
|
1029
|
+
const url = typeof file.url === "string" ? file.url.trim() : "";
|
|
1030
|
+
if (!url)
|
|
1031
|
+
continue;
|
|
1032
|
+
items.push({
|
|
1033
|
+
type: "image",
|
|
1034
|
+
[imageUrlKey]: url,
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
const trimmedText = text.trim();
|
|
1038
|
+
if (trimmedText) {
|
|
1039
|
+
items.push({ type: "text", text: trimmedText });
|
|
1040
|
+
}
|
|
1041
|
+
return items;
|
|
1042
|
+
}
|
|
1043
|
+
shouldRetryTurnStartWithImageURLField(error) {
|
|
1044
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
1045
|
+
if (!message.includes("image_url")) {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
return (message.includes("missing")
|
|
1049
|
+
|| message.includes("unknown field")
|
|
1050
|
+
|| message.includes("expected")
|
|
1051
|
+
|| message.includes("invalid"));
|
|
1052
|
+
}
|
|
1053
|
+
inferImageMimeFromDataUrl(url) {
|
|
1054
|
+
const match = /^data:([^;,]+)[;,]/i.exec(url);
|
|
1055
|
+
if (match?.[1]) {
|
|
1056
|
+
return match[1];
|
|
1057
|
+
}
|
|
1058
|
+
return "image/jpeg";
|
|
1059
|
+
}
|
|
977
1060
|
decodeReasoningItemText(itemObject) {
|
|
978
1061
|
const summary = this.flattenTextValue(itemObject.summary).trim();
|
|
979
1062
|
const content = this.flattenTextValue(itemObject.content).trim();
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AiEvent, ModelSelector } from "./interface.js";
|
|
1
|
+
import type { AiEvent, ModelSelector, FileAttachment } from "./interface.js";
|
|
2
2
|
export type AiBackend = "opencode" | "codex";
|
|
3
3
|
export declare class AiManager {
|
|
4
4
|
private _providers;
|
|
@@ -24,7 +24,7 @@ export declare class AiManager {
|
|
|
24
24
|
getMessages(backend: AiBackend, sessionId: string): Promise<{
|
|
25
25
|
messages: import("./interface.js").MessageInfo[];
|
|
26
26
|
}>;
|
|
27
|
-
prompt(backend: AiBackend, sessionId: string, text: string, model?: ModelSelector, agent?: string): Promise<{
|
|
27
|
+
prompt(backend: AiBackend, sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[]): Promise<{
|
|
28
28
|
ack: true;
|
|
29
29
|
}>;
|
|
30
30
|
abort(backend: AiBackend, sessionId: string): Promise<Record<string, never>>;
|
package/dist/ai/index.js
CHANGED
|
@@ -67,9 +67,9 @@ export class AiManager {
|
|
|
67
67
|
getSession(backend, id) { return this.get(backend).getSession(id); }
|
|
68
68
|
deleteSession(backend, id) { return this.get(backend).deleteSession(id); }
|
|
69
69
|
getMessages(backend, sessionId) { return this.get(backend).getMessages(sessionId); }
|
|
70
|
-
prompt(backend, sessionId, text, model, agent) {
|
|
70
|
+
prompt(backend, sessionId, text, model, agent, files) {
|
|
71
71
|
this.get(backend).setActiveSession?.(sessionId);
|
|
72
|
-
return this.get(backend).prompt(sessionId, text, model, agent);
|
|
72
|
+
return this.get(backend).prompt(sessionId, text, model, agent, files);
|
|
73
73
|
}
|
|
74
74
|
abort(backend, sessionId) { return this.get(backend).abort(sessionId); }
|
|
75
75
|
// Metadata — backend is optional, falls back to first available
|
package/dist/ai/interface.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export interface ModelSelector {
|
|
|
7
7
|
providerID: string;
|
|
8
8
|
modelID: string;
|
|
9
9
|
}
|
|
10
|
+
export interface FileAttachment {
|
|
11
|
+
type: "file";
|
|
12
|
+
mime: string;
|
|
13
|
+
filename?: string;
|
|
14
|
+
url: string;
|
|
15
|
+
}
|
|
10
16
|
export interface MessageInfo {
|
|
11
17
|
id: string;
|
|
12
18
|
role: string;
|
|
@@ -51,7 +57,7 @@ export interface AIProvider {
|
|
|
51
57
|
getMessages(sessionId: string): Promise<{
|
|
52
58
|
messages: MessageInfo[];
|
|
53
59
|
}>;
|
|
54
|
-
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string): Promise<{
|
|
60
|
+
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[]): Promise<{
|
|
55
61
|
ack: true;
|
|
56
62
|
}>;
|
|
57
63
|
abort(sessionId: string): Promise<Record<string, never>>;
|
package/dist/ai/opencode.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIProvider, AiEventEmitter, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
|
|
1
|
+
import type { AIProvider, AiEventEmitter, FileAttachment, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
|
|
2
2
|
export declare class OpenCodeProvider implements AIProvider {
|
|
3
3
|
private client;
|
|
4
4
|
private server;
|
|
@@ -25,7 +25,7 @@ export declare class OpenCodeProvider implements AIProvider {
|
|
|
25
25
|
getMessages(sessionId: string): Promise<{
|
|
26
26
|
messages: MessageInfo[];
|
|
27
27
|
}>;
|
|
28
|
-
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string): Promise<{
|
|
28
|
+
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[]): Promise<{
|
|
29
29
|
ack: true;
|
|
30
30
|
}>;
|
|
31
31
|
abort(sessionId: string): Promise<Record<string, never>>;
|
package/dist/ai/opencode.js
CHANGED
|
@@ -139,7 +139,7 @@ export class OpenCodeProvider {
|
|
|
139
139
|
// -------------------------------------------------------------------------
|
|
140
140
|
// Interaction
|
|
141
141
|
// -------------------------------------------------------------------------
|
|
142
|
-
async prompt(sessionId, text, model, agent) {
|
|
142
|
+
async prompt(sessionId, text, model, agent, files = []) {
|
|
143
143
|
if (sessionId)
|
|
144
144
|
this.lastActiveSessionId = sessionId;
|
|
145
145
|
if (VERBOSE_AI_LOGS) {
|
|
@@ -153,7 +153,7 @@ export class OpenCodeProvider {
|
|
|
153
153
|
// Fire-and-forget — results come back through the SSE event stream.
|
|
154
154
|
// Prefer the async prompt endpoint so long-running turns do not get tied
|
|
155
155
|
// to the request lifecycle the way the basic prompt route can be.
|
|
156
|
-
this.sendPromptAsync(sessionId, text, model, agent).catch((err) => {
|
|
156
|
+
this.sendPromptAsync(sessionId, text, model, agent, files).catch((err) => {
|
|
157
157
|
console.error("[ai] prompt error:", err.message);
|
|
158
158
|
this.emitter?.({
|
|
159
159
|
type: "prompt_error",
|
|
@@ -333,7 +333,7 @@ export class OpenCodeProvider {
|
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
|
-
async sendPromptAsync(sessionId, text, model, agent) {
|
|
336
|
+
async sendPromptAsync(sessionId, text, model, agent, files = []) {
|
|
337
337
|
const server = this.server;
|
|
338
338
|
const authHeader = this.authHeader;
|
|
339
339
|
if (!server || !authHeader) {
|
|
@@ -348,7 +348,10 @@ export class OpenCodeProvider {
|
|
|
348
348
|
accept: "application/json",
|
|
349
349
|
},
|
|
350
350
|
body: JSON.stringify({
|
|
351
|
-
parts: [
|
|
351
|
+
parts: [
|
|
352
|
+
...(text.trim().length > 0 ? [{ type: "text", text }] : []),
|
|
353
|
+
...files,
|
|
354
|
+
],
|
|
352
355
|
...(model ? { model } : {}),
|
|
353
356
|
...(agent ? { agent } : {}),
|
|
354
357
|
}),
|
package/dist/index.js
CHANGED
|
@@ -2363,7 +2363,7 @@ async function processMessage(message) {
|
|
|
2363
2363
|
result = { backends: aiManager.availableBackends() };
|
|
2364
2364
|
break;
|
|
2365
2365
|
case "prompt":
|
|
2366
|
-
result = await aiManager.prompt(backend, payload.sessionId, payload.text, payload.model, payload.agent);
|
|
2366
|
+
result = await aiManager.prompt(backend, payload.sessionId, payload.text, payload.model, payload.agent, payload.files);
|
|
2367
2367
|
break;
|
|
2368
2368
|
case "createSession":
|
|
2369
2369
|
result = await aiManager.createSession(backend, payload.title);
|