codex-adaptive-effort 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.en.md +5 -0
- package/README.md +115 -0
- package/README.zh-CN.md +133 -0
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/bin/cae-desktop-bridge.mjs +41 -0
- package/bin/cae.mjs +152 -0
- package/docs/DESKTOP_LAUNCHER.md +122 -0
- package/docs/LIMITATIONS.md +34 -0
- package/docs/LOCAL_VALIDATION.md +83 -0
- package/docs/NPM.md +73 -0
- package/package.json +56 -0
- package/src/audit.mjs +71 -0
- package/src/codex.mjs +101 -0
- package/src/config.mjs +74 -0
- package/src/context.mjs +87 -0
- package/src/controller.mjs +158 -0
- package/src/desktop.mjs +295 -0
- package/src/judge-timing.mjs +44 -0
- package/src/judge.mjs +105 -0
- package/src/proxy.mjs +183 -0
- package/src/stream.mjs +58 -0
- package/src/util.mjs +49 -0
- package/src/version.mjs +3 -0
package/src/proxy.mjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import { createHmac, randomBytes, randomUUID } from 'node:crypto';
|
|
4
|
+
import { Transform } from 'node:stream';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { CaeError, equalSecret, isObject, parseJson, checkAbort } from './util.mjs';
|
|
7
|
+
import { validateConfig, upstreamBase } from './config.mjs';
|
|
8
|
+
import { Controller } from './controller.mjs';
|
|
9
|
+
import { ResponseObserver } from './stream.mjs';
|
|
10
|
+
|
|
11
|
+
const HOP = new Set(['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer', 'transfer-encoding', 'upgrade']);
|
|
12
|
+
export function forwardedHeaders(headers, response = false) {
|
|
13
|
+
const remove = new Set(HOP);
|
|
14
|
+
for (const name of String(headers.connection ?? '').split(',')) remove.add(name.trim().toLowerCase());
|
|
15
|
+
const result = {};
|
|
16
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
17
|
+
const key = name.toLowerCase();
|
|
18
|
+
if (value === undefined || remove.has(key) || key.startsWith('x-cae-')) continue;
|
|
19
|
+
if (['host', 'content-length', 'cookie', 'set-cookie', 'origin', 'referer', 'forwarded'].includes(key) || key.startsWith('x-forwarded-')) continue;
|
|
20
|
+
if (!response && (key.startsWith('sec-') || key.startsWith('proxy-'))) continue;
|
|
21
|
+
result[key] = value;
|
|
22
|
+
}
|
|
23
|
+
if (!response) result['accept-encoding'] = 'identity';
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function json(res, status, data) {
|
|
27
|
+
if (res.destroyed || res.headersSent) return;
|
|
28
|
+
const bytes = Buffer.from(JSON.stringify(data));
|
|
29
|
+
res.writeHead(status, { 'content-type': 'application/json', 'content-length': bytes.length, 'cache-control': 'no-store' });
|
|
30
|
+
res.end(bytes);
|
|
31
|
+
}
|
|
32
|
+
async function readBody(req, limit) {
|
|
33
|
+
const length = Number(req.headers['content-length']);
|
|
34
|
+
if (Number.isFinite(length) && length > limit) { req.resume(); throw new CaeError('request_too_large', 413); }
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
let size = 0; const chunks = [];
|
|
37
|
+
const timer = setTimeout(() => finish(new CaeError('request_body_timeout', 408)), 15000);
|
|
38
|
+
function cleanup() {
|
|
39
|
+
clearTimeout(timer); req.off('data', data); req.off('end', end); req.off('aborted', abort); req.off('error', error);
|
|
40
|
+
}
|
|
41
|
+
function finish(err, result) { cleanup(); if (err) { req.resume(); reject(err); } else resolve(result); }
|
|
42
|
+
function data(chunk) {
|
|
43
|
+
size += chunk.length;
|
|
44
|
+
if (size > limit) finish(new CaeError('request_too_large', 413));
|
|
45
|
+
else chunks.push(chunk);
|
|
46
|
+
}
|
|
47
|
+
function end() { finish(null, Buffer.concat(chunks)); }
|
|
48
|
+
function abort() { finish(new CaeError('cancelled', 499)); }
|
|
49
|
+
function error() { finish(new CaeError('request_read_error')); }
|
|
50
|
+
req.on('data', data); req.once('end', end); req.once('aborted', abort); req.once('error', error);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export async function startProxy(config, { token, judge, emit = () => {}, allowUpstream = false, auditHealthy = () => true, shadowOnly = false } = {}) {
|
|
54
|
+
const c = validateConfig(config);
|
|
55
|
+
if (typeof token !== 'string' || !/^[a-f0-9]{64}$/.test(token)) throw new CaeError('invalid_local_token');
|
|
56
|
+
if (c.upstream.kind !== 'mock' && !allowUpstream) throw new CaeError('upstream_not_enabled');
|
|
57
|
+
const controller = new Controller(c, judge, { emit, shadowOnly });
|
|
58
|
+
const sessionSalt = randomBytes(32);
|
|
59
|
+
const aborters = new Set(); let port; let inflight = 0;
|
|
60
|
+
const server = http.createServer(async (req, res) => {
|
|
61
|
+
const aborter = new AbortController(); aborters.add(aborter);
|
|
62
|
+
const close = () => { if (!res.writableFinished) aborter.abort(); };
|
|
63
|
+
res.once('close', close); req.once('aborted', () => aborter.abort());
|
|
64
|
+
let tx; let outcome; const started = Date.now(); const id = randomUUID();
|
|
65
|
+
try {
|
|
66
|
+
// Native Node fetch adds sec-fetch-mode; browsers also supply Origin or
|
|
67
|
+
// the site/destination metadata. Token and Host checks remain mandatory.
|
|
68
|
+
if (req.headers.origin !== undefined || ['sec-fetch-site', 'sec-fetch-dest', 'sec-fetch-user'].some(k => req.headers[k] !== undefined))
|
|
69
|
+
throw new CaeError('browser_requests_forbidden', 403);
|
|
70
|
+
if (![`127.0.0.1:${port}`, `localhost:${port}`].includes(req.headers.host)) throw new CaeError('invalid_host', 403);
|
|
71
|
+
if (!equalSecret(req.headers['x-cae-token'], token)) throw new CaeError('local_auth_required', 401);
|
|
72
|
+
if (inflight >= 64) throw new CaeError('too_many_requests', 429);
|
|
73
|
+
++inflight;
|
|
74
|
+
try {
|
|
75
|
+
if (!req.url.startsWith('/') || req.url.startsWith('//')) throw new CaeError('invalid_path', 404);
|
|
76
|
+
const url = new URL(req.url, 'http://127.0.0.1');
|
|
77
|
+
if (url.pathname === '/health' && req.method === 'GET') { json(res, 200, { ok: true, auditHealthy: auditHealthy() === true, ...controller.status() }); return; }
|
|
78
|
+
if (url.pathname === '/control') {
|
|
79
|
+
if (req.method === 'GET') { json(res, 200, controller.status()); return; }
|
|
80
|
+
if (req.method === 'POST') { json(res, 200, controller.control(parseJson(await readBody(req, 4096)))); return; }
|
|
81
|
+
throw new CaeError('method_not_allowed', 405);
|
|
82
|
+
}
|
|
83
|
+
const path = url.pathname.replace(/^\/v1(?=\/)/, '');
|
|
84
|
+
const generate = path === '/responses' && req.method === 'POST';
|
|
85
|
+
const compact = path === '/responses/compact' && req.method === 'POST';
|
|
86
|
+
const models = path === '/models' && req.method === 'GET';
|
|
87
|
+
if (!generate && !compact && !models) throw new CaeError('unsupported_route', 404);
|
|
88
|
+
if (c.upstream.kind !== 'mock' && !/^Bearer [^\r\n]+$/.test(req.headers.authorization ?? '')) throw new CaeError('upstream_auth_required', 401);
|
|
89
|
+
if (req.headers['content-encoding'] && req.headers['content-encoding'] !== 'identity') throw new CaeError('compressed_requests_unsupported', 415);
|
|
90
|
+
let bytes = models ? Buffer.alloc(0) : await readBody(req, c.maxBodyBytes);
|
|
91
|
+
let expectsSse = false;
|
|
92
|
+
if (generate) {
|
|
93
|
+
if (!String(req.headers['content-type'] ?? '').startsWith('application/json')) throw new CaeError('json_required', 415);
|
|
94
|
+
const body = parseJson(bytes);
|
|
95
|
+
if (!isObject(body) || typeof body.model !== 'string') throw new CaeError('invalid_responses_request');
|
|
96
|
+
expectsSse = body.stream === true;
|
|
97
|
+
// prompt_cache_key is NOT a session identity. Without an explicit native
|
|
98
|
+
// session header or local adapter header, there is no cross-call lease.
|
|
99
|
+
const sid = req.headers['x-cae-session'] ?? req.headers.session_id;
|
|
100
|
+
const sessionKey = typeof sid === 'string' && sid.length > 0 && sid.length < 300 ?
|
|
101
|
+
createHmac('sha256', sessionSalt).update(`${req.headers.authorization ?? ''}\0${sid}`).digest('hex') : null;
|
|
102
|
+
tx = await controller.prepare(body, { sessionKey, signal: aborter.signal, id });
|
|
103
|
+
checkAbort(aborter.signal);
|
|
104
|
+
controller.beforeSend(tx);
|
|
105
|
+
if (tx.changed && tx.body) bytes = Buffer.from(JSON.stringify(tx.body));
|
|
106
|
+
}
|
|
107
|
+
checkAbort(aborter.signal);
|
|
108
|
+
outcome = await relay(new URL(upstreamBase(c) + path + url.search), req, res, bytes,
|
|
109
|
+
aborter.signal, c.upstreamTimeoutMs, () => { if (tx) controller.markSent(tx); }, expectsSse);
|
|
110
|
+
} finally { --inflight; }
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const safe = error instanceof CaeError ? error : new CaeError('proxy_transport_error', 502);
|
|
113
|
+
if (!res.headersSent && !res.destroyed) json(res, safe.status, { error: { type: 'cae_error', code: safe.code } });
|
|
114
|
+
else if (!res.writableFinished) res.destroy();
|
|
115
|
+
outcome ??= { completed: false, terminal: safe.code, httpStatus: null };
|
|
116
|
+
} finally {
|
|
117
|
+
if (tx) {
|
|
118
|
+
controller.finish(tx, { completed: outcome?.completed === true });
|
|
119
|
+
emit({ event: 'upstream_outcome', requestId: id, model: tx.model,
|
|
120
|
+
durationMs: Date.now() - started, ...outcome });
|
|
121
|
+
}
|
|
122
|
+
aborters.delete(aborter); res.off('close', close);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
server.headersTimeout = 10000; server.requestTimeout = 15000; server.keepAliveTimeout = 3000;
|
|
126
|
+
server.on('upgrade', (_req, socket) => {
|
|
127
|
+
socket.end('HTTP/1.1 426 Upgrade Required\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
|
|
128
|
+
});
|
|
129
|
+
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(c.port, '127.0.0.1', resolve); });
|
|
130
|
+
port = server.address().port;
|
|
131
|
+
return { port, controller, server,
|
|
132
|
+
async close() {
|
|
133
|
+
for (const a of aborters) a.abort();
|
|
134
|
+
server.closeAllConnections();
|
|
135
|
+
await new Promise(resolve => server.close(resolve));
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function relay(url, incoming, outgoing, bytes, signal, timeoutMs, onSent, expectsSse = false) {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
let settled = false, observer, httpStatus;
|
|
142
|
+
const headers = forwardedHeaders(incoming.headers);
|
|
143
|
+
if (incoming.method === 'POST') { headers['content-length'] = bytes.length; headers['content-type'] = 'application/json'; }
|
|
144
|
+
const client = (url.protocol === 'https:' ? https : http).request(url, { method: incoming.method, headers });
|
|
145
|
+
const abort = () => client.destroy(new CaeError('cancelled', 499));
|
|
146
|
+
const timer = setTimeout(() => client.destroy(new CaeError('upstream_timeout', 504)), timeoutMs);
|
|
147
|
+
function finish(err, value) {
|
|
148
|
+
if (settled) return;
|
|
149
|
+
// Native clients may close immediately after response.completed, before
|
|
150
|
+
// HTTP EOF. A validated terminal is authoritative; mid-stream cancellation is not.
|
|
151
|
+
if (err && observer && httpStatus >= 200 && httpStatus < 300) {
|
|
152
|
+
const observed = observer.end();
|
|
153
|
+
if (observed.completed) { err = null; value = { ...observed, httpStatus }; }
|
|
154
|
+
}
|
|
155
|
+
settled = true; clearTimeout(timer); signal.removeEventListener('abort', abort);
|
|
156
|
+
if (err) reject(err); else resolve(value);
|
|
157
|
+
}
|
|
158
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
159
|
+
client.once('error', err => finish(err instanceof CaeError ? err : new CaeError('upstream_connection_error', 502)));
|
|
160
|
+
client.once('finish', onSent);
|
|
161
|
+
client.once('response', async upstream => {
|
|
162
|
+
if (upstream.statusCode >= 300 && upstream.statusCode < 400) {
|
|
163
|
+
upstream.destroy(); finish(new CaeError('upstream_redirect_refused', 502)); return;
|
|
164
|
+
}
|
|
165
|
+
// Native streaming responses may omit Content-Type. Use the explicit
|
|
166
|
+
// request hint only for observation; leave all response bytes/headers intact.
|
|
167
|
+
httpStatus = upstream.statusCode;
|
|
168
|
+
observer = new ResponseObserver(upstream.headers['content-type'] ?? (expectsSse ? 'text/event-stream' : undefined));
|
|
169
|
+
outgoing.writeHead(upstream.statusCode, forwardedHeaders(upstream.headers, true));
|
|
170
|
+
const tap = new Transform({ transform(chunk, _encoding, done) { observer.push(chunk); done(null, chunk); } });
|
|
171
|
+
try {
|
|
172
|
+
await pipeline(upstream, tap, outgoing);
|
|
173
|
+
const observed = observer.end();
|
|
174
|
+
finish(null, { ...observed, httpStatus: upstream.statusCode,
|
|
175
|
+
completed: upstream.statusCode >= 200 && upstream.statusCode < 300 && observed.completed });
|
|
176
|
+
} catch { finish(new CaeError(signal.aborted ? 'cancelled' : 'upstream_stream_interrupted', signal.aborted ? 499 : 502)); }
|
|
177
|
+
});
|
|
178
|
+
if (signal.aborted) abort();
|
|
179
|
+
else client.end(bytes);
|
|
180
|
+
// No automatic upstream retries, before or after any bytes. Codex may have
|
|
181
|
+
// its own retry policy; the generated one-off launch configuration disables it.
|
|
182
|
+
});
|
|
183
|
+
}
|
package/src/stream.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
2
|
+
import { usageOf } from './audit.mjs';
|
|
3
|
+
import { isObject } from './util.mjs';
|
|
4
|
+
|
|
5
|
+
/** Observe a bounded copy of terminal metadata; never rewrite the byte stream. */
|
|
6
|
+
export class ResponseObserver {
|
|
7
|
+
constructor(contentType, limit = 2 * 1024 * 1024) {
|
|
8
|
+
this.sse = String(contentType ?? '').includes('text/event-stream');
|
|
9
|
+
this.limit = limit; this.buffer = ''; this.decoder = new StringDecoder('utf8');
|
|
10
|
+
this.dropping = false; this.overflow = false; this.createdId = null;
|
|
11
|
+
this.terminal = null; this.completed = false; this.usage = usageOf(null);
|
|
12
|
+
}
|
|
13
|
+
push(chunk) {
|
|
14
|
+
this.buffer += this.decoder.write(chunk);
|
|
15
|
+
if (!this.sse) {
|
|
16
|
+
if (this.buffer.length > this.limit) { this.buffer = ''; this.overflow = true; }
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = /\r?\n\r?\n/.exec(this.buffer))) {
|
|
21
|
+
const frame = this.buffer.slice(0, match.index);
|
|
22
|
+
this.buffer = this.buffer.slice(match.index + match[0].length);
|
|
23
|
+
if (!this.dropping && frame.length <= this.limit) this.frame(frame);
|
|
24
|
+
this.dropping = false;
|
|
25
|
+
}
|
|
26
|
+
if (this.buffer.length > this.limit) { this.buffer = this.buffer.slice(-4); this.dropping = true; this.overflow = true; }
|
|
27
|
+
}
|
|
28
|
+
frame(text) {
|
|
29
|
+
const data = text.split(/\r?\n/).filter(s => s.startsWith('data:')).map(s => s.slice(5).replace(/^ /, '')).join('\n');
|
|
30
|
+
if (!data || data === '[DONE]') return;
|
|
31
|
+
let event;
|
|
32
|
+
try { event = JSON.parse(data); } catch { return; }
|
|
33
|
+
if (!isObject(event)) return;
|
|
34
|
+
if (event.type === 'response.created') this.createdId = event.response?.id ?? null;
|
|
35
|
+
if (['response.completed', 'response.failed', 'response.incomplete'].includes(event.type)) {
|
|
36
|
+
this.terminal = event.type;
|
|
37
|
+
this.completed = event.type === 'response.completed' &&
|
|
38
|
+
typeof event.response?.id === 'string' && event.response.id.length > 0 &&
|
|
39
|
+
event.response.status === 'completed' &&
|
|
40
|
+
(!this.createdId || this.createdId === event.response.id);
|
|
41
|
+
this.usage = usageOf(event.response);
|
|
42
|
+
}
|
|
43
|
+
if (event.type === 'error') { this.terminal = 'error'; this.completed = false; }
|
|
44
|
+
}
|
|
45
|
+
end() {
|
|
46
|
+
this.buffer += this.decoder.end();
|
|
47
|
+
if (!this.sse && !this.overflow) {
|
|
48
|
+
try {
|
|
49
|
+
const body = JSON.parse(this.buffer);
|
|
50
|
+
this.terminal = typeof body.status === 'string' ? body.status : null;
|
|
51
|
+
this.completed = body.status === 'completed' && typeof body.id === 'string' && body.id.length > 0;
|
|
52
|
+
this.usage = usageOf(body);
|
|
53
|
+
} catch { /* Unknown, not zero. */ }
|
|
54
|
+
}
|
|
55
|
+
this.buffer = '';
|
|
56
|
+
return { terminal: this.terminal, completed: this.completed, ...this.usage };
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/util.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export class CaeError extends Error {
|
|
4
|
+
constructor(code, status = 400) {
|
|
5
|
+
super(code);
|
|
6
|
+
this.name = 'CaeError';
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.status = status;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
12
|
+
export function stable(v) {
|
|
13
|
+
if (Array.isArray(v)) return `[${v.map(stable).join(',')}]`;
|
|
14
|
+
if (isObject(v)) return `{${Object.keys(v).sort().map(k => `${JSON.stringify(k)}:${stable(v[k])}`).join(',')}}`;
|
|
15
|
+
return JSON.stringify(v);
|
|
16
|
+
}
|
|
17
|
+
export const digest = (v) => createHash('sha256').update(typeof v === 'string' ? v : stable(v)).digest('hex');
|
|
18
|
+
export function equalSecret(a, b) {
|
|
19
|
+
if (typeof a !== 'string' || typeof b !== 'string') return false;
|
|
20
|
+
const x = Buffer.from(a), y = Buffer.from(b);
|
|
21
|
+
return x.length === y.length && timingSafeEqual(x, y);
|
|
22
|
+
}
|
|
23
|
+
export function checkAbort(signal) {
|
|
24
|
+
if (signal?.aborted) throw new CaeError('cancelled', 499);
|
|
25
|
+
}
|
|
26
|
+
export async function bounded(promise, ms, signal) {
|
|
27
|
+
checkAbort(signal);
|
|
28
|
+
let timer, onAbort;
|
|
29
|
+
const gate = new Promise((_, reject) => {
|
|
30
|
+
timer = setTimeout(() => reject(new CaeError('judge_timeout', 504)), ms);
|
|
31
|
+
onAbort = () => reject(new CaeError('cancelled', 499));
|
|
32
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
33
|
+
});
|
|
34
|
+
try { return await Promise.race([promise, gate]); }
|
|
35
|
+
finally { clearTimeout(timer); signal?.removeEventListener('abort', onAbort); }
|
|
36
|
+
}
|
|
37
|
+
export function shortText(text, limit) {
|
|
38
|
+
if (typeof text !== 'string') return '';
|
|
39
|
+
// Slice by Unicode code points, not UTF-16 halves. Bounds are characters, NOT tokens.
|
|
40
|
+
const points = Array.from(text);
|
|
41
|
+
if (points.length <= limit) return text;
|
|
42
|
+
const half = Math.floor((limit - 18) / 2);
|
|
43
|
+
return points.slice(0, half).join('') + '\n[… truncated …]\n' + points.slice(-half).join('');
|
|
44
|
+
}
|
|
45
|
+
export function parseJson(bytes) {
|
|
46
|
+
try { return JSON.parse(bytes.toString('utf8')); }
|
|
47
|
+
catch { throw new CaeError('invalid_json'); }
|
|
48
|
+
}
|
|
49
|
+
export const knownNumber = v => typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : null;
|
package/src/version.mjs
ADDED