mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 21:02:50 +03:00
feat(executors): make chatgpt-session stream-open timeout tunable
Add OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS to override the 30s CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS default, mirroring resolveDirectHeadersTimeoutMs's validation. Resolved per request in the executor so a changed env var takes effect without a restart.
This commit is contained in:
@@ -2922,6 +2922,15 @@ QUOTA_STORE_DRIVER=sqlite
|
||||
# CODEX_CHATGPT_WEB_BUN=/absolute/path/to/bun
|
||||
# CODEX_WEB_GPT_BUN=/absolute/path/to/bun
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ChatGPT Session provider — stream-open gate
|
||||
# Used by: open-sse/executors/chatgpt-session/bridge.ts
|
||||
# How long the stream-open gate waits for the first committing event before
|
||||
# opening the stream anyway. Falls back to the 30s default when unset, empty,
|
||||
# unparseable, non-finite, or not positive.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS=30000
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Browser-login VNC sessions (optional — src/lib/vncSession/manifest.ts)
|
||||
# Containerized Chromium+VNC used for interactive browser-login credential
|
||||
|
||||
@@ -749,6 +749,7 @@ REQUEST_TIMEOUT_MS (global override)
|
||||
| `OMNIROUTE_CODEX_APPSERVER_AUTO_APPROVE` | `false` | Auto-approve the app-server's own approval prompts (command/file/permission execution on the host). Off by default — prompts are auto-denied; harness tool calls are unaffected (they travel the separate `item/tool/call` passthrough). Accepts `true`/`1`/`yes`. Per-connection override: `providerSpecificData.codexAppServerAutoApprove`. |
|
||||
| `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive response headers. |
|
||||
| `OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS` | `30000` (30s) | Maximum response-start wait (ms) for each direct no-proxy attempt. A timeout retries once on a fresh socket; set `0` to disable the bound and retain the previous behavior. |
|
||||
| `OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS` | `30000` (30s) | How long the `chatgpt-session` executor's stream-open gate waits for the first committing event before opening the stream anyway (`open-sse/executors/chatgpt-session/bridge.ts`). Falls back to the 30s default when unset, empty, unparseable, non-finite, or not positive. |
|
||||
| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive the full response body. |
|
||||
| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | TCP connection establishment timeout. |
|
||||
| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive socket idle timeout. |
|
||||
|
||||
@@ -24,6 +24,7 @@ import { connectionRuntimePaths } from "./chatgpt-web-codex/storageState.ts";
|
||||
import {
|
||||
buildChatGptSessionCompletion,
|
||||
openChatGptSessionStream,
|
||||
resolveChatGptSessionStreamOpenTimeoutMs,
|
||||
type ChatGptSessionResponseMeta,
|
||||
} from "./chatgpt-session/bridge.ts";
|
||||
import { classifyChatGptSessionError } from "./chatgpt-session/errors.ts";
|
||||
@@ -311,7 +312,9 @@ export class ChatGptSessionExecutor extends BaseExecutor {
|
||||
}
|
||||
|
||||
void run();
|
||||
const opened = await openChatGptSessionStream(events, meta);
|
||||
const opened = await openChatGptSessionStream(events, meta, {
|
||||
streamOpenTimeoutMs: resolveChatGptSessionStreamOpenTimeoutMs(),
|
||||
});
|
||||
if (opened.kind === "error") {
|
||||
// Every field of the verdict comes from the bridge's classification of the real adapter
|
||||
// event — status, code and fallbackHint alike. Re-classifying the sanitized message here
|
||||
|
||||
@@ -64,6 +64,24 @@ const COMMENTARY_PHASE: CodexMessagePhase = "commentary";
|
||||
*/
|
||||
export const CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS = 30_000;
|
||||
|
||||
/**
|
||||
* Reads `OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS` and falls back to
|
||||
* {@link CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS} whenever the variable is unset, empty,
|
||||
* unparseable, non-finite, or not positive — mirrors `resolveDirectHeadersTimeoutMs`
|
||||
* (`open-sse/utils/directResponseStartTimeout.ts`). Callers resolve this per request so a
|
||||
* changed environment variable takes effect without a restart.
|
||||
*/
|
||||
export function resolveChatGptSessionStreamOpenTimeoutMs(
|
||||
env: Record<string, string | undefined> = process.env
|
||||
): number {
|
||||
const raw = env.OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS;
|
||||
if (raw == null || raw.trim() === "") return CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS;
|
||||
const parsed = Number(raw);
|
||||
return Number.isFinite(parsed) && parsed > 0
|
||||
? Math.floor(parsed)
|
||||
: CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
/** Race marker for the gate deadline — distinguishable from any `IteratorResult`. */
|
||||
const GATE_TIMED_OUT = Symbol("chatgpt-session-gate-timeout");
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS,
|
||||
buildChatGptSessionCompletion,
|
||||
openChatGptSessionStream,
|
||||
resolveChatGptSessionStreamOpenTimeoutMs,
|
||||
} from "../../open-sse/executors/chatgpt-session/bridge.ts";
|
||||
import type { AdapterEvent } from "../../open-sse/vendor/codex-chatgpt-web/types.ts";
|
||||
|
||||
@@ -438,3 +439,51 @@ test("no event is lost to the deadline race", async () => {
|
||||
assert.match(text, /"content":"one"/);
|
||||
assert.match(text, /"content":"two"/);
|
||||
});
|
||||
|
||||
const STREAM_OPEN_TIMEOUT_ENV_VAR = "OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS";
|
||||
|
||||
function withStreamOpenTimeoutEnv(raw: string | undefined, run: () => void): void {
|
||||
const saved = process.env[STREAM_OPEN_TIMEOUT_ENV_VAR];
|
||||
if (raw === undefined) delete process.env[STREAM_OPEN_TIMEOUT_ENV_VAR];
|
||||
else process.env[STREAM_OPEN_TIMEOUT_ENV_VAR] = raw;
|
||||
try {
|
||||
run();
|
||||
} finally {
|
||||
if (saved === undefined) delete process.env[STREAM_OPEN_TIMEOUT_ENV_VAR];
|
||||
else process.env[STREAM_OPEN_TIMEOUT_ENV_VAR] = saved;
|
||||
}
|
||||
}
|
||||
|
||||
test("resolveChatGptSessionStreamOpenTimeoutMs falls back to the default when the env var is unset", () => {
|
||||
withStreamOpenTimeoutEnv(undefined, () => {
|
||||
assert.equal(
|
||||
resolveChatGptSessionStreamOpenTimeoutMs(),
|
||||
CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("resolveChatGptSessionStreamOpenTimeoutMs honors a valid positive integer", () => {
|
||||
withStreamOpenTimeoutEnv("45000", () => {
|
||||
assert.equal(resolveChatGptSessionStreamOpenTimeoutMs(), 45_000);
|
||||
});
|
||||
});
|
||||
|
||||
const INVALID_STREAM_OPEN_TIMEOUT_INPUTS: Array<[label: string, raw: string]> = [
|
||||
["an empty string", ""],
|
||||
["non-numeric text", "not-a-number"],
|
||||
["zero", "0"],
|
||||
["a negative number", "-10"],
|
||||
["a non-finite value", "Infinity"],
|
||||
];
|
||||
|
||||
for (const [label, raw] of INVALID_STREAM_OPEN_TIMEOUT_INPUTS) {
|
||||
test(`resolveChatGptSessionStreamOpenTimeoutMs falls back to the default for ${label}`, () => {
|
||||
withStreamOpenTimeoutEnv(raw, () => {
|
||||
assert.equal(
|
||||
resolveChatGptSessionStreamOpenTimeoutMs(),
|
||||
CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user