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/CHANGELOG.md +32 -0
- package/README.md +562 -87
- package/package.json +4 -2
- package/scripts/codex-grok.mjs +2 -1
- package/scripts/codex-wrapper.mjs +2 -1
- package/scripts/install-codex-grok-app.sh +64 -13
- package/scripts/launch-desktop.mjs +55 -22
- package/src/paths.mjs +94 -0
- package/src/tools.mjs +143 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-grok-bridge",
|
|
3
|
-
"version": "1.0.
|
|
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": {
|
package/scripts/codex-grok.mjs
CHANGED
|
@@ -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(
|
|
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 =
|
|
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
|
|
2
|
+
# Install or update the dedicated Codex Grok desktop wrapper.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
8
|
-
const
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
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"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Array.isArray(parameters.anyOf)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|