@andrewting19/oracle 0.9.1 → 0.9.3
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/dist/src/remote/server.js +46 -9
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ import { CHATGPT_URL } from "../browser/constants.js";
|
|
|
12
12
|
import { getCliVersion } from "../version.js";
|
|
13
13
|
import { cleanupStaleProfileState, readDevToolsPort, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from "../browser/profileState.js";
|
|
14
14
|
import { normalizeChatgptUrl } from "../browser/utils.js";
|
|
15
|
+
import { launchChrome } from "../browser/chromeLifecycle.js";
|
|
15
16
|
async function findAvailablePort() {
|
|
16
17
|
return await new Promise((resolve, reject) => {
|
|
17
18
|
const srv = net.createServer();
|
|
@@ -38,8 +39,10 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
38
39
|
const color = process.stdout.isTTY
|
|
39
40
|
? (formatter, msg) => formatter(msg)
|
|
40
41
|
: (_formatter, msg) => msg;
|
|
41
|
-
//
|
|
42
|
-
|
|
42
|
+
// Concurrency control: allow parallel browser runs (each gets its own Chrome tab).
|
|
43
|
+
// Set ORACLE_SERVE_MAX_CONCURRENT to limit simultaneous runs (default: unlimited).
|
|
44
|
+
let activeRuns = 0;
|
|
45
|
+
const maxConcurrent = parseInt(process.env.ORACLE_SERVE_MAX_CONCURRENT ?? "0", 10) || Infinity;
|
|
43
46
|
if (!process.listenerCount("unhandledRejection")) {
|
|
44
47
|
process.on("unhandledRejection", (reason) => {
|
|
45
48
|
logger(`Unhandled promise rejection in remote server: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
@@ -67,6 +70,8 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
67
70
|
ok: true,
|
|
68
71
|
version: getCliVersion(),
|
|
69
72
|
uptimeSeconds: Math.round((Date.now() - startedAt) / 1000),
|
|
73
|
+
activeRuns,
|
|
74
|
+
maxConcurrent: maxConcurrent === Infinity ? null : maxConcurrent,
|
|
70
75
|
}));
|
|
71
76
|
return;
|
|
72
77
|
}
|
|
@@ -84,15 +89,15 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
84
89
|
res.end(JSON.stringify({ error: "unauthorized" }));
|
|
85
90
|
return;
|
|
86
91
|
}
|
|
87
|
-
if (
|
|
92
|
+
if (activeRuns >= maxConcurrent) {
|
|
88
93
|
if (verbose) {
|
|
89
|
-
logger(`[serve]
|
|
94
|
+
logger(`[serve] At capacity (${activeRuns}/${maxConcurrent}): rejecting run from ${formatSocket(req)}`);
|
|
90
95
|
}
|
|
91
|
-
res.writeHead(
|
|
92
|
-
res.end(JSON.stringify({ error: "
|
|
96
|
+
res.writeHead(429, { "Content-Type": "application/json" });
|
|
97
|
+
res.end(JSON.stringify({ error: "at_capacity", activeRuns, maxConcurrent }));
|
|
93
98
|
return;
|
|
94
99
|
}
|
|
95
|
-
|
|
100
|
+
activeRuns++;
|
|
96
101
|
const runStartedAt = Date.now();
|
|
97
102
|
let payload = null;
|
|
98
103
|
try {
|
|
@@ -103,7 +108,7 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
catch {
|
|
106
|
-
|
|
111
|
+
activeRuns--;
|
|
107
112
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
108
113
|
res.end(JSON.stringify({ error: "invalid_request" }));
|
|
109
114
|
return;
|
|
@@ -147,6 +152,10 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
147
152
|
else {
|
|
148
153
|
payload.browserConfig = {};
|
|
149
154
|
}
|
|
155
|
+
// Route through shared Chrome instance (each run gets its own isolated tab).
|
|
156
|
+
if (options.sharedChrome) {
|
|
157
|
+
payload.browserConfig.remoteChrome = options.sharedChrome;
|
|
158
|
+
}
|
|
150
159
|
// Enforce manual-login profile when cookie sync is unavailable (e.g., Windows/WSL).
|
|
151
160
|
if (options.manualLoginDefault) {
|
|
152
161
|
payload.browserConfig.manualLogin = true;
|
|
@@ -173,7 +182,7 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
173
182
|
logger(`[serve] Run ${runId} failed after ${Date.now() - runStartedAt}ms: ${message}`);
|
|
174
183
|
}
|
|
175
184
|
finally {
|
|
176
|
-
|
|
185
|
+
activeRuns--;
|
|
177
186
|
res.end();
|
|
178
187
|
try {
|
|
179
188
|
await rm(runDir, { recursive: true, force: true });
|
|
@@ -260,10 +269,38 @@ export async function serveRemote(options = {}) {
|
|
|
260
269
|
else {
|
|
261
270
|
console.log(`Detected ${cookies.length} ChatGPT cookies on this host; runs will reuse this session.`);
|
|
262
271
|
}
|
|
272
|
+
// Launch a shared Chrome instance for concurrent tab-based runs.
|
|
273
|
+
let sharedChrome;
|
|
274
|
+
if (!preferManualLogin) {
|
|
275
|
+
try {
|
|
276
|
+
const userDataDir = await mkdtemp(path.join(os.tmpdir(), "oracle-serve-chrome-"));
|
|
277
|
+
const chrome = await launchChrome({ headless: false, hideWindow: true }, userDataDir, console.log);
|
|
278
|
+
sharedChrome = { host: "127.0.0.1", port: chrome.port };
|
|
279
|
+
console.log(`Shared Chrome launched (pid ${chrome.pid}, port ${chrome.port}). Concurrent runs will use isolated tabs.`);
|
|
280
|
+
// Clean up Chrome on process exit
|
|
281
|
+
const killChrome = async () => {
|
|
282
|
+
try {
|
|
283
|
+
await chrome.kill();
|
|
284
|
+
}
|
|
285
|
+
catch { }
|
|
286
|
+
try {
|
|
287
|
+
await rm(userDataDir, { recursive: true, force: true });
|
|
288
|
+
}
|
|
289
|
+
catch { }
|
|
290
|
+
};
|
|
291
|
+
process.on("SIGINT", () => void killChrome());
|
|
292
|
+
process.on("SIGTERM", () => void killChrome());
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
296
|
+
console.log(`Warning: failed to launch shared Chrome (${message}). Runs will each launch their own Chrome.`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
263
299
|
const server = await createRemoteServer({
|
|
264
300
|
...options,
|
|
265
301
|
manualLoginDefault: preferManualLogin,
|
|
266
302
|
manualLoginProfileDir: manualProfileDir,
|
|
303
|
+
sharedChrome,
|
|
267
304
|
});
|
|
268
305
|
await new Promise((resolve) => {
|
|
269
306
|
const shutdown = () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrewting19/oracle",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "CLI wrapper around OpenAI Responses API with GPT-5.4 Pro, GPT-5.4, GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/steipete/oracle#readme",
|