deepcodex 0.1.0
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/.codex-plugin/plugin.json +24 -0
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/bin/opencodex.js +49 -0
- package/config/desktop.json +9 -0
- package/config/pilot.json +16 -0
- package/config/worker.json +78 -0
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +418 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +127 -0
- package/node_modules/smol-toml/dist/error.d.ts +38 -0
- package/node_modules/smol-toml/dist/error.js +63 -0
- package/node_modules/smol-toml/dist/extract.js +69 -0
- package/node_modules/smol-toml/dist/index.cjs +734 -0
- package/node_modules/smol-toml/dist/index.d.ts +43 -0
- package/node_modules/smol-toml/dist/index.js +33 -0
- package/node_modules/smol-toml/dist/parse.d.ts +36 -0
- package/node_modules/smol-toml/dist/parse.js +149 -0
- package/node_modules/smol-toml/dist/primitive.js +238 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
- package/node_modules/smol-toml/dist/stringify.js +181 -0
- package/node_modules/smol-toml/dist/struct.js +179 -0
- package/node_modules/smol-toml/dist/util.d.ts +38 -0
- package/node_modules/smol-toml/dist/util.js +89 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +47 -0
- package/prompts/worker.md +20 -0
- package/scripts/credentials.js +102 -0
- package/scripts/desktop.js +199 -0
- package/scripts/pilot-router.js +241 -0
- package/scripts/pilot.js +242 -0
- package/scripts/toml.js +6 -0
- package/scripts/worker.js +637 -0
- package/skills/delegate-flash/SKILL.md +63 -0
- package/vendor/codex-router/LICENSE +21 -0
- package/vendor/codex-router/deepseek-responses.js +55 -0
- package/vendor/codex-router/json-number-rewrite.js +58 -0
- package/vendor/codex-router/namespace-relay.js +4294 -0
- package/vendor/codex-router/sse-prefix.js +115 -0
- package/vendor/codex-router/subagent-completion.js +261 -0
- package/vendor/codex-router/tool-arguments.js +111 -0
- package/vendor/codex-router/tool-schema-root.js +1008 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
6
|
+
import { loadConfig } from './worker.js';
|
|
7
|
+
|
|
8
|
+
export function readSecret(input = process.stdin, output = process.stderr) {
|
|
9
|
+
if (!input.isTTY || !output.isTTY) throw new Error('Configure requires an interactive terminal; do not pass the key as an argument.');
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
let secret = '';
|
|
12
|
+
const wasRaw = Boolean(input.isRaw);
|
|
13
|
+
const finish = error => {
|
|
14
|
+
input.removeListener('keypress', onKey);
|
|
15
|
+
input.removeListener('end', onEnd);
|
|
16
|
+
input.removeListener('error', onError);
|
|
17
|
+
input.setRawMode(wasRaw);
|
|
18
|
+
input.pause();
|
|
19
|
+
output.write('\n');
|
|
20
|
+
if (error) reject(error);
|
|
21
|
+
else resolve(secret);
|
|
22
|
+
secret = '';
|
|
23
|
+
};
|
|
24
|
+
const onEnd = () => finish(new Error('Key entry cancelled.'));
|
|
25
|
+
const onError = () => finish(new Error('Unable to read the key from the terminal.'));
|
|
26
|
+
const onKey = (text, key = {}) => {
|
|
27
|
+
if ((key.ctrl && ['c', 'd'].includes(key.name)) || key.name === 'escape') return onEnd();
|
|
28
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
29
|
+
return finish(secret ? null : new Error('The key cannot be empty.'));
|
|
30
|
+
}
|
|
31
|
+
if (key.name === 'backspace') secret = secret.slice(0, -1);
|
|
32
|
+
else if (text && !key.ctrl && !key.meta) {
|
|
33
|
+
if (!/^[\x21-\x7e]+$/.test(text)) return finish(new Error('The key must contain only printable characters without spaces.'));
|
|
34
|
+
secret += text;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
emitKeypressEvents(input);
|
|
38
|
+
input.setRawMode(true);
|
|
39
|
+
input.on('keypress', onKey);
|
|
40
|
+
input.once('end', onEnd);
|
|
41
|
+
input.once('error', onError);
|
|
42
|
+
output.write('DeepSeek API key (hidden): ');
|
|
43
|
+
input.resume();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function saveCredentials(file, name, secret) {
|
|
48
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(name) || !/^[\x21-\x7e]+$/.test(secret)) {
|
|
49
|
+
throw new Error('Invalid credential name or key.');
|
|
50
|
+
}
|
|
51
|
+
const directory = path.dirname(file);
|
|
52
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
53
|
+
const directoryStat = fs.lstatSync(directory);
|
|
54
|
+
if (!directoryStat.isDirectory() || directoryStat.uid !== process.getuid()) {
|
|
55
|
+
throw new Error('Credentials directory must be owned by this user and cannot be a symlink.');
|
|
56
|
+
}
|
|
57
|
+
fs.chmodSync(directory, 0o700);
|
|
58
|
+
let existing = '';
|
|
59
|
+
let fd;
|
|
60
|
+
try {
|
|
61
|
+
fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
62
|
+
const stat = fs.fstatSync(fd);
|
|
63
|
+
if (!stat.isFile() || stat.uid !== process.getuid()) throw new Error('Credentials must be a regular file owned by this user.');
|
|
64
|
+
existing = fs.readFileSync(fd, 'utf8');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error.code !== 'ENOENT') throw new Error('Cannot safely open the credentials file.');
|
|
67
|
+
} finally {
|
|
68
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
69
|
+
}
|
|
70
|
+
const assignment = `${name}=${JSON.stringify(secret)}`;
|
|
71
|
+
const lines = existing.split(/\r\n|\r|\n/);
|
|
72
|
+
const matches = lines.flatMap((line, index) => new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=`).test(line) ? [index] : []);
|
|
73
|
+
if (matches.length > 1) throw new Error('Duplicate key entries in the credentials file; resolve them before configuring.');
|
|
74
|
+
if (matches.length) lines[matches[0]] = assignment;
|
|
75
|
+
else {
|
|
76
|
+
if (lines.at(-1) === '') lines.pop();
|
|
77
|
+
lines.push(assignment, '');
|
|
78
|
+
}
|
|
79
|
+
const temporary = path.join(directory, `.credentials-${randomUUID()}.tmp`);
|
|
80
|
+
try {
|
|
81
|
+
fs.writeFileSync(temporary, lines.join('\n'), { flag: 'wx', mode: 0o600 });
|
|
82
|
+
fs.renameSync(temporary, file);
|
|
83
|
+
} finally {
|
|
84
|
+
fs.rmSync(temporary, { force: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function main(args) {
|
|
89
|
+
if (args.length === 2 && ['--help', '-h'].includes(args[1])) {
|
|
90
|
+
console.log('Usage: opencodex configure\nEnter the DeepSeek key in a hidden terminal prompt. Saves a private plaintext file outside the repository.');
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
if (args.length !== 1 || args[0] !== 'configure') throw new Error('Usage: opencodex configure (no key arguments accepted)');
|
|
94
|
+
const config = loadConfig();
|
|
95
|
+
const name = config.codex.model_providers[config.codex.model_provider].env_key;
|
|
96
|
+
const configured = config.credentials.env_file;
|
|
97
|
+
const file = configured.startsWith('~/') ? path.join(os.homedir(), configured.slice(2)) : configured;
|
|
98
|
+
const secret = await readSecret();
|
|
99
|
+
saveCredentials(file, name, secret);
|
|
100
|
+
console.log('DeepSeek key saved with owner-only permissions. Run opencodex install to activate it or reload an existing router.');
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
8
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
9
|
+
import { ROOT, loadConfig, workerEnvironment, loadCredentials, doctor } from './worker.js';
|
|
10
|
+
import { parseToml } from './toml.js';
|
|
11
|
+
import { startPilot } from './pilot-router.js';
|
|
12
|
+
|
|
13
|
+
const STATE = path.join(os.homedir(), '.config/opencodex/desktop');
|
|
14
|
+
const readJson = filename => JSON.parse(fs.readFileSync(filename, 'utf8'));
|
|
15
|
+
const expandHome = filename => filename.startsWith('~/') ? path.join(os.homedir(), filename.slice(2)) : filename;
|
|
16
|
+
|
|
17
|
+
export function privateWrite(filename, content) {
|
|
18
|
+
const temporary = filename + '.tmp';
|
|
19
|
+
const fd = fs.openSync(temporary, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_NOFOLLOW, 0o600);
|
|
20
|
+
try {
|
|
21
|
+
fs.fchmodSync(fd, 0o600);
|
|
22
|
+
fs.writeFileSync(fd, content);
|
|
23
|
+
} finally { fs.closeSync(fd); }
|
|
24
|
+
fs.renameSync(temporary, filename);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function mergeConfig(text, sections) {
|
|
28
|
+
const expected = parseToml(text);
|
|
29
|
+
const lines = text.match(/[^\n]*\n|[^\n]+$/g) || [];
|
|
30
|
+
if (lines.length && !lines.at(-1).endsWith('\n')) lines[lines.length - 1] += '\n';
|
|
31
|
+
for (const [section, values] of Object.entries(sections)) {
|
|
32
|
+
let target = expected;
|
|
33
|
+
for (const key of section ? section.split('.') : []) {
|
|
34
|
+
if (!Object.hasOwn(target, key)) target[key] = {};
|
|
35
|
+
target = target[key];
|
|
36
|
+
}
|
|
37
|
+
Object.assign(target, values);
|
|
38
|
+
let start = 0;
|
|
39
|
+
if (section) {
|
|
40
|
+
const match = lines.findIndex(line => line.trim() === `[${section}]`);
|
|
41
|
+
if (match < 0) {
|
|
42
|
+
lines.push(`\n[${section}]\n`);
|
|
43
|
+
start = lines.length;
|
|
44
|
+
} else start = match + 1;
|
|
45
|
+
}
|
|
46
|
+
let end = lines.findIndex((line, i) => i >= start && line.trimStart().startsWith('['));
|
|
47
|
+
if (end < 0) end = lines.length;
|
|
48
|
+
const block = lines.slice(start, end);
|
|
49
|
+
for (const [key, value] of Object.entries(values)) {
|
|
50
|
+
const assignment = `${key} = ${JSON.stringify(value)}\n`;
|
|
51
|
+
const indices = block.flatMap((line, i) => line.trimStart().startsWith(key) && line.trimStart().slice(key.length).trimStart().startsWith('=') ? [i] : []);
|
|
52
|
+
if (indices.length > 1) throw new Error(`Duplicate managed field: ${section}.${key}`);
|
|
53
|
+
if (indices.length) block[indices[0]] = assignment;
|
|
54
|
+
else block.push(assignment);
|
|
55
|
+
}
|
|
56
|
+
lines.splice(start, end - start, ...block);
|
|
57
|
+
}
|
|
58
|
+
const result = lines.join('');
|
|
59
|
+
if (!isDeepStrictEqual(parseToml(result), expected)) throw new Error('Configuration merge changed unrelated settings');
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function xml(value) {
|
|
64
|
+
return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function plistDocument(value) {
|
|
68
|
+
const encode = item => {
|
|
69
|
+
if (typeof item === 'boolean') return item ? '<true/>' : '<false/>';
|
|
70
|
+
if (typeof item === 'number' && Number.isInteger(item)) return `<integer>${item}</integer>`;
|
|
71
|
+
if (typeof item === 'string') return `<string>${xml(item)}</string>`;
|
|
72
|
+
if (Array.isArray(item)) return `<array>${item.map(encode).join('')}</array>`;
|
|
73
|
+
if (item && typeof item === 'object') return `<dict>${Object.entries(item).map(([key, entry]) => `<key>${xml(key)}</key>${encode(entry)}`).join('')}</dict>`;
|
|
74
|
+
throw new Error('Unsupported LaunchAgent value');
|
|
75
|
+
};
|
|
76
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">${encode(value)}</plist>\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function copyRuntime(root, runtime) {
|
|
80
|
+
fs.mkdirSync(runtime, { recursive: true, mode: 0o700 });
|
|
81
|
+
for (const directory of ['scripts', 'config', 'prompts', 'vendor']) {
|
|
82
|
+
fs.cpSync(path.join(root, directory), path.join(runtime, directory), { recursive: true,
|
|
83
|
+
filter: source => !['__pycache__', '.env', '.DS_Store'].includes(path.basename(source)) && !/\.py[cod]?$/.test(source) });
|
|
84
|
+
}
|
|
85
|
+
fs.copyFileSync(path.join(root, 'package.json'), path.join(runtime, 'package.json'));
|
|
86
|
+
const dependency = path.dirname(path.dirname(fileURLToPath(import.meta.resolve('smol-toml'))));
|
|
87
|
+
fs.cpSync(dependency, path.join(runtime, 'node_modules/smol-toml'), { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function health(state) {
|
|
91
|
+
const response = await fetch(`http://127.0.0.1:${state.config.port}/health`, {
|
|
92
|
+
headers: { 'x-opencodex-pilot': state.capability },
|
|
93
|
+
signal: AbortSignal.timeout(state.config.startup_timeout_seconds * 1000),
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) throw new Error(`Router health HTTP ${response.status}`);
|
|
96
|
+
return response.json();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function install() {
|
|
100
|
+
if (process.platform !== 'darwin') throw new Error('The Desktop installer requires macOS');
|
|
101
|
+
const config = { ...readJson(path.join(ROOT, 'config/pilot.json')), ...readJson(path.join(ROOT, 'config/desktop.json')) };
|
|
102
|
+
const original = loadConfig();
|
|
103
|
+
const env = workerEnvironment(process.env);
|
|
104
|
+
loadCredentials(env, original);
|
|
105
|
+
const diagnosis = doctor(original, env);
|
|
106
|
+
if (diagnosis.status !== 'ready') throw new Error('OpenCodex worker doctor must be ready before installation');
|
|
107
|
+
const configPath = path.join(env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'config.toml');
|
|
108
|
+
const before = fs.readFileSync(configPath, 'utf8');
|
|
109
|
+
const parsed = parseToml(before);
|
|
110
|
+
if (!['openai', 'opencodex'].includes(parsed.model_provider ?? 'openai')) throw new Error('An unrelated custom provider is active; refusing to replace it');
|
|
111
|
+
if (parsed.openai_base_url || parsed.chatgpt_base_url) throw new Error('An existing endpoint override needs an explicit integration plan');
|
|
112
|
+
fs.mkdirSync(STATE, { recursive: true, mode: 0o700 });
|
|
113
|
+
fs.chmodSync(STATE, 0o700);
|
|
114
|
+
const runtime = path.join(os.homedir(), '.local/share/opencodex/runtime');
|
|
115
|
+
copyRuntime(ROOT, runtime);
|
|
116
|
+
let catalog;
|
|
117
|
+
if (parsed.model_catalog_json && path.resolve(expandHome(parsed.model_catalog_json)) !== path.join(STATE, 'models.json')) {
|
|
118
|
+
catalog = readJson(expandHome(parsed.model_catalog_json));
|
|
119
|
+
} else {
|
|
120
|
+
const result = spawnSync(diagnosis.codex, ['debug', 'models', '--bundled'], { encoding: 'utf8' });
|
|
121
|
+
if (result.error || result.status !== 0) throw new Error('Cannot read the bundled Codex model catalog');
|
|
122
|
+
catalog = JSON.parse(result.stdout);
|
|
123
|
+
}
|
|
124
|
+
const nativeModels = catalog.models.filter(entry => entry.slug !== original.codex.model);
|
|
125
|
+
const child = { ...original.model_metadata, slug: original.codex.model, multi_agent_version: 'v2',
|
|
126
|
+
base_instructions: fs.readFileSync(path.join(ROOT, 'prompts/worker.md'), 'utf8') };
|
|
127
|
+
privateWrite(path.join(STATE, 'models.json'), JSON.stringify({ models: [...nativeModels, child] }));
|
|
128
|
+
Object.assign(config, { native_models: nativeModels.map(entry => entry.slug), child_model: child.slug,
|
|
129
|
+
deepseek_url: original.codex.model_providers['opencodex-deepseek'].base_url + '/responses',
|
|
130
|
+
receipts: path.join(STATE, 'receipts.jsonl'), markers: [] });
|
|
131
|
+
const statePath = path.join(STATE, 'state.json');
|
|
132
|
+
const capability = fs.existsSync(statePath) ? readJson(statePath).capability : randomBytes(32).toString('base64url');
|
|
133
|
+
const state = { config, capability, node: process.execPath, runtime };
|
|
134
|
+
const sections = {
|
|
135
|
+
'': { model_provider: 'opencodex', model_catalog_json: path.join(STATE, 'models.json') },
|
|
136
|
+
agents: { enabled: true, default_subagent_model: child.slug, default_subagent_reasoning_effort: 'high' },
|
|
137
|
+
features: { multi_agent: true, multi_agent_v2: true },
|
|
138
|
+
'model_providers.opencodex': { name: 'OpenCodex', base_url: `http://127.0.0.1:${config.port}`, wire_api: 'responses',
|
|
139
|
+
requires_openai_auth: true, supports_websockets: false, request_max_retries: 0, stream_max_retries: 0, stream_idle_timeout_ms: config.request_timeout_ms },
|
|
140
|
+
'model_providers.opencodex.http_headers': { 'x-opencodex-pilot': capability },
|
|
141
|
+
};
|
|
142
|
+
const updated = mergeConfig(before, sections);
|
|
143
|
+
privateWrite(path.join(STATE, 'config.proposed.toml'), updated);
|
|
144
|
+
if (!fs.existsSync(path.join(STATE, 'config.before.toml'))) privateWrite(path.join(STATE, 'config.before.toml'), before);
|
|
145
|
+
privateWrite(statePath, JSON.stringify(state));
|
|
146
|
+
const label = config.service_label;
|
|
147
|
+
const plist = path.join(os.homedir(), 'Library/LaunchAgents', label + '.plist');
|
|
148
|
+
fs.mkdirSync(path.dirname(plist), { recursive: true });
|
|
149
|
+
privateWrite(plist, plistDocument({ Label: label, ProgramArguments: [process.execPath, path.join(runtime, 'scripts/desktop.js'), 'serve'],
|
|
150
|
+
WorkingDirectory: runtime, RunAtLoad: true, KeepAlive: true, ThrottleInterval: config.service_throttle_seconds,
|
|
151
|
+
StandardOutPath: path.join(STATE, 'service.log'), StandardErrorPath: path.join(STATE, 'service-errors.log'),
|
|
152
|
+
EnvironmentVariables: { HOME: os.homedir(), PATH: env.PATH } }));
|
|
153
|
+
const domain = `gui/${process.getuid()}`;
|
|
154
|
+
spawnSync('launchctl', ['bootout', `${domain}/${label}`]);
|
|
155
|
+
const bootstrap = spawnSync('launchctl', ['bootstrap', domain, plist]);
|
|
156
|
+
if (bootstrap.error || bootstrap.status !== 0) throw new Error('Cannot start OpenCodex LaunchAgent');
|
|
157
|
+
const deadline = Date.now() + config.startup_timeout_seconds * 1000;
|
|
158
|
+
let report;
|
|
159
|
+
while (!report) {
|
|
160
|
+
try { report = await health(state); }
|
|
161
|
+
catch {
|
|
162
|
+
if (Date.now() >= deadline) {
|
|
163
|
+
spawnSync('launchctl', ['bootout', `${domain}/${label}`]);
|
|
164
|
+
throw new Error('Desktop router did not become healthy; user config unchanged');
|
|
165
|
+
}
|
|
166
|
+
await sleep(original.limits.poll_interval_seconds * 1000);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (fs.readFileSync(configPath, 'utf8') !== before) throw new Error('Codex config changed during installation; proposed config was not applied');
|
|
170
|
+
privateWrite(configPath, updated);
|
|
171
|
+
console.log(JSON.stringify({ ...report, service: label, port: config.port, cwd: runtime, owner: 'OpenCodex LaunchAgent',
|
|
172
|
+
native_models: nativeModels.length, subagent_model: child.slug, backup: path.join(STATE, 'config.before.toml'), restart_desktop_required: true }));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function main(args = process.argv.slice(2)) {
|
|
176
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
177
|
+
console.log('Usage: opencodex <install|status>\nActivate or inspect the local macOS Desktop router.');
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
if (args.length !== 1 || !['install', 'serve', 'status'].includes(args[0])) throw new Error('Expected install, serve or status');
|
|
181
|
+
if (args[0] === 'install') await install();
|
|
182
|
+
else {
|
|
183
|
+
const state = readJson(path.join(STATE, 'state.json'));
|
|
184
|
+
if (args[0] === 'status') console.log(JSON.stringify({ ...await health(state), port: state.config.port, cwd: state.runtime }));
|
|
185
|
+
else {
|
|
186
|
+
const env = workerEnvironment(process.env);
|
|
187
|
+
loadCredentials(env, loadConfig());
|
|
188
|
+
if (!env.DEEPSEEK_API_KEY) throw new Error('DeepSeek credential unavailable');
|
|
189
|
+
const server = await startPilot(state.config, env.DEEPSEEK_API_KEY, state.capability);
|
|
190
|
+
console.log(JSON.stringify({ status: 'ready', pid: process.pid, port: server.address().port }));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
197
|
+
try { process.exitCode = await main(); }
|
|
198
|
+
catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
199
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
5
|
+
import { appendFileSync, existsSync, renameSync, statSync } from 'node:fs';
|
|
6
|
+
import { Readable } from 'node:stream';
|
|
7
|
+
import { zstdDecompressSync } from 'node:zlib';
|
|
8
|
+
import { deepSeekCustomToolNames, deepSeekResponsesEffort, deepSeekResponsesInput } from '../vendor/codex-router/deepseek-responses.js';
|
|
9
|
+
import { bridgeCustomTools, flattenNamespaceTools, flattenNamespacedHistory, flattenToolChoice,
|
|
10
|
+
NamespaceToolCallTransform } from '../vendor/codex-router/namespace-relay.js';
|
|
11
|
+
|
|
12
|
+
// Relay adaptation: codex-router/src/router.mjs at 63ec1f3602c28f2a28ccb7e9edaf7b4f7d191c6c.
|
|
13
|
+
// Copyright (c) 2026 codex-router contributors; see ../vendor/codex-router/LICENSE.
|
|
14
|
+
// Same native collaboration token predicate as codex-router at the vendored revision.
|
|
15
|
+
const encryptedToken = /^gAAAAA[A-Za-z0-9_-]+={0,2}$/;
|
|
16
|
+
const nativeHeaderNames = [
|
|
17
|
+
'authorization', 'chatgpt-account-id', 'openai-beta', 'originator', 'session_id',
|
|
18
|
+
'session-id', 'thread-id', 'x-client-request-id', 'x-codex-beta-features',
|
|
19
|
+
'x-codex-installation-id', 'x-codex-parent-thread-id', 'x-codex-turn-metadata',
|
|
20
|
+
'x-codex-turn-state', 'x-codex-window-id', 'x-oai-attestation',
|
|
21
|
+
'x-openai-internal-codex-responses-lite', 'x-openai-subagent',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function nativeHeaders(headers) {
|
|
25
|
+
const result = { 'content-type': 'application/json', accept: 'text/event-stream' };
|
|
26
|
+
for (const key of nativeHeaderNames) if (headers[key]) result[key] = headers[key];
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function sseEvents(text) {
|
|
31
|
+
return text.split(/\r?\n\r?\n/).flatMap(frame => {
|
|
32
|
+
const data = frame.split(/\r?\n/).filter(line => line.startsWith('data:'))
|
|
33
|
+
.map(line => line.slice(5).trimStart()).join('\n');
|
|
34
|
+
return !data || data === '[DONE]' ? [] : [JSON.parse(data)];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function completedOutput(events) {
|
|
39
|
+
const items = new Map();
|
|
40
|
+
for (const event of events) {
|
|
41
|
+
if (event.type === 'response.output_item.done') items.set(event.item.id, event.item);
|
|
42
|
+
if (event.type === 'response.completed') {
|
|
43
|
+
for (const item of event.response.output || []) items.set(item.id, item);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [...items.values()];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function plaintextHandoffs(input) {
|
|
50
|
+
if (!Array.isArray(input)) return input;
|
|
51
|
+
return input.map(item => item.type !== 'agent_message' ? item : {
|
|
52
|
+
...item,
|
|
53
|
+
content: item.content.map(part => part.type === 'encrypted_content' && !encryptedToken.test(part.encrypted_content)
|
|
54
|
+
? { type: 'input_text', text: part.encrypted_content } : part),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function prepareDeepseek(payload, input) {
|
|
59
|
+
const flattened = flattenNamespaceTools(payload.tools, { maxNameLength: 64, aliasCollisions: true });
|
|
60
|
+
const bridged = bridgeCustomTools(flattened.tools, input, flattened.namespaces, payload.tool_choice,
|
|
61
|
+
deepSeekCustomToolNames(flattened.tools, input, payload.tool_choice), { maxNameLength: 64 });
|
|
62
|
+
return {
|
|
63
|
+
namespaces: flattened.namespaces,
|
|
64
|
+
payload: {
|
|
65
|
+
...payload, input: flattenNamespacedHistory(deepSeekResponsesInput(bridged.input), flattened.namespaces),
|
|
66
|
+
tools: bridged.tools, tool_choice: flattenToolChoice(bridged.toolChoice, flattened.namespaces),
|
|
67
|
+
reasoning: { effort: deepSeekResponsesEffort(payload.reasoning?.effort) },
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function startPilot(config, deepseekKey, capability) {
|
|
73
|
+
const relayCache = new Map();
|
|
74
|
+
let requestCount = 0;
|
|
75
|
+
const receipt = entry => {
|
|
76
|
+
if (existsSync(config.receipts) && statSync(config.receipts).size >= config.log_max_bytes) {
|
|
77
|
+
renameSync(config.receipts, config.receipts + '.1');
|
|
78
|
+
}
|
|
79
|
+
appendFileSync(config.receipts, JSON.stringify(entry) + '\n', { mode: 0o600 });
|
|
80
|
+
};
|
|
81
|
+
async function boundedText(response) {
|
|
82
|
+
let size = 0;
|
|
83
|
+
const parts = [];
|
|
84
|
+
for await (const part of response.body) {
|
|
85
|
+
size += part.length;
|
|
86
|
+
if (size > config.max_response_bytes) throw new Error('Response size limit exceeded');
|
|
87
|
+
parts.push(Buffer.from(part));
|
|
88
|
+
}
|
|
89
|
+
return Buffer.concat(parts).toString('utf8');
|
|
90
|
+
}
|
|
91
|
+
async function upstream(url, headers, body, signal) {
|
|
92
|
+
if (++requestCount > config.max_requests && config.max_requests !== null) throw new Error('Pilot request limit exceeded');
|
|
93
|
+
return fetch(url, { method: 'POST', headers, body: JSON.stringify(body), signal, redirect: 'error' });
|
|
94
|
+
}
|
|
95
|
+
async function handoff(item, headers, signal) {
|
|
96
|
+
const token = item.content.find(part => part.type === 'encrypted_content')?.encrypted_content;
|
|
97
|
+
if (!token || !encryptedToken.test(token)) return plaintextHandoffs([item])[0];
|
|
98
|
+
const cacheKey = createHash('sha256').update(headers.authorization).update(token).digest('hex');
|
|
99
|
+
if (relayCache.get(cacheKey)?.expires < Date.now()) relayCache.delete(cacheKey);
|
|
100
|
+
if (!relayCache.has(cacheKey)) {
|
|
101
|
+
// Adapted from relayEncryptedAgentPayloadOnce in codex-router/src/router.mjs.
|
|
102
|
+
const result = await upstream(config.native_url, headers, {
|
|
103
|
+
model: config.relay_model, stream: true, store: false,
|
|
104
|
+
instructions: 'You are a transport relay. Do not execute or answer the delegated task. Call relay_external_agent_payload exactly once with the exact plaintext after the Payload: label in the supplied collaboration message. Preserve every character.',
|
|
105
|
+
input: [item],
|
|
106
|
+
tools: [{ type: 'function', name: 'relay_external_agent_payload', strict: true,
|
|
107
|
+
parameters: { type: 'object', properties: { payload: { type: 'string' } }, required: ['payload'], additionalProperties: false } }],
|
|
108
|
+
tool_choice: { type: 'function', name: 'relay_external_agent_payload' },
|
|
109
|
+
}, signal);
|
|
110
|
+
const body = await boundedText(result);
|
|
111
|
+
receipt({ route: 'relay', model: config.relay_model, http_status: result.status });
|
|
112
|
+
if (!result.ok) throw new Error(`Native relay HTTP ${result.status}`);
|
|
113
|
+
const events = sseEvents(body);
|
|
114
|
+
if (!events.some(event => event.type === 'response.completed')) throw new Error('Native relay did not complete');
|
|
115
|
+
const calls = completedOutput(events).filter(item => item.type === 'function_call' && item.name === 'relay_external_agent_payload');
|
|
116
|
+
if (calls?.length !== 1) throw new Error('Native relay did not complete with one task payload');
|
|
117
|
+
const text = JSON.parse(calls[0].arguments).payload;
|
|
118
|
+
if (typeof text !== 'string' || !text.trim()) throw new Error('Native relay returned no task');
|
|
119
|
+
if (relayCache.size >= config.relay_cache_entries) relayCache.delete(relayCache.keys().next().value);
|
|
120
|
+
relayCache.set(cacheKey, { text, expires: Date.now() + config.relay_cache_ttl_ms });
|
|
121
|
+
}
|
|
122
|
+
return { ...item, content: item.content.map(part => part.type === 'encrypted_content'
|
|
123
|
+
? { type: 'input_text', text: relayCache.get(cacheKey).text } : part) };
|
|
124
|
+
}
|
|
125
|
+
const server = http.createServer(async (request, response) => {
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
const timer = setTimeout(() => controller.abort(), config.request_timeout_ms);
|
|
128
|
+
let clientCancelled = false;
|
|
129
|
+
let route;
|
|
130
|
+
let model;
|
|
131
|
+
response.on('close', () => {
|
|
132
|
+
if (!response.writableFinished && !controller.signal.aborted) {
|
|
133
|
+
clientCancelled = true;
|
|
134
|
+
controller.abort();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
try {
|
|
138
|
+
const supplied = Buffer.from(request.headers['x-opencodex-pilot'] || '');
|
|
139
|
+
const expected = Buffer.from(capability);
|
|
140
|
+
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) {
|
|
141
|
+
response.writeHead(403).end(); return;
|
|
142
|
+
}
|
|
143
|
+
if (request.method === 'GET' && request.url === '/health') {
|
|
144
|
+
response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ status: 'ready', pid: process.pid })); return;
|
|
145
|
+
}
|
|
146
|
+
const path = new URL(request.url, 'http://localhost').pathname;
|
|
147
|
+
const nativePaths = ['/responses', '/responses/compact', '/responses/lite', '/alpha/search'];
|
|
148
|
+
if (request.method !== 'POST' || !nativePaths.includes(path)) {
|
|
149
|
+
response.writeHead(404).end(); return;
|
|
150
|
+
}
|
|
151
|
+
const parts = []; let size = 0;
|
|
152
|
+
for await (const part of request) {
|
|
153
|
+
size += part.length;
|
|
154
|
+
if (size > config.max_request_bytes) throw new Error('Request size limit exceeded');
|
|
155
|
+
parts.push(part);
|
|
156
|
+
}
|
|
157
|
+
let bytes = Buffer.concat(parts);
|
|
158
|
+
const encoding = request.headers['content-encoding'];
|
|
159
|
+
if (encoding === 'zstd') bytes = zstdDecompressSync(bytes, { maxOutputLength: config.max_request_bytes });
|
|
160
|
+
else if (encoding) throw new Error('Unsupported request compression');
|
|
161
|
+
const payload = JSON.parse(bytes.toString('utf8'));
|
|
162
|
+
const isChild = payload.model === config.child_model;
|
|
163
|
+
route = isChild ? 'deepseek' : 'native';
|
|
164
|
+
model = payload.model;
|
|
165
|
+
if (!isChild && payload.model && !config.native_models.includes(payload.model)) throw new Error('Model outside configured catalog');
|
|
166
|
+
if (isChild && path !== '/responses') throw new Error('DeepSeek supports only the Responses endpoint');
|
|
167
|
+
const headers = nativeHeaders(request.headers);
|
|
168
|
+
if (!headers.authorization) throw new Error('Missing Codex authentication');
|
|
169
|
+
let body = payload;
|
|
170
|
+
let namespaces;
|
|
171
|
+
if (isChild) {
|
|
172
|
+
const input = [];
|
|
173
|
+
for (const item of payload.input) input.push(item.type === 'agent_message' ? await handoff(item, headers, controller.signal) : item);
|
|
174
|
+
const prepared = prepareDeepseek(payload, input);
|
|
175
|
+
body = prepared.payload;
|
|
176
|
+
namespaces = prepared.namespaces;
|
|
177
|
+
} else body = { ...payload, input: plaintextHandoffs(payload.input) };
|
|
178
|
+
const nativeUrl = config.native_url.replace(/\/responses$/, '') + path;
|
|
179
|
+
const result = await upstream(isChild ? config.deepseek_url : nativeUrl,
|
|
180
|
+
isChild ? { 'content-type': 'application/json', authorization: `Bearer ${deepseekKey}` } : headers,
|
|
181
|
+
body, controller.signal);
|
|
182
|
+
const entry = {
|
|
183
|
+
route: isChild ? 'deepseek' : 'native', model: payload.model, http_status: result.status,
|
|
184
|
+
recipients: Array.isArray(payload.input) ? payload.input.filter(item => item.type === 'agent_message').map(item => item.recipient) : [],
|
|
185
|
+
task_count: Array.isArray(payload.input) ? payload.input.filter(item => item.type === 'agent_message').length : 0,
|
|
186
|
+
tool_results: config.markers.map(marker => Array.isArray(payload.input) && payload.input.some(item => item.type === 'function_call_output' && JSON.stringify(item.output).includes(marker))),
|
|
187
|
+
};
|
|
188
|
+
const contentType = result.headers.get('content-type') || 'application/octet-stream';
|
|
189
|
+
const responseHeaders = { 'content-type': contentType };
|
|
190
|
+
for (const name of ['x-codex-turn-state', 'x-request-id', 'retry-after']) {
|
|
191
|
+
if (result.headers.has(name)) responseHeaders[name] = result.headers.get(name);
|
|
192
|
+
}
|
|
193
|
+
response.writeHead(result.status, responseHeaders);
|
|
194
|
+
const source = Readable.fromWeb(result.body);
|
|
195
|
+
const stream = isChild && result.ok ? source.pipe(new NamespaceToolCallTransform(namespaces, contentType)) : source;
|
|
196
|
+
if (stream !== source) source.on('error', error => stream.destroy(error));
|
|
197
|
+
const chunks = []; let length = 0;
|
|
198
|
+
for await (const chunk of stream) {
|
|
199
|
+
length += chunk.length;
|
|
200
|
+
if (length > config.max_response_bytes) throw new Error('Response size limit exceeded');
|
|
201
|
+
chunks.push(Buffer.from(chunk));
|
|
202
|
+
if (!response.write(chunk)) await once(response, 'drain', { signal: controller.signal });
|
|
203
|
+
}
|
|
204
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
205
|
+
if (result.ok && (contentType.includes('text/event-stream') || /^(?:event:|data:)/.test(text))) {
|
|
206
|
+
const events = sseEvents(text);
|
|
207
|
+
const completed = events.find(event => event.type === 'response.completed');
|
|
208
|
+
entry.completed = !!completed;
|
|
209
|
+
entry.response_model = completed?.response?.model;
|
|
210
|
+
const usage = completed?.response?.usage;
|
|
211
|
+
entry.usage = usage && { input_tokens: usage.input_tokens, output_tokens: usage.output_tokens,
|
|
212
|
+
cached_tokens: usage.input_tokens_details?.cached_tokens };
|
|
213
|
+
const output = completedOutput(events);
|
|
214
|
+
entry.calls = output.filter(item => item.type === 'function_call' || item.type === 'custom_tool_call').map(item => ({ name: item.name, namespace: item.namespace }));
|
|
215
|
+
const visible = output.filter(item => item.type === 'message').flatMap(item => item.content || []).filter(part => part.type === 'output_text').map(part => part.text).join('\n');
|
|
216
|
+
entry.answers = config.markers.map(marker => visible.includes(marker));
|
|
217
|
+
}
|
|
218
|
+
receipt(entry);
|
|
219
|
+
response.end();
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (clientCancelled) {
|
|
222
|
+
receipt({ route: 'cancelled', upstream_route: route, model, reason: 'codex_disconnected' });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
receipt({ route: 'error', message: String(error.message).replaceAll(deepseekKey, '[REDACTED]').replaceAll(capability, '[REDACTED]') });
|
|
226
|
+
if (!response.headersSent) response.writeHead(502, { 'content-type': 'application/json' }).end(JSON.stringify({ error: { message: 'OpenCodex request failed; inspect local receipts.' } }));
|
|
227
|
+
else response.destroy();
|
|
228
|
+
} finally { clearTimeout(timer); }
|
|
229
|
+
});
|
|
230
|
+
server.listen(config.port, '127.0.0.1');
|
|
231
|
+
await once(server, 'listening');
|
|
232
|
+
return server;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
236
|
+
let input = '';
|
|
237
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
238
|
+
const { config, capability } = JSON.parse(input);
|
|
239
|
+
const server = await startPilot(config, process.env.DEEPSEEK_API_KEY, capability);
|
|
240
|
+
process.stdout.write(JSON.stringify({ pid: process.pid, port: server.address().port }) + '\n');
|
|
241
|
+
}
|