lunel-cli 0.1.118 → 0.1.120

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.
@@ -1,4 +1,4 @@
1
- import type { AIProvider, AiEventEmitter, CodexPromptOptions, AiSyncState, FileAttachment, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
1
+ import type { AIProvider, AiEventEmitter, CodexPromptOptions, 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;
@@ -36,7 +36,6 @@ export declare class CodexProvider implements AIProvider {
36
36
  getMessages(sessionId: string): Promise<{
37
37
  messages: MessageInfo[];
38
38
  }>;
39
- syncState(sessionIds?: string[]): Promise<AiSyncState>;
40
39
  prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[], codexOptions?: CodexPromptOptions): Promise<{
41
40
  ack: true;
42
41
  }>;
@@ -77,6 +76,7 @@ export declare class CodexProvider implements AIProvider {
77
76
  private refreshSessionMetadata;
78
77
  private fetchServerThreadsByArchiveState;
79
78
  private fetchModels;
79
+ private fetchModelById;
80
80
  private resolveModelId;
81
81
  private buildCollaborationMode;
82
82
  private parseThreadListEntry;
package/dist/ai/codex.js CHANGED
@@ -268,80 +268,25 @@ export class CodexProvider {
268
268
  }
269
269
  return { messages: session.messages };
270
270
  }
271
- async syncState(sessionIds = []) {
272
- await this.listSessions().catch(() => ({ sessions: [] }));
273
- const messageSessionIds = new Set(sessionIds);
274
- for (const session of this.sessions.values()) {
275
- if (session.activeTurnId)
276
- messageSessionIds.add(session.id);
277
- }
278
- for (const pending of this.pendingPermissionRequestIds.values()) {
279
- if (pending.sessionId)
280
- messageSessionIds.add(pending.sessionId);
281
- }
282
- for (const pending of this.pendingQuestionRequestIds.values()) {
283
- if (pending.sessionId)
284
- messageSessionIds.add(pending.sessionId);
285
- }
286
- const messages = {};
287
- const statuses = [];
288
- await Promise.allSettled(Array.from(messageSessionIds).map(async (sessionId) => {
289
- const response = await this.getMessages(sessionId);
290
- messages[sessionId] = response.messages;
291
- const session = this.sessions.get(sessionId);
292
- if (!session)
293
- return;
294
- const activeTurnId = await this.resolveInFlightTurnId(sessionId).catch(() => undefined);
295
- session.activeTurnId = activeTurnId;
296
- if (activeTurnId) {
297
- statuses.push({
298
- sessionID: sessionId,
299
- status: { type: "running", activeTurnId },
300
- });
301
- }
302
- }));
303
- return {
304
- sessions: Array.from(this.sessions.values()).map((session) => this.toSessionInfo(session)),
305
- statuses,
306
- messages,
307
- pendingPermissions: Array.from(this.pendingPermissionRequestIds.entries()).map(([id, pending]) => ({
308
- id,
309
- sessionID: pending.sessionId,
310
- messageID: pending.messageId,
311
- callID: pending.callId,
312
- type: pending.method,
313
- title: pending.method,
314
- metadata: { method: pending.method },
315
- })),
316
- pendingQuestions: Array.from(this.pendingQuestionRequestIds.entries()).map(([id, pending]) => ({
317
- id,
318
- sessionID: pending.sessionId,
319
- questions: [],
320
- tool: {
321
- ...(pending.messageId ? { messageID: pending.messageId } : {}),
322
- ...(pending.callId ? { callID: pending.callId } : {}),
323
- },
324
- })),
325
- statusAuthoritative: true,
326
- generatedAt: Date.now(),
327
- };
328
- }
329
271
  async prompt(sessionId, text, model, agent, files = [], codexOptions) {
330
272
  const session = this.ensureLocalSession(sessionId);
331
273
  session.updatedAt = Date.now();
332
274
  (async () => {
333
275
  try {
334
- const effortLevels = ["low", "medium", "high"];
335
- const speedDelta = {
336
- fast: -1,
337
- balanced: 0,
338
- quality: 1,
339
- };
340
- const baseEffort = codexOptions?.reasoningEffort ?? "medium";
341
- const baseIndex = effortLevels.indexOf(baseEffort);
342
- const adjustedIndex = Math.max(0, Math.min(effortLevels.length - 1, baseIndex + (codexOptions?.speed ? speedDelta[codexOptions.speed] : 0)));
343
- const reasoningEffort = effortLevels[adjustedIndex];
344
276
  const modelId = await this.resolveModelId(model);
277
+ const modelInfo = await this.fetchModelById(modelId);
278
+ const effortLevels = modelInfo?.supportedReasoningEfforts.map((effort) => effort.reasoningEffort) ?? [];
279
+ const defaultEffort = modelInfo?.defaultReasoningEffort ?? effortLevels[0] ?? "medium";
280
+ const requestedEffort = codexOptions?.reasoningEffort;
281
+ const reasoningEffort = requestedEffort && effortLevels.includes(requestedEffort)
282
+ ? requestedEffort
283
+ : defaultEffort;
284
+ const requestedSpeedTier = codexOptions?.speed && codexOptions.speed !== "default"
285
+ ? codexOptions.speed
286
+ : undefined;
287
+ const serviceTier = requestedSpeedTier && modelInfo?.additionalSpeedTiers.includes(requestedSpeedTier)
288
+ ? requestedSpeedTier
289
+ : undefined;
345
290
  const collaborationMode = this.buildCollaborationMode(agent, modelId, reasoningEffort);
346
291
  // Freshly created sessions already have a live backend thread from
347
292
  // thread/start. Forcing thread/resume here can attach stale state to a
@@ -354,7 +299,8 @@ export class CodexProvider {
354
299
  threadId: session.id,
355
300
  input: this.makeTurnInputPayload(text, files, imageUrlKey),
356
301
  ...(modelId ? { model: modelId } : {}),
357
- ...(reasoningEffort ? { reasoningEffort } : {}),
302
+ ...(reasoningEffort ? { effort: reasoningEffort } : {}),
303
+ ...(serviceTier ? { serviceTier } : {}),
358
304
  ...(collaborationMode ? { collaborationMode } : {}),
359
305
  });
360
306
  break;
@@ -400,6 +346,9 @@ export class CodexProvider {
400
346
  name: item.displayName || item.model,
401
347
  provider: "codex",
402
348
  description: item.description,
349
+ defaultReasoningEffort: item.defaultReasoningEffort,
350
+ supportedReasoningEfforts: item.supportedReasoningEfforts,
351
+ additionalSpeedTiers: item.additionalSpeedTiers,
403
352
  },
404
353
  ]));
405
354
  const defaultModel = items.find((item) => item.isDefault)?.model;
@@ -1044,16 +993,49 @@ export class CodexProvider {
1044
993
  const displayName = this.readString(obj.displayName)
1045
994
  ?? this.readString(obj.display_name)
1046
995
  ?? model;
996
+ const supportedReasoningEfforts = Array.isArray(obj.supportedReasoningEfforts)
997
+ ? obj.supportedReasoningEfforts
998
+ .map((effort) => {
999
+ const effortObj = this.asRecord(effort);
1000
+ const reasoningEffort = this.readString(effortObj.reasoningEffort)
1001
+ ?? this.readString(effortObj.reasoning_effort);
1002
+ if (!reasoningEffort)
1003
+ return undefined;
1004
+ const description = this.readString(effortObj.description);
1005
+ return {
1006
+ reasoningEffort,
1007
+ ...(description ? { description } : {}),
1008
+ };
1009
+ })
1010
+ .filter((effort) => Boolean(effort))
1011
+ : [];
1012
+ const defaultReasoningEffort = this.readString(obj.defaultReasoningEffort)
1013
+ ?? this.readString(obj.default_reasoning_effort);
1014
+ const additionalSpeedTiers = Array.isArray(obj.additionalSpeedTiers)
1015
+ ? obj.additionalSpeedTiers.filter((tier) => typeof tier === "string" && tier.length > 0)
1016
+ : [];
1047
1017
  return {
1048
1018
  id: this.readString(obj.id) ?? model,
1049
1019
  model,
1050
1020
  displayName,
1051
1021
  description: this.readString(obj.description) ?? "",
1052
1022
  isDefault: Boolean(obj.isDefault ?? obj.is_default),
1023
+ ...(defaultReasoningEffort ? { defaultReasoningEffort } : {}),
1024
+ supportedReasoningEfforts,
1025
+ additionalSpeedTiers,
1053
1026
  };
1054
1027
  })
1055
1028
  .filter((value) => Boolean(value));
1056
1029
  }
1030
+ async fetchModelById(modelId) {
1031
+ const items = await this.fetchModels();
1032
+ if (!modelId) {
1033
+ return items.find((item) => item.isDefault) ?? items[0];
1034
+ }
1035
+ return items.find((item) => item.model === modelId || item.id === modelId)
1036
+ ?? items.find((item) => item.isDefault)
1037
+ ?? items[0];
1038
+ }
1057
1039
  async resolveModelId(model) {
1058
1040
  if (model) {
1059
1041
  return model.providerID === "codex" ? model.modelID : `${model.providerID}/${model.modelID}`;
@@ -14,24 +14,6 @@ export declare class AiManager {
14
14
  backend: AiBackend;
15
15
  }>;
16
16
  }>;
17
- syncState(sessionIds?: Partial<Record<AiBackend, string[]>>): Promise<{
18
- sessions: Array<Record<string, unknown> & {
19
- backend: AiBackend;
20
- }>;
21
- statuses: Array<Record<string, unknown> & {
22
- backend: AiBackend;
23
- }>;
24
- messages: Record<string, unknown>;
25
- pendingPermissions: Array<Record<string, unknown> & {
26
- backend: AiBackend;
27
- }>;
28
- pendingQuestions: Array<Record<string, unknown> & {
29
- backend: AiBackend;
30
- }>;
31
- statusAuthoritativeByBackend: Partial<Record<AiBackend, boolean>>;
32
- syncedBackends: AiBackend[];
33
- generatedAt: number;
34
- }>;
35
17
  createSession(backend: AiBackend, title?: string): Promise<{
36
18
  session: import("./interface.js").SessionInfo;
37
19
  }>;
package/dist/ai/index.js CHANGED
@@ -68,51 +68,6 @@ export class AiManager {
68
68
  const sessions = results.flatMap((r) => (r.status === "fulfilled" ? r.value : []));
69
69
  return { sessions };
70
70
  }
71
- async syncState(sessionIds = {}) {
72
- const results = await Promise.allSettled(this._available.map(async (backend) => {
73
- const provider = this._providers[backend];
74
- const state = provider.syncState
75
- ? await provider.syncState(sessionIds[backend])
76
- : {
77
- sessions: (await provider.listSessions()).sessions,
78
- statuses: [],
79
- messages: {},
80
- generatedAt: Date.now(),
81
- };
82
- return { backend, state };
83
- }));
84
- const sessions = [];
85
- const statuses = [];
86
- const messages = {};
87
- const pendingPermissions = [];
88
- const pendingQuestions = [];
89
- const statusAuthoritativeByBackend = {};
90
- const syncedBackends = [];
91
- for (const result of results) {
92
- if (result.status !== "fulfilled")
93
- continue;
94
- const { backend, state } = result.value;
95
- syncedBackends.push(backend);
96
- statusAuthoritativeByBackend[backend] = state.statusAuthoritative !== false;
97
- sessions.push(...(state.sessions ?? []).map((session) => ({ ...session, backend })));
98
- statuses.push(...(state.statuses ?? []).map((status) => ({ ...status, backend })));
99
- for (const [sessionId, value] of Object.entries(state.messages ?? {})) {
100
- messages[`${backend}:${sessionId}`] = value;
101
- }
102
- pendingPermissions.push(...(state.pendingPermissions ?? []).map((permission) => ({ ...permission, backend })));
103
- pendingQuestions.push(...(state.pendingQuestions ?? []).map((question) => ({ ...question, backend })));
104
- }
105
- return {
106
- sessions,
107
- statuses,
108
- messages,
109
- pendingPermissions,
110
- pendingQuestions,
111
- statusAuthoritativeByBackend,
112
- syncedBackends,
113
- generatedAt: Date.now(),
114
- };
115
- }
116
71
  // Session management — all require explicit backend
117
72
  createSession(backend, title) { return this.get(backend).createSession(title); }
118
73
  getSession(backend, id) { return this.get(backend).getSession(id); }
@@ -8,8 +8,8 @@ export interface ModelSelector {
8
8
  modelID: string;
9
9
  }
10
10
  export interface CodexPromptOptions {
11
- reasoningEffort?: "low" | "medium" | "high";
12
- speed?: "fast" | "balanced" | "quality";
11
+ reasoningEffort?: string;
12
+ speed?: string;
13
13
  permissionMode?: "default" | "full-access";
14
14
  }
15
15
  export interface FileAttachment {
@@ -35,19 +35,6 @@ export interface ProviderInfo {
35
35
  default: Record<string, string>;
36
36
  [key: string]: unknown;
37
37
  }
38
- export interface AiSessionStatus {
39
- sessionID: string;
40
- status: Record<string, unknown> | string;
41
- }
42
- export interface AiSyncState {
43
- sessions: unknown[];
44
- statuses: AiSessionStatus[];
45
- messages: Record<string, MessageInfo[]>;
46
- pendingPermissions?: Record<string, unknown>[];
47
- pendingQuestions?: Record<string, unknown>[];
48
- statusAuthoritative?: boolean;
49
- generatedAt: number;
50
- }
51
38
  /**
52
39
  * Every AI backend (OpenCode, Codex, …) implements this interface.
53
40
  * Method names map 1-to-1 with the "ai" namespace actions in index.ts.
@@ -80,7 +67,6 @@ export interface AIProvider {
80
67
  getMessages(sessionId: string): Promise<{
81
68
  messages: MessageInfo[];
82
69
  }>;
83
- syncState?(sessionIds?: string[]): Promise<AiSyncState>;
84
70
  prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[], codexOptions?: CodexPromptOptions): Promise<{
85
71
  ack: true;
86
72
  }>;
@@ -1,4 +1,4 @@
1
- import type { AIProvider, AiEventEmitter, CodexPromptOptions, AiSyncState, FileAttachment, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
1
+ import type { AIProvider, AiEventEmitter, CodexPromptOptions, 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;
@@ -33,7 +33,6 @@ export declare class OpenCodeProvider implements AIProvider {
33
33
  getMessages(sessionId: string): Promise<{
34
34
  messages: MessageInfo[];
35
35
  }>;
36
- syncState(sessionIds?: string[]): Promise<AiSyncState>;
37
36
  prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[], codexOptions?: CodexPromptOptions): Promise<{
38
37
  ack: true;
39
38
  }>;
@@ -58,9 +57,6 @@ export declare class OpenCodeProvider implements AIProvider {
58
57
  private sendPromptAsync;
59
58
  private reconcileOpenCodeState;
60
59
  private refreshBusySessionMessages;
61
- private fetchSessionStatuses;
62
- private listPendingPermissions;
63
- private listPendingQuestions;
64
60
  private refreshSessionsMetadata;
65
61
  private refreshPendingPermissions;
66
62
  private refreshPendingQuestions;
@@ -393,57 +393,10 @@ export class OpenCodeProvider {
393
393
  throw err;
394
394
  }
395
395
  }
396
- async syncState(sessionIds = []) {
397
- const [sessionsResult, statusesResult, permissionsResult, questionsResult] = await Promise.allSettled([
398
- this.listSessions(),
399
- this.fetchSessionStatuses(),
400
- this.listPendingPermissions(),
401
- this.listPendingQuestions(),
402
- ]);
403
- const sessions = sessionsResult.status === "fulfilled"
404
- ? (sessionsResult.value.sessions ?? [])
405
- : [];
406
- const statuses = statusesResult.status === "fulfilled" ? statusesResult.value : [];
407
- const pendingPermissions = permissionsResult.status === "fulfilled" ? permissionsResult.value : [];
408
- const pendingQuestions = questionsResult.status === "fulfilled" ? questionsResult.value : [];
409
- const messageSessionIds = new Set(sessionIds);
410
- for (const entry of statuses) {
411
- const statusObj = typeof entry.status === "object" && entry.status !== null ? entry.status : {};
412
- const statusType = typeof statusObj.type === "string" ? statusObj.type.toLowerCase() : String(entry.status ?? "").toLowerCase();
413
- if (statusType === "busy" || statusType === "running" || statusType === "working" || statusType === "retry") {
414
- messageSessionIds.add(entry.sessionID);
415
- }
416
- }
417
- for (const permission of pendingPermissions) {
418
- const sessionID = this.readString(permission.sessionID) ?? this.readString(permission.sessionId);
419
- if (sessionID)
420
- messageSessionIds.add(sessionID);
421
- }
422
- for (const question of pendingQuestions) {
423
- const sessionID = this.readString(question.sessionID) ?? this.readString(question.sessionId);
424
- if (sessionID)
425
- messageSessionIds.add(sessionID);
426
- }
427
- const messages = {};
428
- await Promise.allSettled(Array.from(messageSessionIds).map(async (sessionId) => {
429
- const response = await this.getMessages(sessionId);
430
- messages[sessionId] = response.messages;
431
- }));
432
- return {
433
- sessions,
434
- statuses,
435
- messages,
436
- pendingPermissions,
437
- pendingQuestions,
438
- statusAuthoritative: statusesResult.status === "fulfilled",
439
- generatedAt: Date.now(),
440
- };
441
- }
442
396
  // -------------------------------------------------------------------------
443
397
  // Interaction
444
398
  // -------------------------------------------------------------------------
445
399
  async prompt(sessionId, text, model, agent, files = [], codexOptions) {
446
- void codexOptions;
447
400
  if (sessionId)
448
401
  this.lastActiveSessionId = sessionId;
449
402
  if (VERBOSE_AI_LOGS) {
@@ -457,7 +410,7 @@ export class OpenCodeProvider {
457
410
  // Fire-and-forget — results come back through the SSE event stream.
458
411
  // Prefer the async prompt endpoint so long-running turns do not get tied
459
412
  // to the request lifecycle the way the basic prompt route can be.
460
- this.sendPromptAsync(sessionId, text, model, agent, files).catch((err) => {
413
+ this.sendPromptAsync(sessionId, text, model, agent, files, codexOptions).catch((err) => {
461
414
  console.error("[ai] prompt error:", err.message);
462
415
  this.emitter?.({
463
416
  type: "prompt_error",
@@ -644,7 +597,7 @@ export class OpenCodeProvider {
644
597
  }
645
598
  }
646
599
  }
647
- async sendPromptAsync(sessionId, text, model, agent, files = []) {
600
+ async sendPromptAsync(sessionId, text, model, agent, files = [], promptOptions) {
648
601
  const server = this.server;
649
602
  const authHeader = this.authHeader;
650
603
  if (!server || !authHeader) {
@@ -665,6 +618,7 @@ export class OpenCodeProvider {
665
618
  ],
666
619
  ...(model ? { model } : {}),
667
620
  ...(agent ? { agent } : {}),
621
+ ...(promptOptions?.reasoningEffort ? { variant: promptOptions.reasoningEffort } : {}),
668
622
  }),
669
623
  });
670
624
  if (!response.ok) {
@@ -736,43 +690,6 @@ export class OpenCodeProvider {
736
690
  }
737
691
  }
738
692
  }
739
- async fetchSessionStatuses() {
740
- const payload = await this.fetchOpenCodeJson("/session/status", { method: "GET" });
741
- if (!payload || typeof payload !== "object")
742
- return [];
743
- return Object.entries(payload).map(([sessionID, status]) => ({
744
- sessionID,
745
- status: status,
746
- }));
747
- }
748
- async listPendingPermissions() {
749
- const permissionApi = this.client?.permission;
750
- if (!permissionApi?.list)
751
- return [];
752
- const response = await permissionApi.list();
753
- const data = Array.isArray(response.data) ? response.data : [];
754
- return data
755
- .map((entry) => normalizePermissionProperties(this.asRecord(entry)))
756
- .filter((entry) => !!this.readString(entry.id));
757
- }
758
- async listPendingQuestions() {
759
- const data = await this.fetchOpenCodeJson("/question", { method: "GET" });
760
- const questions = Array.isArray(data) ? data : [];
761
- return questions
762
- .flatMap((entry) => {
763
- const question = this.asRecord(entry);
764
- const id = this.readString(question.id);
765
- const sessionID = this.readString(question.sessionID) ?? this.readString(question.sessionId);
766
- if (!id || !sessionID)
767
- return [];
768
- return [{
769
- id,
770
- sessionID,
771
- questions: Array.isArray(question.questions) ? question.questions : [],
772
- tool: typeof question.tool === "object" && question.tool !== null ? question.tool : undefined,
773
- }];
774
- });
775
- }
776
693
  async refreshSessionsMetadata() {
777
694
  const response = await this.client.session.list();
778
695
  const sessions = Array.isArray(response.data) ? response.data : [];
package/dist/index.js CHANGED
@@ -2716,14 +2716,6 @@ async function processMessage(message) {
2716
2716
  case "listSessions":
2717
2717
  result = await aiManager.listAllSessions();
2718
2718
  break;
2719
- case "syncState": {
2720
- const rawSessionIds = payload.sessionIds;
2721
- result = await aiManager.syncState({
2722
- opencode: Array.isArray(rawSessionIds?.opencode) ? rawSessionIds.opencode.filter((id) => typeof id === "string") : undefined,
2723
- codex: Array.isArray(rawSessionIds?.codex) ? rawSessionIds.codex.filter((id) => typeof id === "string") : undefined,
2724
- });
2725
- break;
2726
- }
2727
2719
  case "getSession":
2728
2720
  result = await aiManager.getSession(backend, payload.id);
2729
2721
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.118",
3
+ "version": "0.1.120",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",