@openclaw/codex 2026.7.2-beta.5 → 2026.7.2-beta.7

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.
Files changed (42) hide show
  1. package/dist/{app-server-policy-BmDgseo_.js → app-server-policy-BSaO2JIT.js} +1 -1
  2. package/dist/{attempt-notifications-CMR2_fmR.js → attempt-notifications-AP-a-Q5k.js} +1 -1
  3. package/dist/{bounded-turn-FEbUXoYT.js → bounded-turn-D2ZAgZVz.js} +29 -22
  4. package/dist/{capabilities-BHRSI6Cn.js → capabilities-BFDTVYr4.js} +1 -1
  5. package/dist/cli-metadata.js +1 -1
  6. package/dist/{command-formatters-ico4ZFnk.js → command-formatters-C336eRVS.js} +2 -2
  7. package/dist/{command-handlers-CRmnwI2N.js → command-handlers-DAHUSry9.js} +12 -12
  8. package/dist/{compact-dEN49UH6.js → compact-D_uvHurS.js} +5 -5
  9. package/dist/{computer-use-BjGylO-E.js → computer-use-CYEc8U3w.js} +111 -113
  10. package/dist/{conversation-control-DE7d9DOj.js → conversation-control-xsIeXbo4.js} +6 -6
  11. package/dist/doctor-contract-api.js +1 -1
  12. package/dist/{dynamic-tools-LIt9ojep.js → dynamic-tools-D2gYkSBO.js} +438 -51
  13. package/dist/effective-mcp-catalog-Bbd98Inw.js +114 -0
  14. package/dist/{event-projector-usage-tvgb1NCX.js → event-projector-usage-Xp4AlR2k.js} +20 -3
  15. package/dist/harness.js +13 -9
  16. package/dist/index.js +131 -108
  17. package/dist/media-understanding-provider.js +1 -1
  18. package/dist/{models-BA01NEez.js → models-BBMKU4jA.js} +2 -2
  19. package/dist/{notification-correlation-Bj_Ak_jI.js → notification-correlation-CU6_AnOb.js} +3 -3
  20. package/dist/{plugin-app-cache-key-DSXX-rXO.js → plugin-app-cache-key-DztPafOA.js} +132 -39
  21. package/dist/{rate-limits-S_PXs0ul.js → rate-limits-CU-gVCCS.js} +1 -1
  22. package/dist/{request-CXpPennK.js → request-qKh331yu.js} +3 -3
  23. package/dist/{run-attempt-C5GTPJOE.js → run-attempt-CY9WlDLc.js} +677 -199
  24. package/dist/{runtime-artifact-C2ITXdhM.js → runtime-artifact-CmtG_t1L.js} +90 -24
  25. package/dist/{sandbox-guard-D1Wky__S.js → sandbox-guard-NuhpXmPB.js} +4 -0
  26. package/dist/{session-binding-DlVQ-Pxs.js → session-binding-BXpo9ymA.js} +60 -7
  27. package/dist/{session-catalog-CP2oCcEb.js → session-catalog-DyOgR4WH.js} +268 -44
  28. package/dist/{session-cli-CqZ0Fp2a.js → session-cli-2PZaFLRh.js} +1 -1
  29. package/dist/{session-upstream-marker-QE-m5QFG.js → session-upstream-marker-B6U7tLw5.js} +1 -1
  30. package/dist/{settled-turn-finalizer-CA_2QXIE.js → settled-turn-finalizer-CXY2Z9dT.js} +19 -6
  31. package/dist/{shared-client-BVKE40c-.js → shared-client-DTbvH-XV.js} +166 -49
  32. package/dist/{side-question-CO12sHxo.js → side-question-_ulBk1AL.js} +10 -10
  33. package/dist/{thread-lifecycle-DRg_FdzC.js → thread-lifecycle-DF53ogiA.js} +968 -214
  34. package/dist/{protocol-validators-Dm4eXZmt.js → transcript-mirror-DzsdNCf5.js} +589 -2
  35. package/dist/{upstream-session-fork-_Vrz4WJr.js → upstream-session-fork-Dy4Xvuoj.js} +3 -3
  36. package/dist/{usage-UpVbdX5G.js → usage-CQ6GhD5-.js} +3 -3
  37. package/dist/{web-search-provider.runtime-CaQMYDbp.js → web-search-provider.runtime-CCFhg2z5.js} +3 -3
  38. package/package.json +6 -7
  39. package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
  40. package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
  41. package/dist/realtime-voice-api.js +0 -2
  42. package/dist/transcript-mirror-uKA-A4tB.js +0 -550
@@ -1,6 +1,6 @@
1
- import { V as isJsonObject } from "./shared-client-BVKE40c-.js";
2
- import { m as buildCodexNativeWebSearchThreadConfig } from "./thread-lifecycle-DRg_FdzC.js";
3
- import { t as runBoundedCodexAppServerTurn } from "./bounded-turn-FEbUXoYT.js";
1
+ import { W as isJsonObject } from "./shared-client-DTbvH-XV.js";
2
+ import { h as buildCodexNativeWebSearchThreadConfig } from "./thread-lifecycle-DF53ogiA.js";
3
+ import { t as runBoundedCodexAppServerTurn } from "./bounded-turn-D2ZAgZVz.js";
4
4
  import { readStringParam, resolveSearchTimeoutSeconds, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search";
5
5
  //#region extensions/codex/src/web-search-provider.runtime.ts
6
6
  async function executeCodexWebSearchProviderTool(ctx, args, executionContext, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/codex",
3
- "version": "2026.7.2-beta.5",
3
+ "version": "2026.7.2-beta.7",
4
4
  "description": "OpenClaw Codex app-server harness and native session supervision plugin.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,9 +8,8 @@
8
8
  },
9
9
  "type": "module",
10
10
  "dependencies": {
11
- "@openai/codex": "0.145.0",
12
- "semver": "7.8.5",
13
- "smol-toml": "1.7.0",
11
+ "@openai/codex": "0.146.0",
12
+ "smol-toml": "1.7.1",
14
13
  "typebox": "1.3.6",
15
14
  "ws": "8.21.1",
16
15
  "zod": "4.4.3"
@@ -36,10 +35,10 @@
36
35
  ]
37
36
  },
38
37
  "compat": {
39
- "pluginApi": ">=2026.7.2-beta.5"
38
+ "pluginApi": ">=2026.7.2-beta.7"
40
39
  },
41
40
  "build": {
42
- "openclawVersion": "2026.7.2-beta.5"
41
+ "openclawVersion": "2026.7.2-beta.7"
43
42
  },
44
43
  "release": {
45
44
  "publishToClawHub": true,
@@ -59,7 +58,7 @@
59
58
  "README.md"
60
59
  ],
61
60
  "peerDependencies": {
62
- "openclaw": ">=2026.7.2-beta.5"
61
+ "openclaw": ">=2026.7.2-beta.7"
63
62
  },
64
63
  "peerDependenciesMeta": {
65
64
  "openclaw": {
@@ -1,127 +0,0 @@
1
- import { f as retireSharedCodexAppServerClientIfCurrent, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, t as clearSharedCodexAppServerClientIfCurrent } from "./shared-client-BVKE40c-.js";
2
- import { embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime";
3
- //#region extensions/codex/src/app-server/attempt-client-cleanup.ts
4
- /**
5
- * Best-effort cleanup helpers for Codex app-server startup attempts and turns.
6
- */
7
- /** Timeout for best-effort app-server turn interruption during cleanup. */
8
- const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
9
- /** Timeout for best-effort thread unsubscribe during cleanup. */
10
- const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
11
- /** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
12
- var CodexAppServerUnsafeSubscriptionError = class extends Error {
13
- constructor(message, options) {
14
- super(message, options);
15
- this.name = "CodexAppServerUnsafeSubscriptionError";
16
- }
17
- };
18
- function isCodexAppServerUnsafeSubscriptionError(error) {
19
- return error instanceof CodexAppServerUnsafeSubscriptionError;
20
- }
21
- /** Asserts Codex resumed the exact thread this attempt subscribed to. */
22
- function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
23
- if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
24
- }
25
- async function closeClientAndWaitIfAvailable(client) {
26
- const closeable = client;
27
- if (typeof closeable.closeAndWait === "function") {
28
- await closeable.closeAndWait();
29
- return;
30
- }
31
- closeable.close?.();
32
- }
33
- async function closeCodexStartupClientBestEffort(client) {
34
- if (!client) return;
35
- const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
36
- if (unclaimedSharedClient.closed) {
37
- await closeClientAndWaitIfAvailable(client);
38
- return;
39
- }
40
- if (unclaimedSharedClient.found) {
41
- if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
42
- return;
43
- }
44
- const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
45
- if (retiredSharedClient) {
46
- if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
47
- return;
48
- }
49
- if (clearSharedCodexAppServerClientIfCurrent(client)) {
50
- await closeClientAndWaitIfAvailable(client);
51
- return;
52
- }
53
- await closeClientAndWaitIfAvailable(client);
54
- }
55
- /** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
56
- function interruptCodexTurnBestEffort(client, params) {
57
- interruptCodexTurnAndWaitBestEffort(client, params);
58
- }
59
- /** Sends a bounded turn interrupt and waits for Codex to confirm terminal abort handling. */
60
- async function interruptCodexTurnAndWaitBestEffort(client, params) {
61
- const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
62
- const requestParams = {
63
- threadId: params.threadId,
64
- turnId: params.turnId
65
- };
66
- try {
67
- await (requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams));
68
- } catch (error) {
69
- embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
70
- }
71
- }
72
- /** Unsubscribes from a thread while swallowing cleanup-only failures. */
73
- async function unsubscribeCodexThreadBestEffort(client, params) {
74
- try {
75
- await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
76
- return true;
77
- } catch (error) {
78
- embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
79
- threadId: params.threadId,
80
- error
81
- });
82
- return false;
83
- }
84
- }
85
- /**
86
- * Retires the shared client after a timed-out turn so later runs do not reuse a
87
- * potentially wedged app-server connection.
88
- */
89
- async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
90
- const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client, { failActiveLeases: params.suspectPhysicalClient });
91
- const detachedSharedClient = Boolean(retiredSharedClient);
92
- if (!(params.suspectPhysicalClient && (retiredSharedClient?.closed ?? false))) {
93
- interruptCodexTurnBestEffort(client, {
94
- threadId: params.threadId,
95
- turnId: params.turnId,
96
- timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
97
- });
98
- await unsubscribeCodexThreadBestEffort(client, {
99
- threadId: params.threadId,
100
- timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
101
- });
102
- }
103
- let closedClient = retiredSharedClient?.closed ?? false;
104
- if (!detachedSharedClient) {
105
- const close = client.close;
106
- if (typeof close === "function") try {
107
- close.call(client);
108
- closedClient = true;
109
- } catch (error) {
110
- embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
111
- threadId: params.threadId,
112
- turnId: params.turnId,
113
- error
114
- });
115
- }
116
- }
117
- embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
118
- threadId: params.threadId,
119
- turnId: params.turnId,
120
- reason: params.reason,
121
- detachedSharedClient,
122
- closedClient,
123
- activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
124
- });
125
- }
126
- //#endregion
127
- export { closeCodexStartupClientBestEffort as a, isCodexAppServerUnsafeSubscriptionError as c, assertCodexThreadResumeSubscription as i, retireCodexAppServerClientAfterTimedOutTurn as l, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as n, interruptCodexTurnAndWaitBestEffort as o, CodexAppServerUnsafeSubscriptionError as r, interruptCodexTurnBestEffort as s, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as t, unsubscribeCodexThreadBestEffort as u };
@@ -1,494 +0,0 @@
1
- import { a as getLeasedSharedCodexAppServerClient, l as releaseLeasedSharedCodexAppServerClient, o as getSharedCodexAppServerClient } from "./shared-client-BVKE40c-.js";
2
- import { T as readCodexPluginConfig, p as resolveCodexAppServerRuntimeOptions } from "./session-binding-DlVQ-Pxs.js";
3
- import { n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, u as unsubscribeCodexThreadBestEffort } from "./attempt-client-cleanup-Ia52yRWc.js";
4
- import { i as assertCodexThreadStartResponse } from "./protocol-validators-Dm4eXZmt.js";
5
- import { randomBytes } from "node:crypto";
6
- import { resolveAgentDir, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
7
- import { readRequestBodyWithLimit } from "openclaw/plugin-sdk/webhook-request-guards";
8
- //#region extensions/codex/src/realtime-browser-session.ts
9
- const CODEX_REALTIME_OFFER_PATH = "/plugins/codex/realtime/calls";
10
- const CODEX_REALTIME_PENDING_TTL_MS = 6e4;
11
- const CODEX_REALTIME_SESSION_TTL_MS = 30 * 6e4;
12
- const CODEX_REALTIME_MAX_SESSIONS = 8;
13
- const CODEX_REALTIME_MAX_SDP_BYTES = 256 * 1024;
14
- const CODEX_REALTIME_START_TIMEOUT_MS = 6e4;
15
- const CODEX_REALTIME_PROBE_COOLDOWN_MS = 1e3;
16
- function createResponseDeliveryWaiter(res, onDelivered) {
17
- let settle;
18
- const result = new Promise((resolve) => {
19
- settle = (delivered) => {
20
- res.removeListener("finish", onFinish);
21
- res.removeListener("close", onClose);
22
- resolve(delivered);
23
- };
24
- });
25
- const onFinish = () => {
26
- onDelivered();
27
- settle(true);
28
- };
29
- const onClose = () => settle(false);
30
- res.once("finish", onFinish);
31
- res.once("close", onClose);
32
- return {
33
- result,
34
- cancel: () => settle(false)
35
- };
36
- }
37
- function respondText(res, statusCode, body) {
38
- res.statusCode = statusCode;
39
- res.setHeader("cache-control", "no-store");
40
- res.setHeader("content-type", "text/plain; charset=utf-8");
41
- res.setHeader("x-content-type-options", "nosniff");
42
- res.end(body);
43
- }
44
- function resolveConfiguredControlUiOrigin(req, cfg) {
45
- const rawOrigin = typeof req.headers.origin === "string" ? req.headers.origin.trim() : "";
46
- if (!rawOrigin) return;
47
- let origin;
48
- try {
49
- const parsed = new URL(rawOrigin);
50
- if (parsed.origin !== rawOrigin || parsed.username || parsed.password) return;
51
- origin = parsed.origin;
52
- } catch {
53
- return;
54
- }
55
- return (cfg?.gateway?.controlUi?.allowedOrigins ?? []).some((candidate) => {
56
- const normalized = candidate.trim().toLowerCase();
57
- return normalized === "*" || normalized === origin;
58
- }) ? origin : void 0;
59
- }
60
- function applyRealtimeOfferCorsHeaders(req, res, cfg) {
61
- if (!req.headers.origin) return true;
62
- const origin = resolveConfiguredControlUiOrigin(req, cfg);
63
- if (!origin) return false;
64
- res.setHeader("Access-Control-Allow-Origin", origin);
65
- res.setHeader("Vary", "Origin");
66
- return true;
67
- }
68
- function readBearerToken(req) {
69
- return ((req.headers.authorization?.trim())?.match(/^Bearer\s+([^\s]+)$/i))?.[1];
70
- }
71
- function readNotificationParams(value) {
72
- return value && typeof value === "object" ? value : {};
73
- }
74
- function buildCodexRealtimeThreadStartParams(params) {
75
- return {
76
- cwd: params.cwd,
77
- ephemeral: true,
78
- approvalPolicy: "never",
79
- sandbox: "read-only",
80
- config: { "features.realtime_conversation": true }
81
- };
82
- }
83
- function buildCodexRealtimeStartParams(params) {
84
- const initialItems = [...params.developerInstructions ? [{
85
- role: "developer",
86
- text: params.developerInstructions
87
- }] : [], ...params.initialItems ?? []];
88
- return {
89
- threadId: params.threadId,
90
- outputModality: "audio",
91
- transport: {
92
- type: "webrtc",
93
- sdp: params.sdp
94
- },
95
- version: "v3",
96
- includeStartupContext: true,
97
- ...params.voice ? { voice: params.voice } : {},
98
- ...initialItems.length > 0 ? { initialItems } : {}
99
- };
100
- }
101
- function waitForRealtimeSdpAnswer(answerPromise, signal) {
102
- return new Promise((resolve, reject) => {
103
- let settled = false;
104
- const finish = (result) => {
105
- if (settled) return;
106
- settled = true;
107
- clearTimeout(timeout);
108
- signal.removeEventListener("abort", onAbort);
109
- if ("answer" in result) resolve(result.answer);
110
- else reject(result.error);
111
- };
112
- const onAbort = () => finish({ error: signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Codex realtime session stopped during startup") });
113
- const timeout = setTimeout(() => finish({ error: /* @__PURE__ */ new Error("Codex realtime SDP answer timed out") }), CODEX_REALTIME_START_TIMEOUT_MS);
114
- timeout.unref?.();
115
- signal.addEventListener("abort", onAbort, { once: true });
116
- if (signal.aborted) {
117
- onAbort();
118
- return;
119
- }
120
- answerPromise.then((answer) => finish({ answer }), (error) => finish({ error: error instanceof Error ? error : /* @__PURE__ */ new Error("Codex realtime failed") }));
121
- });
122
- }
123
- function createCodexRealtimeBrowserSessionBroker(params) {
124
- const pendingOffers = /* @__PURE__ */ new Map();
125
- const reservations = /* @__PURE__ */ new Set();
126
- const activeSessions = /* @__PURE__ */ new Set();
127
- const inFlightHandlers = /* @__PURE__ */ new Set();
128
- const readyClients = /* @__PURE__ */ new Set();
129
- const shutdownController = new AbortController();
130
- let cleanedUp = false;
131
- let probePromise;
132
- let nextProbeAt = 0;
133
- const closeSession = async (session) => {
134
- if (!activeSessions.delete(session)) return;
135
- clearTimeout(session.timer);
136
- reservations.delete(session.reservationToken);
137
- session.disposeNotificationHandler();
138
- try {
139
- await session.client.request("thread/realtime/stop", { threadId: session.threadId }, { timeoutMs: 2e3 });
140
- } catch {}
141
- await unsubscribeCodexThreadBestEffort(session.client, {
142
- threadId: session.threadId,
143
- timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
144
- });
145
- releaseLeasedSharedCodexAppServerClient(session.client);
146
- };
147
- const resolveSubscriptionClientOptions = (request) => {
148
- const pluginConfig = readCodexPluginConfig(params.getPluginConfig());
149
- const agentDir = request.agentId && request.cfg ? resolveAgentDir(request.cfg, request.agentId) : void 0;
150
- return {
151
- startOptions: resolveCodexAppServerRuntimeOptions({
152
- pluginConfig,
153
- config: request.cfg,
154
- agentDir
155
- }).start,
156
- pluginConfig,
157
- config: request.cfg,
158
- agentDir,
159
- authRequirement: "subscription",
160
- timeoutMs: CODEX_REALTIME_START_TIMEOUT_MS
161
- };
162
- };
163
- const ensureSubscriptionRuntime = async (request) => {
164
- const client = await getSharedCodexAppServerClient({
165
- ...resolveSubscriptionClientOptions(request),
166
- abandonSignal: shutdownController.signal
167
- });
168
- if (!readyClients.has(client)) {
169
- readyClients.add(client);
170
- client.addCloseHandler((closedClient) => {
171
- readyClients.delete(closedClient);
172
- Promise.allSettled([...activeSessions].filter((session) => session.client === closedClient).map((session) => closeSession(session)));
173
- nextProbeAt = 0;
174
- requestProbe();
175
- });
176
- }
177
- };
178
- const runProbe = () => {
179
- if (probePromise) return probePromise;
180
- const cfg = params.getConfig();
181
- nextProbeAt = Date.now() + CODEX_REALTIME_PROBE_COOLDOWN_MS;
182
- const running = ensureSubscriptionRuntime({
183
- cfg,
184
- ...cfg ? { agentId: resolveDefaultAgentId(cfg) } : {}
185
- }).finally(() => {
186
- if (probePromise === running) probePromise = void 0;
187
- });
188
- probePromise = running;
189
- return running;
190
- };
191
- function requestProbe() {
192
- if (cleanedUp || shutdownController.signal.aborted || probePromise || Date.now() < nextProbeAt) return;
193
- runProbe().catch(() => void 0);
194
- }
195
- const warmup = async () => {
196
- await runProbe();
197
- };
198
- const prunePendingOffers = () => {
199
- const now = Date.now();
200
- for (const [token, offer] of pendingOffers) if (offer.expiresAt <= now) {
201
- pendingOffers.delete(token);
202
- reservations.delete(token);
203
- }
204
- };
205
- const broker = {
206
- capabilities: {
207
- transports: ["webrtc"],
208
- handlesAgentConsult: true,
209
- supportsToolCalls: false,
210
- supportsVideoFrames: false
211
- },
212
- isConfigured: () => {
213
- if (cleanedUp || shutdownController.signal.aborted) return false;
214
- if (readyClients.size > 0) return true;
215
- requestProbe();
216
- return false;
217
- },
218
- createBrowserSession: async (request) => {
219
- if (cleanedUp || shutdownController.signal.aborted) throw new Error("Codex OAuth realtime is stopping; restart Gateway and try again");
220
- await ensureSubscriptionRuntime(request);
221
- if (cleanedUp || shutdownController.signal.aborted) throw new Error("Codex OAuth realtime is stopping; restart Gateway and try again");
222
- prunePendingOffers();
223
- if (reservations.size >= CODEX_REALTIME_MAX_SESSIONS) throw new Error("Too many concurrent Codex OAuth realtime sessions; try again in a minute");
224
- const token = randomBytes(32).toString("base64url");
225
- const expiresAt = Date.now() + CODEX_REALTIME_PENDING_TTL_MS;
226
- reservations.add(token);
227
- pendingOffers.set(token, {
228
- expiresAt,
229
- request
230
- });
231
- const voice = request.voice?.trim() || void 0;
232
- return {
233
- provider: "openai",
234
- transport: "webrtc",
235
- clientSecret: token,
236
- offerUrl: CODEX_REALTIME_OFFER_PATH,
237
- ...voice ? { voice } : {},
238
- expiresAt
239
- };
240
- },
241
- cancelBrowserSession: (session) => {
242
- if (session.transport !== "webrtc") return;
243
- pendingOffers.delete(session.clientSecret);
244
- reservations.delete(session.clientSecret);
245
- }
246
- };
247
- const handleOffer = async (req, res) => {
248
- const corsAllowed = applyRealtimeOfferCorsHeaders(req, res, params.getConfig());
249
- if (req.method === "OPTIONS") {
250
- if (!corsAllowed) {
251
- respondText(res, 403, "Origin not allowed");
252
- return true;
253
- }
254
- res.statusCode = 204;
255
- res.setHeader("cache-control", "no-store");
256
- res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
257
- res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
258
- res.setHeader("Vary", "Origin, Access-Control-Request-Method, Access-Control-Request-Headers");
259
- if (req.headers["access-control-request-private-network"] === "true") res.setHeader("Access-Control-Allow-Private-Network", "true");
260
- res.setHeader("Access-Control-Max-Age", "600");
261
- res.end();
262
- return true;
263
- }
264
- if (req.method !== "POST") {
265
- respondText(res, 405, "Method not allowed");
266
- return true;
267
- }
268
- if (!req.headers["content-type"]?.toLowerCase().startsWith("application/sdp")) {
269
- respondText(res, 415, "Expected application/sdp");
270
- return true;
271
- }
272
- prunePendingOffers();
273
- const token = readBearerToken(req);
274
- const offer = token ? pendingOffers.get(token) : void 0;
275
- if (!token || !offer || offer.expiresAt <= Date.now()) {
276
- respondText(res, 401, "Invalid or expired realtime session token");
277
- return true;
278
- }
279
- pendingOffers.delete(token);
280
- const requestController = new AbortController();
281
- const abortFromBrowser = () => {
282
- requestController.abort(/* @__PURE__ */ new Error("Browser realtime offer request closed"));
283
- };
284
- req.once("aborted", abortFromBrowser);
285
- res.once("close", abortFromBrowser);
286
- const detachBrowserAbort = () => {
287
- req.removeListener("aborted", abortFromBrowser);
288
- res.removeListener("close", abortFromBrowser);
289
- };
290
- const lifecycleSignal = AbortSignal.any([shutdownController.signal, requestController.signal]);
291
- let client;
292
- let session;
293
- let threadId;
294
- let reservationTransferred = false;
295
- let responseDeliveryWaiter;
296
- try {
297
- const sdp = await readRequestBodyWithLimit(req, {
298
- maxBytes: CODEX_REALTIME_MAX_SDP_BYTES,
299
- timeoutMs: 15e3
300
- });
301
- if (!sdp.trim()) {
302
- respondText(res, 400, "SDP offer is required");
303
- return true;
304
- }
305
- if (lifecycleSignal.aborted) throw new Error("Codex realtime session stopped during startup");
306
- client = await getLeasedSharedCodexAppServerClient({
307
- ...resolveSubscriptionClientOptions(offer.request),
308
- abandonSignal: lifecycleSignal
309
- });
310
- threadId = assertCodexThreadStartResponse(await client.request("thread/start", buildCodexRealtimeThreadStartParams({ cwd: offer.request.workspaceDir ?? process.cwd() }), {
311
- timeoutMs: CODEX_REALTIME_START_TIMEOUT_MS,
312
- signal: lifecycleSignal
313
- })).thread.id;
314
- if (lifecycleSignal.aborted) throw new Error("Codex realtime session stopped during startup");
315
- let resolveSdp;
316
- let rejectSdp;
317
- const answerPromise = new Promise((resolve, reject) => {
318
- resolveSdp = resolve;
319
- rejectSdp = reject;
320
- });
321
- const startupController = new AbortController();
322
- const startupSignal = AbortSignal.any([lifecycleSignal, startupController.signal]);
323
- let startupComplete = false;
324
- const stopStartup = (error) => {
325
- if (!startupController.signal.aborted) startupController.abort(error);
326
- };
327
- const closeAfterStartup = () => {
328
- if (startupComplete && session) closeSession(session);
329
- };
330
- const disposeNotificationHandler = client.addNotificationHandler((notification) => {
331
- const notificationParams = readNotificationParams(notification.params);
332
- if (notificationParams.threadId !== threadId) return;
333
- if (notification.method === "thread/realtime/sdp") {
334
- if (typeof notificationParams.sdp === "string" && notificationParams.sdp.trim()) resolveSdp(notificationParams.sdp);
335
- else rejectSdp(/* @__PURE__ */ new Error("Codex returned an empty realtime SDP answer"));
336
- return;
337
- }
338
- if (notification.method === "thread/realtime/error") {
339
- const error = new Error(typeof notificationParams.message === "string" ? notificationParams.message : "Codex realtime session failed");
340
- rejectSdp(error);
341
- stopStartup(error);
342
- closeAfterStartup();
343
- return;
344
- }
345
- if (notification.method === "thread/realtime/closed") {
346
- const error = /* @__PURE__ */ new Error("Codex realtime session closed before returning an SDP answer");
347
- rejectSdp(error);
348
- stopStartup(error);
349
- closeAfterStartup();
350
- }
351
- });
352
- const timer = setTimeout(() => {
353
- if (session) closeSession(session);
354
- }, CODEX_REALTIME_SESSION_TTL_MS);
355
- timer.unref?.();
356
- session = {
357
- client,
358
- reservationToken: token,
359
- threadId,
360
- timer,
361
- disposeNotificationHandler
362
- };
363
- reservationTransferred = true;
364
- activeSessions.add(session);
365
- const answerResultPromise = waitForRealtimeSdpAnswer(answerPromise, startupSignal).then((answer) => ({
366
- ok: true,
367
- answer
368
- }), (error) => ({
369
- ok: false,
370
- error: error instanceof Error ? error : /* @__PURE__ */ new Error("Codex realtime session failed")
371
- }));
372
- try {
373
- await client.request("thread/realtime/start", buildCodexRealtimeStartParams({
374
- threadId,
375
- sdp,
376
- developerInstructions: offer.request.instructions?.trim() || void 0,
377
- voice: offer.request.voice?.trim() || void 0,
378
- initialItems: offer.request.initialItems
379
- }), {
380
- timeoutMs: CODEX_REALTIME_START_TIMEOUT_MS,
381
- signal: startupSignal
382
- });
383
- } catch (error) {
384
- if (startupController.signal.aborted) {
385
- const answerResult = await answerResultPromise;
386
- if (!answerResult.ok) throw answerResult.error;
387
- throw startupController.signal.reason instanceof Error ? startupController.signal.reason : /* @__PURE__ */ new Error("Codex realtime session stopped during startup");
388
- }
389
- stopStartup(error instanceof Error ? error : /* @__PURE__ */ new Error("Codex realtime session failed to start"));
390
- throw error;
391
- }
392
- const answerResult = await answerResultPromise;
393
- if (!answerResult.ok) throw answerResult.error;
394
- if (startupSignal.aborted) throw startupSignal.reason instanceof Error ? startupSignal.reason : /* @__PURE__ */ new Error("Codex realtime session stopped during startup");
395
- startupComplete = true;
396
- responseDeliveryWaiter = createResponseDeliveryWaiter(res, detachBrowserAbort);
397
- res.statusCode = 200;
398
- res.setHeader("cache-control", "no-store");
399
- res.setHeader("content-type", "application/sdp");
400
- res.setHeader("x-content-type-options", "nosniff");
401
- res.end(answerResult.answer);
402
- const delivered = await responseDeliveryWaiter.result;
403
- responseDeliveryWaiter = void 0;
404
- if (!delivered || lifecycleSignal.aborted) await closeSession(session);
405
- return true;
406
- } catch (error) {
407
- if (session) await closeSession(session);
408
- else if (client) {
409
- if (threadId) await unsubscribeCodexThreadBestEffort(client, {
410
- threadId,
411
- timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
412
- });
413
- releaseLeasedSharedCodexAppServerClient(client);
414
- }
415
- if (requestController.signal.aborted) return true;
416
- respondText(res, 502, error instanceof Error ? error.message : "Codex realtime session failed");
417
- return true;
418
- } finally {
419
- responseDeliveryWaiter?.cancel();
420
- detachBrowserAbort();
421
- if (!reservationTransferred) reservations.delete(token);
422
- }
423
- };
424
- const trackedHandleOffer = (req, res) => {
425
- const handling = handleOffer(req, res);
426
- inFlightHandlers.add(handling);
427
- return handling.finally(() => {
428
- inFlightHandlers.delete(handling);
429
- });
430
- };
431
- const handler = trackedHandleOffer;
432
- const cleanup = async () => {
433
- if (cleanedUp) return;
434
- cleanedUp = true;
435
- shutdownController.abort();
436
- pendingOffers.clear();
437
- await Promise.all([...activeSessions].map((session) => closeSession(session)));
438
- await Promise.allSettled(inFlightHandlers);
439
- reservations.clear();
440
- };
441
- return {
442
- broker,
443
- handler,
444
- warmup,
445
- cleanup
446
- };
447
- }
448
- //#endregion
449
- //#region extensions/codex/realtime-voice-api.ts
450
- /**
451
- * Bundled Codex realtime integration shared with the bundled OpenAI provider.
452
- *
453
- * This artifact keeps the HTTP offer route and the provider fallback on one
454
- * process-owned runtime without adding a general Plugin SDK registration API.
455
- */
456
- const CODEX_REALTIME_GLOBAL_STATE = Symbol.for("openclaw.codex.realtime-voice.v1");
457
- function getGlobalState() {
458
- const root = globalThis;
459
- const state = root[CODEX_REALTIME_GLOBAL_STATE] ??= {
460
- version: 1,
461
- sources: /* @__PURE__ */ new Map()
462
- };
463
- state.sources ??= /* @__PURE__ */ new Map();
464
- return state;
465
- }
466
- function configureCodexRealtimeBrowserSession(params) {
467
- const state = getGlobalState();
468
- const leaseId = Symbol("codex-realtime-registration");
469
- state.sources.set(leaseId, params);
470
- if (state.runtime) return createRuntimeLease(state, state.runtime, leaseId);
471
- const resolveCurrentSource = () => Array.from(state.sources.values()).at(-1);
472
- const created = createCodexRealtimeBrowserSessionBroker({
473
- getConfig: () => resolveCurrentSource()?.getConfig(),
474
- getPluginConfig: () => resolveCurrentSource()?.getPluginConfig()
475
- });
476
- state.runtime = created;
477
- return createRuntimeLease(state, created, leaseId);
478
- }
479
- function createRuntimeLease(state, runtime, leaseId) {
480
- let released = false;
481
- return {
482
- ...runtime,
483
- cleanup: async () => {
484
- if (released) return;
485
- released = true;
486
- state.sources.delete(leaseId);
487
- if (state.runtime !== runtime || state.sources.size > 0) return;
488
- state.runtime = void 0;
489
- await runtime.cleanup();
490
- }
491
- };
492
- }
493
- //#endregion
494
- export { CODEX_REALTIME_OFFER_PATH as n, configureCodexRealtimeBrowserSession as t };
@@ -1,2 +0,0 @@
1
- import { n as CODEX_REALTIME_OFFER_PATH, t as configureCodexRealtimeBrowserSession } from "./realtime-voice-api-BPcA1uN9.js";
2
- export { CODEX_REALTIME_OFFER_PATH, configureCodexRealtimeBrowserSession };