@galaxy-yearn/codex-deepseek-gateway 0.1.5 → 0.1.6
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/README.md +27 -28
- package/bin/codex-deepseek-gateway.js +40 -11
- package/config/codex-model-catalog.json +130 -0
- package/package.json +2 -1
- package/src/codex-launch.js +240 -45
- package/src/codex-sessions.js +19 -7
- package/src/protocol.js +328 -63
- package/src/server.js +3 -0
package/src/codex-launch.js
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
4
|
import { dirname, join, resolve } from 'node:path';
|
|
4
5
|
import { clearScreenDown, cursorTo } from 'node:readline';
|
|
5
6
|
import { readCodexConfig } from './codex-config.js';
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_PROVIDER = 'deepseek-gateway';
|
|
8
9
|
export const REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
|
|
9
|
-
const SELECTED_ROW = '\x1b[38;
|
|
10
|
+
const SELECTED_ROW = '\x1b[38;2;91;124;255m';
|
|
10
11
|
const RESET_STYLE = '\x1b[0m';
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const CODEX_PLATFORM_PACKAGE_BY_TARGET = {
|
|
14
|
+
'x86_64-unknown-linux-musl': '@openai/codex-linux-x64',
|
|
15
|
+
'aarch64-unknown-linux-musl': '@openai/codex-linux-arm64',
|
|
16
|
+
'x86_64-apple-darwin': '@openai/codex-darwin-x64',
|
|
17
|
+
'aarch64-apple-darwin': '@openai/codex-darwin-arm64',
|
|
18
|
+
'x86_64-pc-windows-msvc': '@openai/codex-win32-x64',
|
|
19
|
+
'aarch64-pc-windows-msvc': '@openai/codex-win32-arm64',
|
|
20
|
+
};
|
|
11
21
|
|
|
12
22
|
function print(message = '') {
|
|
13
23
|
process.stdout.write(message);
|
|
@@ -46,6 +56,7 @@ export function createLaunchContext(options) {
|
|
|
46
56
|
all: options.all,
|
|
47
57
|
codexHome,
|
|
48
58
|
installDir: options.dir,
|
|
59
|
+
modelCatalogPath: join(options.dir, 'config', 'codex-model-catalog.json'),
|
|
49
60
|
models,
|
|
50
61
|
projectRoot: findProjectRoot(process.cwd()),
|
|
51
62
|
provider: options.provider || DEFAULT_PROVIDER,
|
|
@@ -59,7 +70,7 @@ export function missingModelMessage(context) {
|
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
function configOverrideArgs(context) {
|
|
62
|
-
|
|
73
|
+
const args = [
|
|
63
74
|
'-c',
|
|
64
75
|
`model_provider=${JSON.stringify(context.provider)}`,
|
|
65
76
|
'-c',
|
|
@@ -71,21 +82,10 @@ function configOverrideArgs(context) {
|
|
|
71
82
|
'-c',
|
|
72
83
|
'model_reasoning_summary="auto"',
|
|
73
84
|
];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return
|
|
78
|
-
'-c',
|
|
79
|
-
`model_provider=${context.provider}`,
|
|
80
|
-
'-c',
|
|
81
|
-
`model=${context.model}`,
|
|
82
|
-
'-c',
|
|
83
|
-
`model_reasoning_effort=${context.reasoningEffort}`,
|
|
84
|
-
'-c',
|
|
85
|
-
'model_supports_reasoning_summaries=true',
|
|
86
|
-
'-c',
|
|
87
|
-
'model_reasoning_summary=auto',
|
|
88
|
-
];
|
|
85
|
+
if (context.provider === DEFAULT_PROVIDER && context.modelCatalogPath) {
|
|
86
|
+
args.push('-c', `model_catalog_json=${JSON.stringify(context.modelCatalogPath)}`);
|
|
87
|
+
}
|
|
88
|
+
return args;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
export function codexNewArgs(context) {
|
|
@@ -96,20 +96,146 @@ export function codexResumeArgs(sessionId, context) {
|
|
|
96
96
|
return ['resume', sessionId, ...configOverrideArgs(context)];
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
function quoteShellArg(arg) {
|
|
100
|
+
const value = String(arg);
|
|
101
|
+
if (/^[A-Za-z0-9_./:=@%+,-]+$/.test(value)) return value;
|
|
102
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
export function codexResumeCommand(sessionId, context) {
|
|
104
|
-
return ['codex',
|
|
106
|
+
return ['codex', ...codexResumeArgs(sessionId, context)].map(quoteShellArg).join(' ');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function codexTargetTriple(platform = process.platform, arch = process.arch) {
|
|
110
|
+
if ((platform === 'linux' || platform === 'android') && arch === 'x64') return 'x86_64-unknown-linux-musl';
|
|
111
|
+
if ((platform === 'linux' || platform === 'android') && arch === 'arm64') return 'aarch64-unknown-linux-musl';
|
|
112
|
+
if (platform === 'darwin' && arch === 'x64') return 'x86_64-apple-darwin';
|
|
113
|
+
if (platform === 'darwin' && arch === 'arm64') return 'aarch64-apple-darwin';
|
|
114
|
+
if (platform === 'win32' && arch === 'x64') return 'x86_64-pc-windows-msvc';
|
|
115
|
+
if (platform === 'win32' && arch === 'arm64') return 'aarch64-pc-windows-msvc';
|
|
116
|
+
return '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function pathEntries(env = process.env) {
|
|
120
|
+
const pathValue = env.PATH || env.Path || env.path || '';
|
|
121
|
+
return pathValue.split(process.platform === 'win32' ? ';' : ':').filter(Boolean);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function candidateExecutables(command, env = process.env) {
|
|
125
|
+
if (command.includes('/') || command.includes('\\')) return [command];
|
|
126
|
+
const extensions = process.platform === 'win32'
|
|
127
|
+
? (env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
|
|
128
|
+
: [''];
|
|
129
|
+
const names = process.platform === 'win32'
|
|
130
|
+
? extensions.map((extension) => `${command}${extension.toLowerCase()}`)
|
|
131
|
+
: [command];
|
|
132
|
+
return pathEntries(env).flatMap((entry) => names.map((name) => join(entry, name)));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function npmShimCodexPackageRoot(shimPath) {
|
|
136
|
+
const base = dirname(shimPath);
|
|
137
|
+
const packageRoot = join(base, 'node_modules', '@openai', 'codex');
|
|
138
|
+
return existsSync(join(packageRoot, 'package.json')) ? packageRoot : '';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function ancestorCodexPackageRoot(filePath) {
|
|
142
|
+
try {
|
|
143
|
+
let current = dirname(realpathSync(filePath));
|
|
144
|
+
while (true) {
|
|
145
|
+
const packageJson = join(current, 'package.json');
|
|
146
|
+
if (existsSync(packageJson)) {
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(readFileSync(packageJson, 'utf8'));
|
|
149
|
+
if (parsed?.name === '@openai/codex') return current;
|
|
150
|
+
} catch {
|
|
151
|
+
// Keep walking; this is only a best-effort executable locator.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const parent = dirname(current);
|
|
155
|
+
if (parent === current) return '';
|
|
156
|
+
current = parent;
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
return '';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function codexPackageRoot() {
|
|
164
|
+
try {
|
|
165
|
+
return dirname(require.resolve('@openai/codex/package.json'));
|
|
166
|
+
} catch {
|
|
167
|
+
for (const candidate of candidateExecutables('codex')) {
|
|
168
|
+
const packageRoot = npmShimCodexPackageRoot(candidate) || ancestorCodexPackageRoot(candidate);
|
|
169
|
+
if (packageRoot) return packageRoot;
|
|
170
|
+
}
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function codexPlatformPackageJson(packageRoot, platformPackage) {
|
|
176
|
+
try {
|
|
177
|
+
return createRequire(join(packageRoot, 'package.json')).resolve(`${platformPackage}/package.json`);
|
|
178
|
+
} catch {
|
|
179
|
+
const nested = join(packageRoot, 'node_modules', platformPackage, 'package.json');
|
|
180
|
+
return existsSync(nested) ? nested : '';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function resolveCodexExecutable() {
|
|
185
|
+
const packageRoot = codexPackageRoot();
|
|
186
|
+
const targetTriple = codexTargetTriple();
|
|
187
|
+
const platformPackage = CODEX_PLATFORM_PACKAGE_BY_TARGET[targetTriple];
|
|
188
|
+
if (packageRoot && targetTriple && platformPackage) {
|
|
189
|
+
const packageJson = codexPlatformPackageJson(packageRoot, platformPackage);
|
|
190
|
+
if (existsSync(packageJson)) {
|
|
191
|
+
const executable = join(
|
|
192
|
+
dirname(packageJson),
|
|
193
|
+
'vendor',
|
|
194
|
+
targetTriple,
|
|
195
|
+
'bin',
|
|
196
|
+
process.platform === 'win32' ? 'codex.exe' : 'codex',
|
|
197
|
+
);
|
|
198
|
+
if (existsSync(executable)) return executable;
|
|
199
|
+
}
|
|
200
|
+
const bundledExecutable = join(
|
|
201
|
+
packageRoot,
|
|
202
|
+
'vendor',
|
|
203
|
+
targetTriple,
|
|
204
|
+
'bin',
|
|
205
|
+
process.platform === 'win32' ? 'codex.exe' : 'codex',
|
|
206
|
+
);
|
|
207
|
+
if (existsSync(bundledExecutable)) return bundledExecutable;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const candidate of candidateExecutables('codex')) {
|
|
211
|
+
if (existsSync(candidate) && !candidate.endsWith('.cmd') && !candidate.endsWith('.bat') && !candidate.endsWith('.ps1')) {
|
|
212
|
+
return candidate;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return 'codex';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function codexProcessEnv() {
|
|
219
|
+
const packageRoot = codexPackageRoot();
|
|
220
|
+
if (!packageRoot) return process.env;
|
|
221
|
+
return {
|
|
222
|
+
...process.env,
|
|
223
|
+
CODEX_MANAGED_BY_NPM: '1',
|
|
224
|
+
CODEX_MANAGED_PACKAGE_ROOT: realpathSync(packageRoot),
|
|
225
|
+
};
|
|
105
226
|
}
|
|
106
227
|
|
|
107
228
|
export async function runCodex(args) {
|
|
108
|
-
const child = spawn(
|
|
229
|
+
const child = spawn(resolveCodexExecutable(), args, {
|
|
109
230
|
stdio: 'inherit',
|
|
110
|
-
shell:
|
|
231
|
+
shell: false,
|
|
232
|
+
env: codexProcessEnv(),
|
|
111
233
|
});
|
|
112
234
|
process.exitCode = await new Promise((resolveChild) => {
|
|
235
|
+
child.on('error', (error) => {
|
|
236
|
+
process.stderr.write(`Failed to launch codex: ${error.message}\n`);
|
|
237
|
+
resolveChild(1);
|
|
238
|
+
});
|
|
113
239
|
child.on('exit', (exitCode) => resolveChild(exitCode ?? 0));
|
|
114
240
|
});
|
|
115
241
|
}
|
|
@@ -118,25 +244,73 @@ function rowOffset(header) {
|
|
|
118
244
|
return header ? 4 : 2;
|
|
119
245
|
}
|
|
120
246
|
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
const
|
|
247
|
+
export function pickerWindow(rows, selected, windowSize, currentOffset = 0) {
|
|
248
|
+
const allRows = Array.isArray(rows) ? rows : [];
|
|
249
|
+
const size = Math.max(1, Math.trunc(Number(windowSize) || allRows.length || 1));
|
|
250
|
+
const clampedSelected = Math.min(Math.max(0, selected), Math.max(0, allRows.length - 1));
|
|
251
|
+
const maxOffset = Math.max(0, allRows.length - size);
|
|
252
|
+
let offset = Math.min(Math.max(0, Math.trunc(Number(currentOffset) || 0)), maxOffset);
|
|
253
|
+
if (clampedSelected < offset) {
|
|
254
|
+
offset = clampedSelected;
|
|
255
|
+
} else if (clampedSelected >= offset + size) {
|
|
256
|
+
offset = clampedSelected - size + 1;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
offset,
|
|
260
|
+
rows: allRows.slice(offset, offset + size),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function pickerControls(shortcuts = []) {
|
|
265
|
+
const labels = shortcuts.map((shortcut) => shortcut.label).filter(Boolean);
|
|
266
|
+
return [...labels, '↑/↓ select', '← back', 'Enter confirm', 'Esc quit'].join(' ');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function renderRow(state, absoluteIndex) {
|
|
270
|
+
if (absoluteIndex < state.offset || absoluteIndex >= state.offset + state.visibleRows.length) return;
|
|
271
|
+
const visibleIndex = absoluteIndex - state.offset;
|
|
272
|
+
cursorTo(process.stdout, 0, rowOffset(state.header) + visibleIndex);
|
|
273
|
+
const row = `${absoluteIndex === state.selected ? '>' : ' '} ${state.rows[absoluteIndex]}`;
|
|
274
|
+
const styled = absoluteIndex === state.selected ? `${SELECTED_ROW}${row}${RESET_STYLE}` : row;
|
|
125
275
|
print(`\x1b[2K${styled}`);
|
|
126
276
|
}
|
|
127
277
|
|
|
128
|
-
function
|
|
129
|
-
const { title, rows, selected, header } = state;
|
|
278
|
+
function pickerLines(state) {
|
|
279
|
+
const { title, rows, selected, header, emptyMessage, shortcuts } = state;
|
|
280
|
+
const window = pickerWindow(rows, selected, state.windowSize, state.offset);
|
|
281
|
+
state.offset = window.offset;
|
|
282
|
+
state.visibleRows = window.rows;
|
|
283
|
+
const lines = [title, ''];
|
|
284
|
+
if (header) lines.push(` ${header}`, ` ${'-'.repeat(header.length)}`);
|
|
285
|
+
for (const [visibleIndex, rowText] of state.visibleRows.entries()) {
|
|
286
|
+
const absoluteIndex = state.offset + visibleIndex;
|
|
287
|
+
const row = `${absoluteIndex === selected ? '>' : ' '} ${rowText}`;
|
|
288
|
+
lines.push(absoluteIndex === selected ? `${SELECTED_ROW}${row}${RESET_STYLE}` : row);
|
|
289
|
+
}
|
|
290
|
+
if (!rows.length && emptyMessage) lines.push(emptyMessage);
|
|
291
|
+
if (rows.length > state.visibleRows.length) {
|
|
292
|
+
lines.push('', ` Showing ${state.offset + 1}-${state.offset + state.visibleRows.length} of ${rows.length}`);
|
|
293
|
+
}
|
|
294
|
+
lines.push('', ` ${pickerControls(shortcuts)}`);
|
|
295
|
+
return lines;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function renderPicker(state, { clear = true } = {}) {
|
|
299
|
+
const lines = pickerLines(state);
|
|
130
300
|
cursorTo(process.stdout, 0, 0);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
301
|
+
if (clear) {
|
|
302
|
+
clearScreenDown(process.stdout);
|
|
303
|
+
print(`${lines.join('\n')}\n`);
|
|
304
|
+
state.renderedLineCount = lines.length;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
for (const line of lines) {
|
|
308
|
+
print(`\x1b[2K${line}\n`);
|
|
137
309
|
}
|
|
138
|
-
|
|
139
|
-
|
|
310
|
+
for (let index = lines.length; index < (state.renderedLineCount || 0); index += 1) {
|
|
311
|
+
print('\x1b[2K\n');
|
|
312
|
+
}
|
|
313
|
+
state.renderedLineCount = lines.length;
|
|
140
314
|
}
|
|
141
315
|
|
|
142
316
|
function openPickerScreen() {
|
|
@@ -147,11 +321,23 @@ function closePickerScreen() {
|
|
|
147
321
|
process.stdout.write('\x1b[?25h\x1b[?1049l');
|
|
148
322
|
}
|
|
149
323
|
|
|
150
|
-
export async function pick(title, rows, header = '') {
|
|
151
|
-
|
|
324
|
+
export async function pick(title, rows, header = '', options = {}) {
|
|
325
|
+
const shortcuts = Array.isArray(options.shortcuts) ? options.shortcuts : [];
|
|
326
|
+
if (!rows.length && !shortcuts.length) return { action: 'back' };
|
|
152
327
|
let selected = 0;
|
|
153
328
|
const stdin = process.stdin;
|
|
154
|
-
const state = {
|
|
329
|
+
const state = {
|
|
330
|
+
title,
|
|
331
|
+
rows,
|
|
332
|
+
selected,
|
|
333
|
+
header,
|
|
334
|
+
shortcuts,
|
|
335
|
+
windowSize: options.windowSize,
|
|
336
|
+
emptyMessage: options.emptyMessage,
|
|
337
|
+
offset: 0,
|
|
338
|
+
visibleRows: [],
|
|
339
|
+
renderedLineCount: 0,
|
|
340
|
+
};
|
|
155
341
|
renderPicker(state);
|
|
156
342
|
|
|
157
343
|
return await new Promise((resolvePick) => {
|
|
@@ -163,13 +349,25 @@ export async function pick(title, rows, header = '') {
|
|
|
163
349
|
const key = chunk.toString('utf8');
|
|
164
350
|
if (key === '\u0003' || key === '\u001b') return done({ action: 'cancel' });
|
|
165
351
|
if (key === '\u001b[D') return done({ action: 'back' });
|
|
166
|
-
|
|
352
|
+
for (const shortcut of shortcuts) {
|
|
353
|
+
if (key.toLowerCase() === String(shortcut.key || '').toLowerCase()) {
|
|
354
|
+
return done({ action: shortcut.action || shortcut.key });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if ((key === '\r' || key === '\n') && rows.length) return done({ action: 'select', index: selected });
|
|
358
|
+
if (!rows.length) return;
|
|
167
359
|
const previous = selected;
|
|
360
|
+
const previousOffset = state.offset;
|
|
168
361
|
if (key === '\u001b[A') selected = Math.max(0, selected - 1);
|
|
169
362
|
else if (key === '\u001b[B') selected = Math.min(rows.length - 1, selected + 1);
|
|
170
363
|
else return;
|
|
171
364
|
if (selected === previous) return;
|
|
172
365
|
state.selected = selected;
|
|
366
|
+
const nextWindow = pickerWindow(rows, selected, state.windowSize, state.offset);
|
|
367
|
+
if (nextWindow.offset !== previousOffset) {
|
|
368
|
+
renderPicker(state, { clear: false });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
173
371
|
renderRow(state, previous);
|
|
174
372
|
renderRow(state, selected);
|
|
175
373
|
};
|
|
@@ -222,6 +420,7 @@ export async function chooseLaunchContext(context) {
|
|
|
222
420
|
}
|
|
223
421
|
|
|
224
422
|
export async function newConversation(options) {
|
|
423
|
+
if (options.print) throw new Error('--print is only supported with sessions');
|
|
225
424
|
const context = createLaunchContext(options);
|
|
226
425
|
if (!context.model) {
|
|
227
426
|
print(missingModelMessage(context));
|
|
@@ -233,9 +432,5 @@ export async function newConversation(options) {
|
|
|
233
432
|
const selected = await chooseLaunchContext(context);
|
|
234
433
|
if (!selected) return;
|
|
235
434
|
}
|
|
236
|
-
if (options.print || (!hasLaunchOverrides && !canPick)) {
|
|
237
|
-
print(`${codexNewCommand(context)}\n`);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
435
|
await runCodex(codexNewArgs(context));
|
|
241
436
|
}
|
package/src/codex-sessions.js
CHANGED
|
@@ -27,7 +27,7 @@ const ID_WIDTH = 36;
|
|
|
27
27
|
const TITLE_WIDTH = 16;
|
|
28
28
|
const TABLE_INDENT = ' ';
|
|
29
29
|
const COLUMN_GAP = ' ';
|
|
30
|
-
const
|
|
30
|
+
export const DEFAULT_SESSION_LIMIT = 15;
|
|
31
31
|
|
|
32
32
|
function print(message = '') {
|
|
33
33
|
process.stdout.write(message);
|
|
@@ -186,7 +186,7 @@ function resolveSessionSelection(selection, sessionsList) {
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
function printSessions(sessionsList, options, context) {
|
|
189
|
-
const listed = sessionsList.slice(0, options.limit ||
|
|
189
|
+
const listed = sessionsList.slice(0, options.limit || DEFAULT_SESSION_LIMIT);
|
|
190
190
|
print(`Codex sessions ${options.all ? `under ${context.codexHome}` : `for project ${context.projectRoot}`}\n`);
|
|
191
191
|
print(`Target: ${context.provider} / ${context.model} / ${context.reasoningEffort}\n\n`);
|
|
192
192
|
if (!listed.length) {
|
|
@@ -215,23 +215,35 @@ function sessionRows(sessionsList) {
|
|
|
215
215
|
return sessionsList.map((session) => sessionRow(session));
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
export function sessionPickerRows(allSessions) {
|
|
219
|
+
return sessionRows(allSessions);
|
|
220
|
+
}
|
|
221
|
+
|
|
218
222
|
async function chooseSessionFlow(allSessions, context, limit) {
|
|
219
223
|
if (!context.models.length) {
|
|
220
224
|
print(missingModelMessage(context));
|
|
221
225
|
return null;
|
|
222
226
|
}
|
|
223
227
|
|
|
224
|
-
const listed = allSessions.slice(0, limit);
|
|
225
228
|
const header = sessionHeader();
|
|
229
|
+
const windowSize = limit || DEFAULT_SESSION_LIMIT;
|
|
226
230
|
return await withPickerScreen(async () => {
|
|
227
231
|
let step = 'session';
|
|
228
232
|
while (true) {
|
|
229
233
|
if (step === 'session') {
|
|
230
|
-
const
|
|
231
|
-
|
|
234
|
+
const result = await pick(
|
|
235
|
+
`Choose Codex session`,
|
|
236
|
+
sessionPickerRows(allSessions),
|
|
237
|
+
header,
|
|
238
|
+
{
|
|
239
|
+
windowSize,
|
|
240
|
+
emptyMessage: 'No matching sessions found.',
|
|
241
|
+
shortcuts: [{ key: 'n', action: 'new', label: 'N new' }],
|
|
242
|
+
},
|
|
243
|
+
);
|
|
232
244
|
if (result.action === 'cancel') return null;
|
|
233
245
|
if (result.action === 'back') return null;
|
|
234
|
-
context.selectedSession = result.
|
|
246
|
+
context.selectedSession = result.action === 'new' ? { newConversation: true } : allSessions[result.index];
|
|
235
247
|
step = 'model';
|
|
236
248
|
} else if (step === 'model') {
|
|
237
249
|
const action = await pickModel(context);
|
|
@@ -273,7 +285,7 @@ export async function sessions(options) {
|
|
|
273
285
|
return;
|
|
274
286
|
}
|
|
275
287
|
|
|
276
|
-
const selected = await chooseSessionFlow(allSessions, context, options.limit ||
|
|
288
|
+
const selected = await chooseSessionFlow(allSessions, context, options.limit || DEFAULT_SESSION_LIMIT);
|
|
277
289
|
if (selected?.newConversation) await runCodex(codexNewArgs(context));
|
|
278
290
|
else if (selected) await runCodex(codexResumeArgs(selected.id, context));
|
|
279
291
|
}
|