mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-24 08:02:14 +03:00
style(release): normalize post-freeze Codex paths (#8875)
This commit is contained in:
@@ -1,17 +1,15 @@
|
||||
import {
|
||||
bridgeToResponsesSSE,
|
||||
buildResponseJSON,
|
||||
} from "../vendor/codex-chatgpt-web/bridge.ts";
|
||||
import { bridgeToResponsesSSE, buildResponseJSON } from "../vendor/codex-chatgpt-web/bridge.ts";
|
||||
import { AsyncEventQueue } from "../vendor/codex-chatgpt-web/event-queue.ts";
|
||||
import type { AdapterEvent } from "../vendor/codex-chatgpt-web/types.ts";
|
||||
import { sanitizeErrorMessage } from "../utils/error.ts";
|
||||
import { PROVIDERS } from "../config/constants.ts";
|
||||
import { BaseExecutor, type ExecuteInput, type ExecutorExecuteResult } from "./base.ts";
|
||||
import { CodexAppServerClient, type CodexAppServerClientOptions } from "./codex/appServerClient.ts";
|
||||
import {
|
||||
CodexAppServerClient,
|
||||
type CodexAppServerClientOptions,
|
||||
} from "./codex/appServerClient.ts";
|
||||
import { resolveAppServerConfig, resolveThreadStartPolicy, type CodexAppServerConfig } from "./codex/appServerConfig.ts";
|
||||
resolveAppServerConfig,
|
||||
resolveThreadStartPolicy,
|
||||
type CodexAppServerConfig,
|
||||
} from "./codex/appServerConfig.ts";
|
||||
import {
|
||||
translateNotification,
|
||||
translateToolCall,
|
||||
@@ -283,9 +281,7 @@ export class CodexAppServerExecutor extends BaseExecutor {
|
||||
// Harness function tools are advertised via thread/start's `dynamicTools`,
|
||||
// which is an EXPERIMENTAL app-server field: opt into experimental API so
|
||||
// codex accepts it (and can emit the item/tool/call ServerRequest).
|
||||
capabilities: hasTools
|
||||
? { experimentalApi: true, requestAttestation: false }
|
||||
: null,
|
||||
capabilities: hasTools ? { experimentalApi: true, requestAttestation: false } : null,
|
||||
});
|
||||
const threadResult = (await client.request("thread/start", {
|
||||
cwd: config.cwd,
|
||||
@@ -339,7 +335,9 @@ export class CodexAppServerExecutor extends BaseExecutor {
|
||||
// request (the stateless-full-history contract every OmniRoute provider uses).
|
||||
client.onToolCall((_id, params, api) => {
|
||||
if (terminated) return;
|
||||
const toolParams = (params && typeof params === "object" ? params : {}) as DynamicToolCallLike;
|
||||
const toolParams = (
|
||||
params && typeof params === "object" ? params : {}
|
||||
) as DynamicToolCallLike;
|
||||
translateToolCall(toolParams, (event) => events.push(event));
|
||||
// Settle the app-server request so the socket does not stall. The router
|
||||
// does not have the tool output (the harness will produce it next turn),
|
||||
|
||||
@@ -53,8 +53,7 @@ const APPROVAL_REQUEST_METHODS = new Set<string>([
|
||||
]);
|
||||
|
||||
const ROUTER_APPROVAL_NOTE = "router: harness-controlled execution";
|
||||
const ROUTER_DENIAL_NOTE =
|
||||
"router: denied by default (set codexAppServerAutoApprove to opt in)";
|
||||
const ROUTER_DENIAL_NOTE = "router: denied by default (set codexAppServerAutoApprove to opt in)";
|
||||
|
||||
export interface CodexAppServerClientOptions {
|
||||
/** Transport factory. Defaults to the shared wreq-js websocket() when omitted. */
|
||||
@@ -137,7 +136,11 @@ export class CodexAppServerClient {
|
||||
}
|
||||
|
||||
/** Send a ClientRequest and resolve when its id-matched response arrives. */
|
||||
request<T = unknown>(method: string, params: unknown, timeoutMs = this.defaultTimeoutMs): Promise<T> {
|
||||
request<T = unknown>(
|
||||
method: string,
|
||||
params: unknown,
|
||||
timeoutMs = this.defaultTimeoutMs
|
||||
): Promise<T> {
|
||||
const id = this.nextId++;
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
if (!this.ws || this.closed) {
|
||||
@@ -216,7 +219,9 @@ export class CodexAppServerClient {
|
||||
this.pending.delete(id);
|
||||
if (msg.error) {
|
||||
const err = msg.error as { code?: unknown; message?: unknown };
|
||||
pending.reject(new Error(`${String(err.code ?? "error")}: ${String(err.message ?? "unknown")}`));
|
||||
pending.reject(
|
||||
new Error(`${String(err.code ?? "error")}: ${String(err.message ?? "unknown")}`)
|
||||
);
|
||||
} else {
|
||||
pending.resolve(msg.result);
|
||||
}
|
||||
|
||||
@@ -156,7 +156,10 @@ export function isLocalAppServerHost(hostname: string): boolean {
|
||||
* psd-sourced token may go anywhere: whoever wrote the psd already knows it.
|
||||
*/
|
||||
export function resolveAppServerConfig(psd: ProviderSpecificData): CodexAppServerConfig | null {
|
||||
const urlRes = firstStringWithSource(psd?.codexAppServerUrl, process.env.OMNIROUTE_CODEX_APPSERVER_WS);
|
||||
const urlRes = firstStringWithSource(
|
||||
psd?.codexAppServerUrl,
|
||||
process.env.OMNIROUTE_CODEX_APPSERVER_WS
|
||||
);
|
||||
if (!urlRes || !isWebSocketUrl(urlRes.value)) return null;
|
||||
|
||||
const tokenRes = resolveTokenWithSource(psd);
|
||||
@@ -174,13 +177,21 @@ export function resolveAppServerConfig(psd: ProviderSpecificData): CodexAppServe
|
||||
firstString(psd?.codexAppServerCwd, process.env.OMNIROUTE_CODEX_APPSERVER_CWD) ?? "/tmp";
|
||||
|
||||
const approvalPolicy =
|
||||
firstString(psd?.codexAppServerApprovalPolicy, process.env.OMNIROUTE_CODEX_APPSERVER_APPROVAL) ??
|
||||
undefined;
|
||||
firstString(
|
||||
psd?.codexAppServerApprovalPolicy,
|
||||
process.env.OMNIROUTE_CODEX_APPSERVER_APPROVAL
|
||||
) ?? undefined;
|
||||
const sandbox =
|
||||
firstString(psd?.codexAppServerSandbox, process.env.OMNIROUTE_CODEX_APPSERVER_SANDBOX) ??
|
||||
undefined;
|
||||
|
||||
return { url, token, cwd, ...(approvalPolicy ? { approvalPolicy } : {}), ...(sandbox ? { sandbox } : {}) };
|
||||
return {
|
||||
url,
|
||||
token,
|
||||
cwd,
|
||||
...(approvalPolicy ? { approvalPolicy } : {}),
|
||||
...(sandbox ? { sandbox } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,8 @@ export type CodexAppServerHealth = {
|
||||
export async function testCodexAppServerConnection(
|
||||
connection: any
|
||||
): Promise<CodexAppServerHealth | null> {
|
||||
const psd = (connection?.providerSpecificData as Record<string, unknown> | undefined) || undefined;
|
||||
const psd =
|
||||
(connection?.providerSpecificData as Record<string, unknown> | undefined) || undefined;
|
||||
// Fire the /readyz probe when EITHER (a) the connection opted into the
|
||||
// app-server transport via the per-connection flag (a `codex` provider
|
||||
// connection with codexTransport==="app-server"), OR (b) this is the
|
||||
@@ -56,9 +57,8 @@ export async function testCodexAppServerConnection(
|
||||
|
||||
// Dynamic import (not a static top-level import) so this executor-config module
|
||||
// stays behind the open-sse boundary the no-restricted-imports lint rule enforces.
|
||||
const { resolveAppServerConfig } = await import(
|
||||
"@omniroute/open-sse/executors/codex/appServerConfig.ts"
|
||||
);
|
||||
const { resolveAppServerConfig } =
|
||||
await import("@omniroute/open-sse/executors/codex/appServerConfig.ts");
|
||||
const config = resolveAppServerConfig(psd);
|
||||
if (!config) {
|
||||
// Also reached when the credential/URL binding refused (env token + remote
|
||||
@@ -75,7 +75,9 @@ export async function testCodexAppServerConnection(
|
||||
}
|
||||
|
||||
// ws://host:port → http://host:port/readyz ; wss:// → https://.
|
||||
const httpBase = config.url.replace(/^ws(s?):\/\//i, (_m, s) => `http${s}://`).replace(/\/+$/, "");
|
||||
const httpBase = config.url
|
||||
.replace(/^ws(s?):\/\//i, (_m, s) => `http${s}://`)
|
||||
.replace(/\/+$/, "");
|
||||
const readyzUrl = `${httpBase}/readyz`;
|
||||
|
||||
const controller = new AbortController();
|
||||
@@ -109,7 +111,11 @@ export async function testCodexAppServerConnection(
|
||||
import("@omniroute/open-sse/executors/codex/appServerAuthProbe.ts"),
|
||||
import("@omniroute/open-sse/executors/codex.ts"),
|
||||
]);
|
||||
authStatus = await probeCodexAppServerAuth(config, getCodexAppServerWebsocketTransport(), 8000);
|
||||
authStatus = await probeCodexAppServerAuth(
|
||||
config,
|
||||
getCodexAppServerWebsocketTransport(),
|
||||
8000
|
||||
);
|
||||
} catch (probeErr: any) {
|
||||
// If the auth probe itself fails to load/run, don't fail the whole health
|
||||
// check — the server IS reachable. Treat as unknown-but-reachable (valid).
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"tests/unit/auth-terminal-status.test.ts",
|
||||
"tests/unit/authz/discovery-routes-local-only.test.ts",
|
||||
"tests/unit/authz/oauth-autoimport-local-only.test.ts",
|
||||
"tests/unit/quota-exhaustion-cutoff-opencode.test.ts",
|
||||
"tests/unit/quota-exhaustion-cutoff-opencode.test.ts",
|
||||
"tests/unit/authz/route-guard-local-prefix.test.ts",
|
||||
"tests/unit/authz/route-guard-skills-collect.test.ts",
|
||||
"tests/unit/authz/route-guard-version-get-exemption.test.ts",
|
||||
|
||||
@@ -98,10 +98,7 @@ function makeExecuteInput(overrides: Partial<ExecuteInput> = {}): ExecuteInput {
|
||||
// ── Gating ──────────────────────────────────────────────────────────────────
|
||||
|
||||
test("isCodexAppServerRequired: true only when codexTransport==='app-server' + configured", () => {
|
||||
assert.equal(
|
||||
isCodexAppServerRequired({ providerSpecificData: { ...APP_SERVER_PSD } }),
|
||||
true
|
||||
);
|
||||
assert.equal(isCodexAppServerRequired({ providerSpecificData: { ...APP_SERVER_PSD } }), true);
|
||||
// wrong transport
|
||||
assert.equal(
|
||||
isCodexAppServerRequired({
|
||||
@@ -135,10 +132,7 @@ test("isCodexAppServerRequired: false when OMNIROUTE_CODEX_APP_SERVER_ENABLED=fa
|
||||
const prev = process.env.OMNIROUTE_CODEX_APP_SERVER_ENABLED;
|
||||
process.env.OMNIROUTE_CODEX_APP_SERVER_ENABLED = "false";
|
||||
try {
|
||||
assert.equal(
|
||||
isCodexAppServerRequired({ providerSpecificData: { ...APP_SERVER_PSD } }),
|
||||
false
|
||||
);
|
||||
assert.equal(isCodexAppServerRequired({ providerSpecificData: { ...APP_SERVER_PSD } }), false);
|
||||
} finally {
|
||||
if (prev === undefined) delete process.env.OMNIROUTE_CODEX_APP_SERVER_ENABLED;
|
||||
else process.env.OMNIROUTE_CODEX_APP_SERVER_ENABLED = prev;
|
||||
@@ -146,7 +140,10 @@ test("isCodexAppServerRequired: false when OMNIROUTE_CODEX_APP_SERVER_ENABLED=fa
|
||||
});
|
||||
|
||||
test("resolveAppServerConfig: env fallback + token-file, ws-scheme validation", () => {
|
||||
assert.equal(resolveAppServerConfig({ codexAppServerUrl: "http://x", codexAppServerToken: "t" }), null);
|
||||
assert.equal(
|
||||
resolveAppServerConfig({ codexAppServerUrl: "http://x", codexAppServerToken: "t" }),
|
||||
null
|
||||
);
|
||||
const cfg = resolveAppServerConfig({ ...APP_SERVER_PSD });
|
||||
assert.deepEqual(cfg, { url: "ws://ts-egress:1456", token: "deadbeef", cwd: "/tmp" });
|
||||
});
|
||||
@@ -157,14 +154,8 @@ test("translateNotification: maps deltas, done and error to AdapterEvents", () =
|
||||
const events: AdapterEvent[] = [];
|
||||
const push = (e: AdapterEvent) => events.push(e);
|
||||
|
||||
assert.equal(
|
||||
translateNotification("item/agentMessage/delta", { delta: "Hel" }, push),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
translateNotification("item/reasoning/textDelta", { delta: "think" }, push),
|
||||
false
|
||||
);
|
||||
assert.equal(translateNotification("item/agentMessage/delta", { delta: "Hel" }, push), false);
|
||||
assert.equal(translateNotification("item/reasoning/textDelta", { delta: "think" }, push), false);
|
||||
// terminal → returns true
|
||||
assert.equal(
|
||||
translateNotification(
|
||||
@@ -186,10 +177,8 @@ test("translateNotification: maps deltas, done and error to AdapterEvents", () =
|
||||
|
||||
test("translateNotification: error notification maps to error event (terminal)", () => {
|
||||
const events: AdapterEvent[] = [];
|
||||
const isTerminal = translateNotification(
|
||||
"error",
|
||||
{ error: { message: "boom" } },
|
||||
(e) => events.push(e)
|
||||
const isTerminal = translateNotification("error", { error: { message: "boom" } }, (e) =>
|
||||
events.push(e)
|
||||
);
|
||||
assert.equal(isTerminal, true);
|
||||
assert.equal(events[0].type, "error");
|
||||
@@ -343,7 +332,9 @@ async function runStreamingTurn(): Promise<{
|
||||
|
||||
test("CodexAppServerExecutor: streaming turn emits initialize → thread/start → turn/start in order", async () => {
|
||||
const { sent } = await runStreamingTurn();
|
||||
const methods = sent.filter((f) => typeof f.method === "string" && f.id != null).map((f) => f.method);
|
||||
const methods = sent
|
||||
.filter((f) => typeof f.method === "string" && f.id != null)
|
||||
.map((f) => f.method);
|
||||
const lifecycle = methods.filter(
|
||||
(m) => m === "initialize" || m === "thread/start" || m === "turn/start"
|
||||
);
|
||||
@@ -639,7 +630,13 @@ test("CodexAppServerExecutor: async post-turn/start completion does not close th
|
||||
const result = await Promise.race([
|
||||
executor.execute(makeExecuteInput({ stream: false })),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error("execute() hung: socket closed before async completion (BUG#3 regressed)")), 5000)
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error("execute() hung: socket closed before async completion (BUG#3 regressed)")
|
||||
),
|
||||
5000
|
||||
)
|
||||
),
|
||||
]);
|
||||
const response = "response" in result ? result.response : (result as Response);
|
||||
@@ -648,7 +645,11 @@ test("CodexAppServerExecutor: async post-turn/start completion does not close th
|
||||
status?: string;
|
||||
output?: Array<{ content?: Array<{ text?: string }> }>;
|
||||
};
|
||||
assert.equal(body.status, "completed", "the turn completed after the async terminal notification");
|
||||
assert.equal(
|
||||
body.status,
|
||||
"completed",
|
||||
"the turn completed after the async terminal notification"
|
||||
);
|
||||
const text = body.output?.[0]?.content?.[0]?.text ?? "";
|
||||
assert.equal(text, "ASYNC-OK", "the model output that arrived AFTER turn/start is present");
|
||||
});
|
||||
@@ -693,7 +694,10 @@ const AUTH_CONFIG = { url: "ws://ts-egress:1456", token: "deadbeef", cwd: "/tmp"
|
||||
|
||||
test("probeCodexAppServerAuth: account with email → authenticated", async () => {
|
||||
const fn = fakeAuthTransport({
|
||||
result: { account: { type: "chatgpt", email: "user@example.com", planType: "pro" }, requiresOpenaiAuth: true },
|
||||
result: {
|
||||
account: { type: "chatgpt", email: "user@example.com", planType: "pro" },
|
||||
requiresOpenaiAuth: true,
|
||||
},
|
||||
});
|
||||
const status = await probeCodexAppServerAuth(AUTH_CONFIG, fn, 3000);
|
||||
assert.equal(status.state, "authenticated");
|
||||
@@ -710,7 +714,9 @@ test("probeCodexAppServerAuth: no account → logged_out", async () => {
|
||||
});
|
||||
|
||||
test("probeCodexAppServerAuth: auth-error on account/read → logged_out", async () => {
|
||||
const fn = fakeAuthTransport({ error: { code: -32000, message: "AuthRequiredError: please login" } });
|
||||
const fn = fakeAuthTransport({
|
||||
error: { code: -32000, message: "AuthRequiredError: please login" },
|
||||
});
|
||||
const status = await probeCodexAppServerAuth(AUTH_CONFIG, fn, 3000);
|
||||
assert.equal(status.state, "logged_out");
|
||||
});
|
||||
@@ -836,9 +842,8 @@ test("resolveAppServerConfig: env URL + env token pairs regardless of host", ()
|
||||
// ── Health probe: redirect pinning + binding inheritance ────────────────────
|
||||
|
||||
test("testCodexAppServerConnection: readyz probe pins redirects (no token leak via 30x)", async () => {
|
||||
const { testCodexAppServerConnection } = await import(
|
||||
"../../src/app/api/providers/[id]/test/codexAppServerHealth.ts"
|
||||
);
|
||||
const { testCodexAppServerConnection } =
|
||||
await import("../../src/app/api/providers/[id]/test/codexAppServerHealth.ts");
|
||||
const originalFetch = globalThis.fetch;
|
||||
const seen: Array<{ url: string; init?: RequestInit }> = [];
|
||||
globalThis.fetch = (async (url: unknown, init?: RequestInit) => {
|
||||
@@ -864,9 +869,8 @@ test("testCodexAppServerConnection: readyz probe pins redirects (no token leak v
|
||||
});
|
||||
|
||||
test("testCodexAppServerConnection: env token + remote psd URL reports unconfigured, no network", async () => {
|
||||
const { testCodexAppServerConnection } = await import(
|
||||
"../../src/app/api/providers/[id]/test/codexAppServerHealth.ts"
|
||||
);
|
||||
const { testCodexAppServerConnection } =
|
||||
await import("../../src/app/api/providers/[id]/test/codexAppServerHealth.ts");
|
||||
const originalFetch = globalThis.fetch;
|
||||
let fetched = false;
|
||||
globalThis.fetch = (async () => {
|
||||
@@ -881,7 +885,10 @@ test("testCodexAppServerConnection: env token + remote psd URL reports unconfigu
|
||||
});
|
||||
assert.ok(result);
|
||||
assert.equal(result!.valid, false);
|
||||
assert.match(String((result!.diagnosis as { code?: string })?.code), /app_server_unconfigured/);
|
||||
assert.match(
|
||||
String((result!.diagnosis as { code?: string })?.code),
|
||||
/app_server_unconfigured/
|
||||
);
|
||||
});
|
||||
assert.equal(fetched, false, "binding refusal must happen before any network call");
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user