@galaxy-yearn/codex-deepseek-gateway 0.1.4 → 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 +85 -199
- package/bin/codex-deepseek-gateway.js +52 -12
- package/config/codex-model-catalog.json +130 -0
- package/config/gateway.example.json +1 -17
- package/package.json +3 -2
- package/src/codex-launch.js +242 -39
- package/src/codex-sessions.js +72 -16
- package/src/config.js +1 -1
- package/src/firecrawl.js +2 -2
- package/src/protocol.js +640 -130
- package/src/server.js +128 -15
- package/src/tavily.js +3 -3
- package/src/web-search-emulator.js +104 -23
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,25 +70,22 @@ 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',
|
|
66
77
|
`model=${JSON.stringify(context.model)}`,
|
|
67
78
|
'-c',
|
|
68
79
|
`model_reasoning_effort=${JSON.stringify(context.reasoningEffort)}`,
|
|
69
|
-
];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function configOverrideCommandParts(context) {
|
|
73
|
-
return [
|
|
74
|
-
'-c',
|
|
75
|
-
`model_provider=${context.provider}`,
|
|
76
80
|
'-c',
|
|
77
|
-
|
|
81
|
+
'model_supports_reasoning_summaries=true',
|
|
78
82
|
'-c',
|
|
79
|
-
|
|
83
|
+
'model_reasoning_summary="auto"',
|
|
80
84
|
];
|
|
85
|
+
if (context.provider === DEFAULT_PROVIDER && context.modelCatalogPath) {
|
|
86
|
+
args.push('-c', `model_catalog_json=${JSON.stringify(context.modelCatalogPath)}`);
|
|
87
|
+
}
|
|
88
|
+
return args;
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
export function codexNewArgs(context) {
|
|
@@ -88,20 +96,146 @@ export function codexResumeArgs(sessionId, context) {
|
|
|
88
96
|
return ['resume', sessionId, ...configOverrideArgs(context)];
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
function quoteShellArg(arg) {
|
|
100
|
+
const value = String(arg);
|
|
101
|
+
if (/^[A-Za-z0-9_./:=@%+,-]+$/.test(value)) return value;
|
|
102
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
export function codexResumeCommand(sessionId, context) {
|
|
96
|
-
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
|
+
};
|
|
97
226
|
}
|
|
98
227
|
|
|
99
228
|
export async function runCodex(args) {
|
|
100
|
-
const child = spawn(
|
|
229
|
+
const child = spawn(resolveCodexExecutable(), args, {
|
|
101
230
|
stdio: 'inherit',
|
|
102
|
-
shell:
|
|
231
|
+
shell: false,
|
|
232
|
+
env: codexProcessEnv(),
|
|
103
233
|
});
|
|
104
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
|
+
});
|
|
105
239
|
child.on('exit', (exitCode) => resolveChild(exitCode ?? 0));
|
|
106
240
|
});
|
|
107
241
|
}
|
|
@@ -110,25 +244,73 @@ function rowOffset(header) {
|
|
|
110
244
|
return header ? 4 : 2;
|
|
111
245
|
}
|
|
112
246
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
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;
|
|
117
275
|
print(`\x1b[2K${styled}`);
|
|
118
276
|
}
|
|
119
277
|
|
|
120
|
-
function
|
|
121
|
-
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);
|
|
122
300
|
cursorTo(process.stdout, 0, 0);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
output += `${index === selected ? `${SELECTED_ROW}${row}${RESET_STYLE}` : row}\n`;
|
|
301
|
+
if (clear) {
|
|
302
|
+
clearScreenDown(process.stdout);
|
|
303
|
+
print(`${lines.join('\n')}\n`);
|
|
304
|
+
state.renderedLineCount = lines.length;
|
|
305
|
+
return;
|
|
129
306
|
}
|
|
130
|
-
|
|
131
|
-
|
|
307
|
+
for (const line of lines) {
|
|
308
|
+
print(`\x1b[2K${line}\n`);
|
|
309
|
+
}
|
|
310
|
+
for (let index = lines.length; index < (state.renderedLineCount || 0); index += 1) {
|
|
311
|
+
print('\x1b[2K\n');
|
|
312
|
+
}
|
|
313
|
+
state.renderedLineCount = lines.length;
|
|
132
314
|
}
|
|
133
315
|
|
|
134
316
|
function openPickerScreen() {
|
|
@@ -139,11 +321,23 @@ function closePickerScreen() {
|
|
|
139
321
|
process.stdout.write('\x1b[?25h\x1b[?1049l');
|
|
140
322
|
}
|
|
141
323
|
|
|
142
|
-
export async function pick(title, rows, header = '') {
|
|
143
|
-
|
|
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' };
|
|
144
327
|
let selected = 0;
|
|
145
328
|
const stdin = process.stdin;
|
|
146
|
-
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
|
+
};
|
|
147
341
|
renderPicker(state);
|
|
148
342
|
|
|
149
343
|
return await new Promise((resolvePick) => {
|
|
@@ -155,13 +349,25 @@ export async function pick(title, rows, header = '') {
|
|
|
155
349
|
const key = chunk.toString('utf8');
|
|
156
350
|
if (key === '\u0003' || key === '\u001b') return done({ action: 'cancel' });
|
|
157
351
|
if (key === '\u001b[D') return done({ action: 'back' });
|
|
158
|
-
|
|
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;
|
|
159
359
|
const previous = selected;
|
|
360
|
+
const previousOffset = state.offset;
|
|
160
361
|
if (key === '\u001b[A') selected = Math.max(0, selected - 1);
|
|
161
362
|
else if (key === '\u001b[B') selected = Math.min(rows.length - 1, selected + 1);
|
|
162
363
|
else return;
|
|
163
364
|
if (selected === previous) return;
|
|
164
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
|
+
}
|
|
165
371
|
renderRow(state, previous);
|
|
166
372
|
renderRow(state, selected);
|
|
167
373
|
};
|
|
@@ -214,6 +420,7 @@ export async function chooseLaunchContext(context) {
|
|
|
214
420
|
}
|
|
215
421
|
|
|
216
422
|
export async function newConversation(options) {
|
|
423
|
+
if (options.print) throw new Error('--print is only supported with sessions');
|
|
217
424
|
const context = createLaunchContext(options);
|
|
218
425
|
if (!context.model) {
|
|
219
426
|
print(missingModelMessage(context));
|
|
@@ -225,9 +432,5 @@ export async function newConversation(options) {
|
|
|
225
432
|
const selected = await chooseLaunchContext(context);
|
|
226
433
|
if (!selected) return;
|
|
227
434
|
}
|
|
228
|
-
if (options.print || (!hasLaunchOverrides && !canPick)) {
|
|
229
|
-
print(`${codexNewCommand(context)}\n`);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
435
|
await runCodex(codexNewArgs(context));
|
|
233
436
|
}
|
package/src/codex-sessions.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
readFileSync,
|
|
6
6
|
readdirSync,
|
|
7
7
|
readSync,
|
|
8
|
+
statSync,
|
|
8
9
|
} from 'node:fs';
|
|
9
10
|
import { join, resolve } from 'node:path';
|
|
10
11
|
import {
|
|
@@ -26,7 +27,7 @@ const ID_WIDTH = 36;
|
|
|
26
27
|
const TITLE_WIDTH = 16;
|
|
27
28
|
const TABLE_INDENT = ' ';
|
|
28
29
|
const COLUMN_GAP = ' ';
|
|
29
|
-
const
|
|
30
|
+
export const DEFAULT_SESSION_LIMIT = 15;
|
|
30
31
|
|
|
31
32
|
function print(message = '') {
|
|
32
33
|
process.stdout.write(message);
|
|
@@ -60,6 +61,48 @@ function parseJsonLine(line) {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
function isSubagentSession(meta) {
|
|
65
|
+
return meta?.thread_source === 'subagent' || Boolean(meta?.parent_thread_id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function timestampFromUserMessage(row) {
|
|
69
|
+
const payload = row?.payload;
|
|
70
|
+
if (row?.type === 'event_msg' && payload?.type === 'user_message') return row.timestamp || '';
|
|
71
|
+
if (row?.type === 'response_item' && payload?.type === 'message' && payload.role === 'user') return row.timestamp || '';
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readLastUserMessageTimestamp(file, chunkSize = 256 * 1024) {
|
|
76
|
+
const handle = openSync(file, 'r');
|
|
77
|
+
try {
|
|
78
|
+
let position = statSync(file).size;
|
|
79
|
+
let suffix = Buffer.alloc(0);
|
|
80
|
+
while (position > 0) {
|
|
81
|
+
const length = Math.min(chunkSize, position);
|
|
82
|
+
position -= length;
|
|
83
|
+
const buffer = Buffer.alloc(length);
|
|
84
|
+
const read = readSync(handle, buffer, 0, length, position);
|
|
85
|
+
let data = buffer.subarray(0, read);
|
|
86
|
+
if (suffix.length) data = Buffer.concat([data, suffix]);
|
|
87
|
+
|
|
88
|
+
let lineEnd = data.length;
|
|
89
|
+
for (let index = data.length - 1; index >= 0; index -= 1) {
|
|
90
|
+
if (data[index] !== 10) continue;
|
|
91
|
+
const row = parseJsonLine(data.subarray(index + 1, lineEnd).toString('utf8').trim());
|
|
92
|
+
const timestamp = timestampFromUserMessage(row);
|
|
93
|
+
if (timestamp) return timestamp;
|
|
94
|
+
lineEnd = index;
|
|
95
|
+
}
|
|
96
|
+
suffix = data.subarray(0, lineEnd);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const row = parseJsonLine(suffix.toString('utf8').trim());
|
|
100
|
+
return timestampFromUserMessage(row);
|
|
101
|
+
} finally {
|
|
102
|
+
closeSync(handle);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
63
106
|
function readSessionIndex(codexHome) {
|
|
64
107
|
const file = join(codexHome, 'session_index.jsonl');
|
|
65
108
|
const byId = new Map();
|
|
@@ -118,16 +161,17 @@ function isInsideProject(cwd, projectRoot) {
|
|
|
118
161
|
return sessionCwd === root || sessionCwd.startsWith(`${root}${process.platform === 'win32' ? '\\' : '/'}`);
|
|
119
162
|
}
|
|
120
163
|
|
|
121
|
-
function readSession(file, indexById) {
|
|
164
|
+
export function readSession(file, indexById) {
|
|
122
165
|
const lines = readStart(file).split(/\r?\n/).filter(Boolean);
|
|
123
166
|
const meta = parseJsonLine(lines[0])?.payload;
|
|
124
167
|
if (!meta?.id) return null;
|
|
168
|
+
if (isSubagentSession(meta)) return null;
|
|
125
169
|
const index = indexById.get(meta.id) || {};
|
|
126
170
|
return {
|
|
127
171
|
id: meta.id,
|
|
128
172
|
provider: meta.model_provider || '',
|
|
129
173
|
cwd: meta.cwd || '',
|
|
130
|
-
updatedAt: index.updated_at || meta.timestamp || '',
|
|
174
|
+
updatedAt: readLastUserMessageTimestamp(file) || index.updated_at || meta.timestamp || '',
|
|
131
175
|
title: index.thread_name || firstUserPreview(lines) || '(untitled)',
|
|
132
176
|
};
|
|
133
177
|
}
|
|
@@ -142,7 +186,7 @@ function resolveSessionSelection(selection, sessionsList) {
|
|
|
142
186
|
}
|
|
143
187
|
|
|
144
188
|
function printSessions(sessionsList, options, context) {
|
|
145
|
-
const listed = sessionsList.slice(0, options.limit ||
|
|
189
|
+
const listed = sessionsList.slice(0, options.limit || DEFAULT_SESSION_LIMIT);
|
|
146
190
|
print(`Codex sessions ${options.all ? `under ${context.codexHome}` : `for project ${context.projectRoot}`}\n`);
|
|
147
191
|
print(`Target: ${context.provider} / ${context.model} / ${context.reasoningEffort}\n\n`);
|
|
148
192
|
if (!listed.length) {
|
|
@@ -171,18 +215,37 @@ function sessionRows(sessionsList) {
|
|
|
171
215
|
return sessionsList.map((session) => sessionRow(session));
|
|
172
216
|
}
|
|
173
217
|
|
|
218
|
+
export function sessionPickerRows(allSessions) {
|
|
219
|
+
return sessionRows(allSessions);
|
|
220
|
+
}
|
|
221
|
+
|
|
174
222
|
async function chooseSessionFlow(allSessions, context, limit) {
|
|
175
223
|
if (!context.models.length) {
|
|
176
224
|
print(missingModelMessage(context));
|
|
177
225
|
return null;
|
|
178
226
|
}
|
|
179
227
|
|
|
180
|
-
const listed = allSessions.slice(0, limit);
|
|
181
228
|
const header = sessionHeader();
|
|
229
|
+
const windowSize = limit || DEFAULT_SESSION_LIMIT;
|
|
182
230
|
return await withPickerScreen(async () => {
|
|
183
|
-
let step = '
|
|
231
|
+
let step = 'session';
|
|
184
232
|
while (true) {
|
|
185
|
-
if (step === '
|
|
233
|
+
if (step === 'session') {
|
|
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
|
+
);
|
|
244
|
+
if (result.action === 'cancel') return null;
|
|
245
|
+
if (result.action === 'back') return null;
|
|
246
|
+
context.selectedSession = result.action === 'new' ? { newConversation: true } : allSessions[result.index];
|
|
247
|
+
step = 'model';
|
|
248
|
+
} else if (step === 'model') {
|
|
186
249
|
const action = await pickModel(context);
|
|
187
250
|
if (action !== 'select') return null;
|
|
188
251
|
step = 'reasoning';
|
|
@@ -190,14 +253,7 @@ async function chooseSessionFlow(allSessions, context, limit) {
|
|
|
190
253
|
const action = await pickReasoning(context);
|
|
191
254
|
if (action === 'cancel') return null;
|
|
192
255
|
if (action === 'back') step = 'model';
|
|
193
|
-
else
|
|
194
|
-
} else {
|
|
195
|
-
const rows = [NEW_SESSION_ROW, ...sessionRows(listed)];
|
|
196
|
-
const result = await pick(`Choose Codex session for ${context.provider} / ${context.model} / ${context.reasoningEffort}`, rows, header);
|
|
197
|
-
if (result.action === 'cancel') return null;
|
|
198
|
-
if (result.action === 'back') step = 'reasoning';
|
|
199
|
-
else if (result.index === 0) return { newConversation: true };
|
|
200
|
-
else return listed[result.index - 1];
|
|
256
|
+
else return context.selectedSession;
|
|
201
257
|
}
|
|
202
258
|
}
|
|
203
259
|
});
|
|
@@ -229,7 +285,7 @@ export async function sessions(options) {
|
|
|
229
285
|
return;
|
|
230
286
|
}
|
|
231
287
|
|
|
232
|
-
const selected = await chooseSessionFlow(allSessions, context, options.limit ||
|
|
288
|
+
const selected = await chooseSessionFlow(allSessions, context, options.limit || DEFAULT_SESSION_LIMIT);
|
|
233
289
|
if (selected?.newConversation) await runCodex(codexNewArgs(context));
|
|
234
290
|
else if (selected) await runCodex(codexResumeArgs(selected.id, context));
|
|
235
291
|
}
|
package/src/config.js
CHANGED
|
@@ -25,7 +25,7 @@ export function loadConfig(env = process.env) {
|
|
|
25
25
|
tavilyBaseUrl: mergedEnv.TAVILY_BASE_URL || 'https://api.tavily.com',
|
|
26
26
|
tavilySearchDepth: mergedEnv.TAVILY_SEARCH_DEPTH || 'basic',
|
|
27
27
|
tavilyMaxResults: Number(mergedEnv.TAVILY_MAX_RESULTS || 5),
|
|
28
|
-
tavilyMaxSearchRounds: Number(mergedEnv.TAVILY_MAX_SEARCH_ROUNDS ||
|
|
28
|
+
tavilyMaxSearchRounds: Number(mergedEnv.TAVILY_MAX_SEARCH_ROUNDS || 10),
|
|
29
29
|
tavilyTimeoutMs: Number(mergedEnv.TAVILY_TIMEOUT_MS || 15000),
|
|
30
30
|
tavilySnippetChars: Number(mergedEnv.TAVILY_SNIPPET_CHARS || 650),
|
|
31
31
|
tavilyResultMaxChars: Number(mergedEnv.TAVILY_RESULT_MAX_CHARS || 6000),
|
package/src/firecrawl.js
CHANGED
|
@@ -304,12 +304,12 @@ export function formatFirecrawlScrapeResult({ url = '', title = '', summary = ''
|
|
|
304
304
|
if (links?.length) {
|
|
305
305
|
lines.push('Page links:');
|
|
306
306
|
for (const [index, link] of links.entries()) {
|
|
307
|
-
lines.push(`
|
|
307
|
+
lines.push(`Link ${index + 1}: ${link.title || link.url}`);
|
|
308
308
|
lines.push(`URL: ${link.url}`);
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
|
-
lines.push('Use this opened page only as source text.
|
|
312
|
+
lines.push('Use this opened page only as source text. If it supports the answer, include the page title and URL.');
|
|
313
313
|
const text = lines.filter(Boolean).join('\n');
|
|
314
314
|
const maxChars = Number(config.firecrawlResultMaxChars) || DEFAULT_TOTAL_CHARS;
|
|
315
315
|
if (text.length <= maxChars) return text;
|