codex-grok-bridge 1.0.3 → 1.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-grok-bridge",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Run Grok 4.6 as the model inside Codex, with Codex still owning tools, permissions, history and MCP. Uses the grok login session, not an API key.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,7 +9,8 @@
9
9
  "node": ">=22"
10
10
  },
11
11
  "os": [
12
- "darwin"
12
+ "darwin",
13
+ "linux"
13
14
  ],
14
15
  "bin": {
15
16
  "codex-grok": "scripts/codex-grok.mjs"
@@ -38,6 +39,7 @@
38
39
  "scripts/launch-desktop.mjs",
39
40
  "Open Codex with Grok.command",
40
41
  "README.md",
42
+ "CHANGELOG.md",
41
43
  "LICENSE"
42
44
  ],
43
45
  "scripts": {
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
3
  import { startRuntime } from "../src/runtime.mjs";
4
+ import { resolveCodexBinary } from "../src/paths.mjs";
4
5
  const runtime = await startRuntime();
5
6
  const userArgs = process.argv.slice(2);
6
7
  const overrides = [
@@ -16,7 +17,7 @@ const args =
16
17
  userArgs[0] === "exec"
17
18
  ? [userArgs[0], ...overrides, ...userArgs.slice(1)]
18
19
  : [...overrides, ...userArgs];
19
- const child = spawn("/Applications/Codex.app/Contents/Resources/codex", args, {
20
+ const child = spawn(resolveCodexBinary(), args, {
20
21
  stdio: "inherit",
21
22
  env: { ...process.env, CODEX_GROK_BRIDGE_TOKEN: runtime.token },
22
23
  });
@@ -4,8 +4,9 @@ import { createInterface } from "node:readline";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { startRuntime } from "../src/runtime.mjs";
6
6
  import { Router } from "../src/router.mjs";
7
+ import { resolveCodexBinary } from "../src/paths.mjs";
7
8
 
8
- const binary = "/Applications/Codex.app/Contents/Resources/codex";
9
+ const binary = resolveCodexBinary();
9
10
  const args = process.argv.slice(2);
10
11
  if (!args.includes("app-server")) {
11
12
  const child = spawn(binary, args, { stdio: "inherit" });
@@ -1,14 +1,12 @@
1
1
  #!/bin/sh
2
- # Install or update the bridge inside /Applications/Codex Grok.app.
2
+ # Install or update the dedicated Codex Grok desktop wrapper.
3
3
  #
4
- # Default is a JS-only sync: the bundle's scripts/ and src/ are brought in line
5
- # with this checkout, in place. The previous version began with `rm -rf` on the
6
- # directory that CODEX_CLI_PATH points into, which destroys a running app's
7
- # entrypoint; nothing here removes a directory, and stale files are pruned one
8
- # by one only when the repo no longer has them.
4
+ # macOS: sync JS into /Applications/Codex Grok.app (must already exist).
5
+ # Linux: create ~/.local/share/codex-grok-bridge/app and a user .desktop.
6
+ # Never writes /Applications/Codex.app or /usr/lib/chatgpt.
9
7
  #
10
8
  # install-codex-grok-app.sh sync bridge JS (safe, fast)
11
- # install-codex-grok-app.sh --full also rebuild the launcher applet and sign
9
+ # install-codex-grok-app.sh --full macOS: rebuild applet; Linux: rewrite launcher
12
10
  # install-codex-grok-app.sh --force proceed even if a Codex Grok window is open
13
11
  #
14
12
  # ESM is not hot-reloaded: an open Codex Grok window keeps running the code it
@@ -16,11 +14,20 @@
16
14
  set -eu
17
15
 
18
16
  ROOT="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)"
19
- # CODEX_GROK_APP exists so this script can be exercised against a scratch copy
20
- # instead of the installed app.
21
- APP="${CODEX_GROK_APP:-/Applications/Codex Grok.app}"
22
- BRIDGE="$APP/Contents/Resources/bridge"
23
17
  USER_DATA="$HOME/.local/share/codex-grok-bridge/desktop"
18
+ PLATFORM="${CODEX_GROK_PLATFORM:-$(uname -s)}"
19
+ case "$PLATFORM" in
20
+ Linux|linux) PLATFORM=linux ;;
21
+ Darwin|darwin) PLATFORM=darwin ;;
22
+ esac
23
+
24
+ if [ "$PLATFORM" = linux ]; then
25
+ APP="${CODEX_GROK_APP:-$HOME/.local/share/codex-grok-bridge/app}"
26
+ BRIDGE="$APP"
27
+ else
28
+ APP="${CODEX_GROK_APP:-/Applications/Codex Grok.app}"
29
+ BRIDGE="$APP/Contents/Resources/bridge"
30
+ fi
24
31
 
25
32
  FULL=0
26
33
  FORCE=0
@@ -33,7 +40,14 @@ for arg in "$@"; do
33
40
  esac
34
41
  done
35
42
 
36
- if [ ! -d "$APP/Contents" ]; then
43
+ case "$APP" in
44
+ /usr/lib/chatgpt|/usr/lib/chatgpt/*|/usr/bin/chatgpt|/Applications/Codex.app|/Applications/Codex.app/*)
45
+ echo "refusing to install into the stock ChatGPT/Codex prefix: $APP" >&2
46
+ exit 1
47
+ ;;
48
+ esac
49
+
50
+ if [ "$PLATFORM" != linux ] && [ ! -d "$APP/Contents" ]; then
37
51
  echo "missing $APP" >&2
38
52
  exit 1
39
53
  fi
@@ -71,7 +85,44 @@ for dir in scripts src; do
71
85
  done
72
86
  [ "$status" -eq 0 ] || { echo "bundle did not match the checkout" >&2; exit 1; }
73
87
 
74
- if [ "$FULL" -eq 1 ]; then
88
+ write_linux_wrapper() {
89
+ NODE="${NODE:-$(command -v node || true)}"
90
+ [ -n "$NODE" ] || { echo "no node on PATH; set NODE=/path/to/node" >&2; exit 1; }
91
+ if [ -L "$NODE" ]; then
92
+ NODE="$(python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$NODE")"
93
+ fi
94
+ mkdir -p "$APP/bin"
95
+ LAUNCHER="$APP/bin/codex-grok-desktop"
96
+ NODE_Q=$(printf "%s" "$NODE" | sed "s/'/'\\\\''/g")
97
+ cat > "$LAUNCHER" <<EOF
98
+ #!/bin/sh
99
+ set -eu
100
+ ROOT="\$(CDPATH= cd -- "\$(dirname "\$0")/.." && pwd)"
101
+ exec '$NODE_Q' "\$ROOT/scripts/launch-desktop.mjs" "\$@"
102
+ EOF
103
+ chmod +x "$LAUNCHER"
104
+ APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
105
+ mkdir -p "$APPS_DIR"
106
+ DESKTOP="$APPS_DIR/codex-grok.desktop"
107
+ cat > "$DESKTOP" <<EOF
108
+ [Desktop Entry]
109
+ Type=Application
110
+ Name=Codex Grok
111
+ Comment=Codex desktop with Grok via the local bridge
112
+ Exec="$LAUNCHER" %U
113
+ Icon=chatgpt
114
+ Terminal=false
115
+ Categories=Development;
116
+ StartupWMClass=ChatGPT
117
+ EOF
118
+ update-desktop-database "$APPS_DIR" >/dev/null 2>&1 || true
119
+ echo "linux wrapper $LAUNCHER"
120
+ echo "desktop entry $DESKTOP"
121
+ }
122
+
123
+ if [ "$PLATFORM" = linux ]; then
124
+ write_linux_wrapper
125
+ elif [ "$FULL" -eq 1 ]; then
75
126
  NODE="${NODE:-$(command -v node || true)}"
76
127
  [ -n "$NODE" ] || { echo "no node on PATH; set NODE=/path/to/node" >&2; exit 1; }
77
128
  if [ -L "$NODE" ]; then
@@ -1,11 +1,19 @@
1
1
  import { execFileSync, spawn } from "node:child_process";
2
2
  import { appendFileSync, mkdirSync } from "node:fs";
3
- import { homedir } from "node:os";
4
3
  import path from "node:path";
5
4
  import { fileURLToPath } from "node:url";
5
+ import {
6
+ desktopLaunchArgs,
7
+ desktopLaunchEnv,
8
+ desktopUserDataDir,
9
+ isGrokDesktopProcess,
10
+ resolveDesktopApp,
11
+ resolvePlatform,
12
+ } from "../src/paths.mjs";
6
13
 
7
- const userData = path.join(homedir(), ".local/share/codex-grok-bridge/desktop");
8
- const logFile = path.join(homedir(), ".local/share/codex-grok-bridge/launch.log");
14
+ const platform = resolvePlatform();
15
+ const userData = desktopUserDataDir();
16
+ const logFile = path.join(userData, "..", "launch.log");
9
17
  mkdirSync(userData, { recursive: true, mode: 0o700 });
10
18
  mkdirSync(path.dirname(logFile), { recursive: true, mode: 0o700 });
11
19
 
@@ -18,12 +26,7 @@ function grokPid() {
18
26
  encoding: "utf8",
19
27
  });
20
28
  for (const line of out.split("\n")) {
21
- if (
22
- line.includes(`--user-data-dir=${userData}`) &&
23
- /MacOS\/(ChatGPT|Codex)(\s|$)/.test(line) &&
24
- !line.includes("Helper") &&
25
- !line.includes("crashpad")
26
- )
29
+ if (isGrokDesktopProcess(line, userData))
27
30
  return Number(line.trim().split(/\s+/)[0]);
28
31
  }
29
32
  return null;
@@ -41,6 +44,7 @@ function activate(pid) {
41
44
  }
42
45
 
43
46
  function notify(text) {
47
+ if (platform !== "darwin") return;
44
48
  try {
45
49
  execFileSync("/usr/bin/osascript", [
46
50
  "-e",
@@ -49,19 +53,28 @@ function notify(text) {
49
53
  } catch {}
50
54
  }
51
55
 
52
- const wrapper = fileURLToPath(new URL("./codex-wrapper.mjs", import.meta.url));
53
- try {
54
- const running = grokPid();
55
- if (running) {
56
- log(`activate pid=${running}`);
57
- try {
58
- activate(running);
59
- notify("Codex Grok 창을 앞으로 가져왔습니다.");
60
- process.exit(0);
61
- } catch (error) {
62
- log(`activate failed: ${error.message}`);
63
- }
64
- }
56
+ function startLinux(wrapper) {
57
+ const app = resolveDesktopApp();
58
+ const pathPrefix = `${path.dirname(process.execPath)}:/usr/local/bin:/usr/bin:/bin`;
59
+ log(`start linux app=${app} wrapper=${wrapper}`);
60
+ const child = spawn(app, desktopLaunchArgs(userData), {
61
+ stdio: "inherit",
62
+ env: {
63
+ ...process.env,
64
+ ...desktopLaunchEnv({ wrapper, userData, pathPrefix }),
65
+ },
66
+ });
67
+ child.on("error", (error) => {
68
+ log(`spawn failed: ${error.message}`);
69
+ process.exit(1);
70
+ });
71
+ child.on("exit", (code) => {
72
+ log(`chatgpt exit=${code}`);
73
+ process.exit(code ?? 1);
74
+ });
75
+ }
76
+
77
+ function startDarwin(wrapper) {
65
78
  log(`start wrapper=${wrapper}`);
66
79
  const child = spawn(
67
80
  "/usr/bin/open",
@@ -95,6 +108,26 @@ try {
95
108
  }
96
109
  process.exit(code ?? 1);
97
110
  });
111
+ }
112
+
113
+ const wrapper = fileURLToPath(new URL("./codex-wrapper.mjs", import.meta.url));
114
+ try {
115
+ const running = grokPid();
116
+ if (running) {
117
+ log(`activate pid=${running}`);
118
+ if (platform !== "darwin") {
119
+ process.exit(0);
120
+ }
121
+ try {
122
+ activate(running);
123
+ notify("Codex Grok 창을 앞으로 가져왔습니다.");
124
+ process.exit(0);
125
+ } catch (error) {
126
+ log(`activate failed: ${error.message}`);
127
+ }
128
+ }
129
+ if (platform === "linux") startLinux(wrapper);
130
+ else startDarwin(wrapper);
98
131
  } catch (error) {
99
132
  log(`fatal: ${error.message}`);
100
133
  notify(error.message);
package/src/paths.mjs ADDED
@@ -0,0 +1,94 @@
1
+ import path from "node:path";
2
+ import { homedir } from "node:os";
3
+
4
+ export const LINUX_CODEX_BINARY = "/usr/lib/chatgpt/resources/codex";
5
+ export const LINUX_CHATGPT_BIN = "/usr/lib/chatgpt/ChatGPT";
6
+ export const DARWIN_CODEX_BINARY = "/Applications/Codex.app/Contents/Resources/codex";
7
+ export const DARWIN_CODEX_APP = "/Applications/Codex.app";
8
+ export const LINUX_STOCK_PREFIX = "/usr/lib/chatgpt";
9
+
10
+ export function resolvePlatform(options = {}) {
11
+ const env = options.env ?? process.env;
12
+ return options.platform ?? env.CODEX_GROK_PLATFORM ?? process.platform;
13
+ }
14
+
15
+ export function resolveCodexBinary(options = {}) {
16
+ const env = options.env ?? process.env;
17
+ if (env.CODEX_BINARY) return env.CODEX_BINARY;
18
+ return resolvePlatform({ ...options, env }) === "linux"
19
+ ? LINUX_CODEX_BINARY
20
+ : DARWIN_CODEX_BINARY;
21
+ }
22
+
23
+ export function resolveDesktopApp(options = {}) {
24
+ const env = options.env ?? process.env;
25
+ if (env.CODEX_DESKTOP_APP) return env.CODEX_DESKTOP_APP;
26
+ return resolvePlatform({ ...options, env }) === "linux"
27
+ ? LINUX_CHATGPT_BIN
28
+ : DARWIN_CODEX_APP;
29
+ }
30
+
31
+ export function desktopUserDataDir(home = homedir()) {
32
+ return path.join(home, ".local/share/codex-grok-bridge/desktop");
33
+ }
34
+
35
+ export function linuxAppDir(home = homedir()) {
36
+ return path.join(home, ".local/share/codex-grok-bridge/app");
37
+ }
38
+
39
+ export function linuxDesktopEntryPath(home = homedir(), xdgDataHome) {
40
+ const dataHome = xdgDataHome || path.join(home, ".local/share");
41
+ return path.join(dataHome, "applications", "codex-grok.desktop");
42
+ }
43
+
44
+ export function desktopLaunchEnv({ wrapper, userData, pathPrefix }) {
45
+ const env = {
46
+ CODEX_CLI_PATH: wrapper,
47
+ CODEX_APP_SERVER_FORCE_CLI: "1",
48
+ CODEX_ELECTRON_USER_DATA_PATH: userData,
49
+ };
50
+ if (pathPrefix) env.PATH = pathPrefix;
51
+ return env;
52
+ }
53
+
54
+ export function desktopLaunchArgs(userData) {
55
+ return [`--user-data-dir=${userData}`];
56
+ }
57
+
58
+ export function isGrokDesktopProcess(line, userData, options = {}) {
59
+ if (!line.includes(`--user-data-dir=${userData}`)) return false;
60
+ if (line.includes("Helper") || line.includes("crashpad")) return false;
61
+ const app = resolveDesktopApp(options);
62
+ if (line.includes(app)) return true;
63
+ return resolvePlatform(options) === "linux"
64
+ ? line.includes(LINUX_CHATGPT_BIN)
65
+ : /MacOS\/(ChatGPT|Codex)(\s|$)/.test(line);
66
+ }
67
+
68
+ export function isForbiddenInstallDir(app) {
69
+ const resolved = path.resolve(app);
70
+ const forbidden = [
71
+ LINUX_STOCK_PREFIX,
72
+ "/usr/bin/chatgpt",
73
+ "/usr/share/applications/chatgpt.desktop",
74
+ DARWIN_CODEX_APP,
75
+ ];
76
+ return forbidden.some(
77
+ (prefix) => resolved === prefix || resolved.startsWith(`${prefix}/`),
78
+ );
79
+ }
80
+
81
+ export function linuxDesktopEntry({ exec, icon = "chatgpt" }) {
82
+ return [
83
+ "[Desktop Entry]",
84
+ "Type=Application",
85
+ "Name=Codex Grok",
86
+ "Comment=Codex desktop with Grok via the local bridge",
87
+ `Exec=${exec}`,
88
+ `Icon=${icon}`,
89
+ "Terminal=false",
90
+ "Categories=Development;",
91
+ "StartupWMClass=ChatGPT",
92
+ "",
93
+ ].join("\n");
94
+ }
package/src/tools.mjs CHANGED
@@ -56,16 +56,145 @@ const GROK_INPUT_ITEM_TYPES = new Set([
56
56
  "function_call_output",
57
57
  "shell_call",
58
58
  ]);
59
+ // Codex item ids, status, and new client metadata have 422'd the upstream.
60
+ // Keep only the fields Grok's Responses input is known to accept.
61
+ const INPUT_ITEM_FIELDS = {
62
+ message: ["type", "role", "content"],
63
+ reasoning: ["type", "summary"],
64
+ function_call: ["type", "name", "call_id", "arguments"],
65
+ function_call_output: ["type", "name", "call_id", "output"],
66
+ shell_call: ["type", "name", "call_id", "action"],
67
+ };
68
+ const CONTENT_PART_FIELDS = {
69
+ input_text: ["type", "text"],
70
+ output_text: ["type", "text"],
71
+ summary_text: ["type", "text"],
72
+ input_image: ["type", "image_url", "detail"],
73
+ };
74
+
75
+ function pickKeys(node, keys) {
76
+ const next = {};
77
+ for (const key of keys) {
78
+ if (node[key] !== undefined) next[key] = node[key];
79
+ }
80
+ return next;
81
+ }
82
+
83
+ function whitelistContentPart(part) {
84
+ if (!part || typeof part !== "object" || Array.isArray(part)) return part;
85
+ const keys = CONTENT_PART_FIELDS[part.type];
86
+ if (!keys) return part;
87
+ const next = pickKeys(part, keys);
88
+ if (next.image_url && typeof next.image_url === "object")
89
+ next.image_url = pickKeys(next.image_url, ["url", "detail"]);
90
+ return next;
91
+ }
92
+
93
+ function whitelistInputNode(node) {
94
+ if (!node || typeof node !== "object" || Array.isArray(node)) return node;
95
+ const itemKeys = INPUT_ITEM_FIELDS[node.type];
96
+ if (itemKeys) {
97
+ const next = pickKeys(node, itemKeys);
98
+ if (Array.isArray(next.content))
99
+ next.content = next.content.map(whitelistContentPart);
100
+ return next;
101
+ }
102
+ if (node.type == null && node.role != null && node.content !== undefined) {
103
+ const next = pickKeys(node, ["role", "content"]);
104
+ if (Array.isArray(next.content))
105
+ next.content = next.content.map(whitelistContentPart);
106
+ return next;
107
+ }
108
+ return whitelistContentPart(node);
109
+ }
110
+
111
+ function isObjectFragment(schema) {
112
+ return (
113
+ schema &&
114
+ typeof schema === "object" &&
115
+ !Array.isArray(schema) &&
116
+ (schema.type === "object" || schema.type == null)
117
+ );
118
+ }
119
+
120
+ function mergeObjectFragments(fragments) {
121
+ const properties = {};
122
+ let required;
123
+ for (const fragment of fragments) {
124
+ if (fragment.properties && typeof fragment.properties === "object")
125
+ Object.assign(properties, fragment.properties);
126
+ if (Array.isArray(fragment.required)) {
127
+ required =
128
+ required === undefined
129
+ ? fragment.required.slice()
130
+ : required.filter((key) => fragment.required.includes(key));
131
+ }
132
+ }
133
+ return { properties, required };
134
+ }
135
+
136
+ function emitObjectParameters(source, overrides = {}) {
137
+ const properties =
138
+ overrides.properties ??
139
+ (source.properties && typeof source.properties === "object"
140
+ ? source.properties
141
+ : {});
142
+ const required =
143
+ overrides.required !== undefined
144
+ ? overrides.required
145
+ : Array.isArray(source.required)
146
+ ? source.required
147
+ : undefined;
148
+ const next = { type: "object", properties: { ...properties } };
149
+ if (required?.length) next.required = required.slice();
150
+ if (source.additionalProperties !== undefined)
151
+ next.additionalProperties = source.additionalProperties;
152
+ if (source.description !== undefined) next.description = source.description;
153
+ return next;
154
+ }
59
155
 
60
156
  function usableParameters(parameters) {
61
- if (!parameters || typeof parameters !== "object") return OBJECT_SCHEMA;
62
- if (
63
- parameters.type === "object" ||
64
- Array.isArray(parameters.oneOf) ||
65
- Array.isArray(parameters.anyOf)
66
- )
67
- return parameters;
68
- return OBJECT_SCHEMA;
157
+ if (!parameters || typeof parameters !== "object" || Array.isArray(parameters))
158
+ return OBJECT_SCHEMA;
159
+ const variants = Array.isArray(parameters.oneOf)
160
+ ? parameters.oneOf
161
+ : Array.isArray(parameters.anyOf)
162
+ ? parameters.anyOf
163
+ : null;
164
+ if (!variants) {
165
+ return parameters.type === "object"
166
+ ? emitObjectParameters(parameters)
167
+ : OBJECT_SCHEMA;
168
+ }
169
+ const fragments = variants.filter(isObjectFragment);
170
+ if (fragments.length === 0) {
171
+ return parameters.type === "object"
172
+ ? emitObjectParameters(parameters)
173
+ : OBJECT_SCHEMA;
174
+ }
175
+ const merged = mergeObjectFragments(fragments);
176
+ const properties = {
177
+ ...(parameters.properties && typeof parameters.properties === "object"
178
+ ? parameters.properties
179
+ : {}),
180
+ ...merged.properties,
181
+ };
182
+ let required = merged.required;
183
+ if (Array.isArray(parameters.required)) {
184
+ required =
185
+ required === undefined
186
+ ? parameters.required.slice()
187
+ : required.filter((key) => parameters.required.includes(key));
188
+ }
189
+ const first = fragments[0];
190
+ return emitObjectParameters(
191
+ {
192
+ additionalProperties:
193
+ parameters.additionalProperties ?? first.additionalProperties,
194
+ description: parameters.description ?? first.description,
195
+ },
196
+ { properties, required },
197
+ );
69
198
  }
70
199
 
71
200
  function sanitize(name) {
@@ -189,7 +318,7 @@ function toProxyInputNode(node, map, state) {
189
318
  if (
190
319
  key === "encrypted_content" ||
191
320
  key === "encrypted_function_args" ||
192
- key === "internal_chat_message_metadata_passthrough"
321
+ key.startsWith("internal_")
193
322
  )
194
323
  continue;
195
324
  const converted = toProxyInputNode(value, map, state);
@@ -204,7 +333,7 @@ function toProxyInputNode(node, map, state) {
204
333
  (part) => part && typeof part.text === "string" && part.text.trim(),
205
334
  );
206
335
  if (!summary.length) return DROP;
207
- return { type: "reasoning", summary };
336
+ return whitelistInputNode({ type: "reasoning", summary });
208
337
  }
209
338
 
210
339
  if (next.type === "agent_message") {
@@ -214,7 +343,7 @@ function toProxyInputNode(node, map, state) {
214
343
  ? [{ type: "input_text", text: next.text }]
215
344
  : [];
216
345
  if (!content.length) return DROP;
217
- return { type: "message", role: "assistant", content };
346
+ return whitelistInputNode({ type: "message", role: "assistant", content });
218
347
  }
219
348
 
220
349
  if (
@@ -227,10 +356,7 @@ function toProxyInputNode(node, map, state) {
227
356
  delete next.namespace;
228
357
  if (typeof next.call_id === "string") state.callIds.set(next.call_id, proxyName);
229
358
  }
230
- return next;
231
- }
232
-
233
- if (
359
+ } else if (
234
360
  next.type === "custom_tool_call" &&
235
361
  typeof next.name === "string"
236
362
  ) {
@@ -244,10 +370,7 @@ function toProxyInputNode(node, map, state) {
244
370
  delete next.input;
245
371
  if (typeof next.call_id === "string") state.callIds.set(next.call_id, proxyName);
246
372
  }
247
- return next;
248
- }
249
-
250
- if (
373
+ } else if (
251
374
  next.type === "function_call_output" ||
252
375
  next.type === "custom_tool_call_output"
253
376
  ) {
@@ -262,10 +385,9 @@ function toProxyInputNode(node, map, state) {
262
385
  next.name = proxyName;
263
386
  delete next.namespace;
264
387
  }
265
- return next;
266
388
  }
267
389
 
268
- return next;
390
+ return whitelistInputNode(next);
269
391
  }
270
392
 
271
393
  function toProxyInput(input, map) {