codex-grok-bridge 1.5.0 → 1.6.1
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 +9 -0
- package/Open Codex with Grok.command +1 -1
- package/README.md +24 -22
- package/package.json +2 -2
- package/scripts/codex-grok.mjs +2 -1
- package/scripts/install-codex-grok-app.ps1 +27 -1
- package/scripts/launch-desktop.mjs +51 -22
- package/src/models.mjs +6 -5
- package/src/paths.mjs +30 -4
- package/src/tools.mjs +27 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.6.1 — 2026-09-28
|
|
6
|
+
|
|
7
|
+
- Darwin bundled CLI resolves `Codex.app/Contents/Resources/codex-cli/bin/codex` when that file exists (Codex 26.924). The legacy `Contents/Resources/codex` path remains the fallback. `CODEX_BINARY` still wins. Linux and Windows layouts are unchanged.
|
|
8
|
+
|
|
9
|
+
## 1.6.0 — 2026-09-27
|
|
10
|
+
|
|
11
|
+
- Default catalog model is `grok-4.7` (`Grok 4.7 / xAI`). `grok-4.6` stays listed so existing threads still resolve.
|
|
12
|
+
- `grok-*` still routes to `grok_build_cli`. No other model ids were added. `GROK_BRIDGE_MODELS` still appends extra `grok-*` ids.
|
|
13
|
+
|
|
5
14
|
## 1.5.0 — 2026-09-12
|
|
6
15
|
|
|
7
16
|
First cut that accepts **Windows** (`"os": ["darwin", "linux", "win32"]`).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# Double-click to open the dedicated Codex window with Grok 4.
|
|
2
|
+
# Double-click to open the dedicated Codex window with Grok 4.7 available.
|
|
3
3
|
# Resolves everything from this file's own location, so the checkout can live
|
|
4
4
|
# anywhere and be moved without editing this launcher.
|
|
5
5
|
set -eu
|
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# codex-grok-bridge
|
|
2
2
|
|
|
3
|
-
Run **Grok 4.
|
|
4
|
-
history and MCP.
|
|
3
|
+
Run **Grok 4.7 as the model inside Codex**. Codex still owns tools, permissions,
|
|
4
|
+
history and MCP. `grok-4.6` stays on the model list so existing threads still
|
|
5
|
+
resolve. Inference uses the installed `grok` CLI login session — not an
|
|
5
6
|
xAI API key.
|
|
6
7
|
|
|
7
8
|
```
|
|
8
|
-
Codex UI/CLI → app-server → scripts/codex-wrapper.mjs (adds grok-4.6 to the model list)
|
|
9
|
+
Codex UI/CLI → app-server → scripts/codex-wrapper.mjs (adds grok-4.7 and grok-4.6 to the model list)
|
|
9
10
|
→ localhost /v1/responses (the bridge)
|
|
10
11
|
→ cli-chat-proxy.grok.com
|
|
11
12
|
→ Codex executes every tool call; results return as the next input
|
|
@@ -20,7 +21,7 @@ prefixes stay untouched; `scripts/install-codex-grok-app.sh` and
|
|
|
20
21
|
---
|
|
21
22
|
## What this is
|
|
22
23
|
|
|
23
|
-
A local bridge that puts Grok 4.
|
|
24
|
+
A local bridge that puts Grok 4.7 on Codex’s model list and sends Codex
|
|
24
25
|
`/v1/responses` traffic to `cli-chat-proxy.grok.com`. Grok does the inference.
|
|
25
26
|
Codex runs every tool call (shell, patch, MCP, …) and feeds the results back as
|
|
26
27
|
the next request’s `input`. That is the same agent loop as the GPT path.
|
|
@@ -32,9 +33,9 @@ so Codex still recognizes them.
|
|
|
32
33
|
Fourteen files under `src/`. Zero runtime dependencies. Node.js ≥ 22.
|
|
33
34
|
|
|
34
35
|
This is not a second-opinion review product and not a Grok-native search
|
|
35
|
-
product.
|
|
36
|
-
`grok-*` route.
|
|
37
|
-
|
|
36
|
+
product. The picker shows `grok-4.7` (Grok 4.7 / xAI) and keeps `grok-4.6`.
|
|
37
|
+
Any other `grok-*` id uses the same route. `GROK_BRIDGE_MODELS` adds further
|
|
38
|
+
`grok-*` ids. If Codex exposes a web-search tool, Grok can call
|
|
38
39
|
that tool the same way it calls any other Codex tool.
|
|
39
40
|
|
|
40
41
|
## Requirements
|
|
@@ -59,7 +60,7 @@ re-verification.
|
|
|
59
60
|
|
|
60
61
|
```sh
|
|
61
62
|
npm install -g codex-grok-bridge # macOS, Linux, or Windows; Node ≥ 22
|
|
62
|
-
codex-grok # launches Codex with Grok 4.
|
|
63
|
+
codex-grok # launches Codex with Grok 4.7 available
|
|
63
64
|
codex-grok exec --skip-git-repo-check --sandbox workspace-write 'your task'
|
|
64
65
|
```
|
|
65
66
|
|
|
@@ -84,9 +85,9 @@ On Linux that wrapper is `~/.local/share/codex-grok-bridge/app` plus a user
|
|
|
84
85
|
`.desktop` entry; the stock `/usr/lib/chatgpt` tree is not patched. On Windows
|
|
85
86
|
it is `%LOCALAPPDATA%\codex-grok-bridge`; `WindowsApps` is never written.
|
|
86
87
|
|
|
87
|
-
In the new Codex window, **select Grok 4.
|
|
88
|
-
thread**. Existing GPT models stay on the list
|
|
89
|
-
open does not get this extension.
|
|
88
|
+
In the new Codex window, **select Grok 4.7 / xAI before starting a new
|
|
89
|
+
thread**. Existing GPT models stay on the list, and so does `grok-4.6`. A Codex
|
|
90
|
+
window that was already open does not get this extension.
|
|
90
91
|
|
|
91
92
|
The dedicated window stores UI data under
|
|
92
93
|
`~/.local/share/codex-grok-bridge/desktop` and **shares** the normal Codex home
|
|
@@ -280,7 +281,7 @@ Start here when something breaks. Do not open `~/.grok/auth.json` or
|
|
|
280
281
|
| `GROK_BRIDGE_TRANSPORT=fetch` | Use Node `fetch` instead of `node:http(s)` |
|
|
281
282
|
| `GROK_BRIDGE_INFERENCE=cli` | Fall back to the CLI envelope path |
|
|
282
283
|
| `GROK_BRIDGE_DIAGNOSTICS=off` | Do not write the JSONL log |
|
|
283
|
-
| `GROK_BRIDGE_MODELS` | Extra `grok-*` catalog ids (comma or space)
|
|
284
|
+
| `GROK_BRIDGE_MODELS` | Extra `grok-*` catalog ids (comma or space) beyond `grok-4.7` and `grok-4.6` |
|
|
284
285
|
| `NODE` | Absolute `node` binary for the `.command` launcher / desktop scripts |
|
|
285
286
|
| `CODEX_GROK_APP` | Alternate app path for the desktop installer (macOS: `/Applications/Codex Grok.app`; Linux: `~/.local/share/codex-grok-bridge/app`; Windows: `%LOCALAPPDATA%\codex-grok-bridge\app`) |
|
|
286
287
|
|
|
@@ -347,7 +348,7 @@ responses. Codex’s and Grok’s own retention policies still apply.
|
|
|
347
348
|
|
|
348
349
|
## 이게 뭔가
|
|
349
350
|
|
|
350
|
-
Grok 4.
|
|
351
|
+
Grok 4.7을 Codex 모델 목록에 넣고, Codex의 `/v1/responses`를
|
|
351
352
|
`cli-chat-proxy.grok.com`으로 넘기는 로컬 브리지입니다. 추론은 Grok이 합니다.
|
|
352
353
|
도구 호출(셸, 패치, MCP, …)은 Codex가 실행하고, 결과는 다음 요청의 `input`으로
|
|
353
354
|
돌아갑니다. GPT 경로와 같은 에이전트 루프입니다.
|
|
@@ -359,10 +360,11 @@ calling으로 옮기고, 상류 Responses 스트림을 전달한 뒤, Codex가
|
|
|
359
360
|
`src/` 아래 파일 14개. 런타임 의존성 없음. Node.js 22 이상. 1.5.0부터 npm
|
|
360
361
|
`"os"`는 `darwin` / `linux` / `win32`입니다.
|
|
361
362
|
|
|
362
|
-
코드 리뷰 전용 제품이 아니고 Grok 네이티브 검색 제품도 아닙니다.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
노출하면 Grok은 다른 Codex 도구와 같이 그 도구를
|
|
363
|
+
코드 리뷰 전용 제품이 아니고 Grok 네이티브 검색 제품도 아닙니다. 피커에는
|
|
364
|
+
`grok-4.7`(Grok 4.7 / xAI)이 기본으로 보이고 `grok-4.6`도 남습니다. 그 외
|
|
365
|
+
`grok-*` id도 같은 경로로 붙고, `GROK_BRIDGE_MODELS`로 카탈로그에 더합니다.
|
|
366
|
+
Codex가 웹 검색 도구를 노출하면 Grok은 다른 Codex 도구와 같이 그 도구를
|
|
367
|
+
호출할 수 있습니다.
|
|
366
368
|
|
|
367
369
|
## 필요한 것
|
|
368
370
|
|
|
@@ -385,7 +387,7 @@ id(4.7 포함)를 내놓으면 같은 `grok-*` 경로로 붙습니다. 기본
|
|
|
385
387
|
|
|
386
388
|
```sh
|
|
387
389
|
npm install -g codex-grok-bridge # macOS, Linux, Windows; Node ≥ 22
|
|
388
|
-
codex-grok # Grok 4.
|
|
390
|
+
codex-grok # Grok 4.7이 있는 Codex를 띄움
|
|
389
391
|
codex-grok exec --skip-git-repo-check --sandbox workspace-write '작업 내용'
|
|
390
392
|
```
|
|
391
393
|
|
|
@@ -410,9 +412,9 @@ Linux에서는 `~/.local/share/codex-grok-bridge/app`과 사용자 `.desktop`이
|
|
|
410
412
|
정품 `/usr/lib/chatgpt`는 패치하지 않습니다. Windows에서는
|
|
411
413
|
`%LOCALAPPDATA%\codex-grok-bridge`이고 `WindowsApps`는 쓰지 않습니다.
|
|
412
414
|
|
|
413
|
-
새로 열린 Codex 창에서 **새 작업을 시작하기 전에 Grok 4.
|
|
414
|
-
기존 GPT
|
|
415
|
-
주입되지 않습니다.
|
|
415
|
+
새로 열린 Codex 창에서 **새 작업을 시작하기 전에 Grok 4.7 / xAI를 선택**하세요.
|
|
416
|
+
기존 GPT 모델과 `grok-4.6`도 목록에 남습니다. 이미 열려 있던 일반 Codex 창에는
|
|
417
|
+
이 확장이 주입되지 않습니다.
|
|
416
418
|
|
|
417
419
|
전용 창은 UI 데이터를 `~/.local/share/codex-grok-bridge/desktop`에 두고,
|
|
418
420
|
계정·작업·설정은 기존 Codex 홈을 **공유**합니다. 작업 내용과 설정 변경은 다른
|
|
@@ -595,7 +597,7 @@ provider에 Codex `request_max_retries` / `stream_max_retries`를 2로 두어
|
|
|
595
597
|
| `GROK_BRIDGE_TRANSPORT=fetch` | `node:http(s)` 대신 Node `fetch` |
|
|
596
598
|
| `GROK_BRIDGE_INFERENCE=cli` | CLI 봉투 경로로 폴백 |
|
|
597
599
|
| `GROK_BRIDGE_DIAGNOSTICS=off` | JSONL 로그를 쓰지 않음 |
|
|
598
|
-
| `GROK_BRIDGE_MODELS` | 카탈로그에 더할 `grok-*` id (쉼표/공백)
|
|
600
|
+
| `GROK_BRIDGE_MODELS` | `grok-4.7`과 `grok-4.6` 외에 카탈로그에 더할 `grok-*` id (쉼표/공백) |
|
|
599
601
|
| `NODE` | `.command` / 데스크톱 스크립트가 쓸 `node` 절대 경로 |
|
|
600
602
|
| `CODEX_GROK_APP` | 데스크톱 설치기가 쓸 앱 경로 (macOS: `/Applications/Codex Grok.app`; Linux: `~/.local/share/codex-grok-bridge/app`; Windows: `%LOCALAPPDATA%\codex-grok-bridge\app`) |
|
|
601
603
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-grok-bridge",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Run Grok 4.
|
|
3
|
+
"version": "1.6.1",
|
|
4
|
+
"description": "Run Grok 4.7 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",
|
|
7
7
|
"author": "deximple",
|
package/scripts/codex-grok.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { startRuntime } from "../src/runtime.mjs";
|
|
4
4
|
import { resolveCodexBinary } from "../src/paths.mjs";
|
|
5
|
+
import { DEFAULT_GROK_MODEL } from "../src/models.mjs";
|
|
5
6
|
const runtime = await startRuntime();
|
|
6
7
|
const userArgs = process.argv.slice(2);
|
|
7
8
|
const overrides = [
|
|
@@ -11,7 +12,7 @@ const overrides = [
|
|
|
11
12
|
"-c",
|
|
12
13
|
`model_catalog_json=${JSON.stringify(runtime.catalogPath)}`,
|
|
13
14
|
"-m",
|
|
14
|
-
|
|
15
|
+
DEFAULT_GROK_MODEL,
|
|
15
16
|
];
|
|
16
17
|
const args =
|
|
17
18
|
userArgs[0] === "exec"
|
|
@@ -25,7 +25,33 @@ New-Item -ItemType Directory -Force -Path (Join-Path $App "scripts"), (Join-Path
|
|
|
25
25
|
Copy-Item (Join-Path $Root "scripts\*.mjs") (Join-Path $App "scripts") -Force
|
|
26
26
|
Copy-Item (Join-Path $Root "src\*.mjs") (Join-Path $App "src") -Force
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
function Resolve-NodeExe {
|
|
29
|
+
$cmd = Get-Command node -ErrorAction SilentlyContinue
|
|
30
|
+
if ($cmd -and $cmd.Source) { return $cmd.Source }
|
|
31
|
+
if ($env:NODE) { return $env:NODE }
|
|
32
|
+
$candidates = @()
|
|
33
|
+
if ($env:ProgramFiles) {
|
|
34
|
+
$candidates += Join-Path $env:ProgramFiles "nodejs\node.exe"
|
|
35
|
+
}
|
|
36
|
+
$x86 = ${env:ProgramFiles(x86)}
|
|
37
|
+
if ($x86) {
|
|
38
|
+
$candidates += Join-Path $x86 "nodejs\node.exe"
|
|
39
|
+
}
|
|
40
|
+
if ($env:LOCALAPPDATA) {
|
|
41
|
+
$candidates += Join-Path $env:LOCALAPPDATA "Programs\nodejs\node.exe"
|
|
42
|
+
$candidates += Join-Path $env:LOCALAPPDATA "Programs\node\node.exe"
|
|
43
|
+
}
|
|
44
|
+
if ($env:USERPROFILE) {
|
|
45
|
+
$candidates += Join-Path $env:USERPROFILE "node\node.exe"
|
|
46
|
+
$candidates += Join-Path $env:USERPROFILE "nodejs\node.exe"
|
|
47
|
+
}
|
|
48
|
+
foreach ($candidate in $candidates) {
|
|
49
|
+
if ($candidate -and (Test-Path -LiteralPath $candidate)) { return $candidate }
|
|
50
|
+
}
|
|
51
|
+
Write-Error "node not found. Set NODE to the node.exe path, or install Node.js."
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
$Node = Resolve-NodeExe
|
|
29
55
|
$Launcher = Join-Path $App "codex-grok-desktop.cmd"
|
|
30
56
|
@"
|
|
31
57
|
@echo off
|
|
@@ -7,8 +7,10 @@ import {
|
|
|
7
7
|
desktopLaunchEnv,
|
|
8
8
|
desktopUserDataDir,
|
|
9
9
|
isGrokDesktopProcess,
|
|
10
|
+
parseWin32ProcessJson,
|
|
10
11
|
resolveDesktopApp,
|
|
11
12
|
resolvePlatform,
|
|
13
|
+
selectGrokDesktopPid,
|
|
12
14
|
} from "../src/paths.mjs";
|
|
13
15
|
|
|
14
16
|
const platform = resolvePlatform();
|
|
@@ -22,25 +24,41 @@ function log(message) {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
function grokPidWin32() {
|
|
25
|
-
const systemRoot = process.env.SystemRoot || "C:\\Windows";
|
|
26
|
-
const powershell = `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
|
|
27
27
|
try {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
const pid = Number(String(out).trim());
|
|
38
|
-
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
28
|
+
const injected = process.env.CODEX_GROK_CIM_JSON;
|
|
29
|
+
const rows =
|
|
30
|
+
injected != null
|
|
31
|
+
? parseWin32ProcessJson(injected)
|
|
32
|
+
: parseWin32ProcessJson(queryWin32Processes());
|
|
33
|
+
return selectGrokDesktopPid(rows, userData, {
|
|
34
|
+
platform: "win32",
|
|
35
|
+
env: process.env,
|
|
36
|
+
});
|
|
39
37
|
} catch {
|
|
40
38
|
return null;
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
|
|
42
|
+
function queryWin32Processes() {
|
|
43
|
+
const systemRoot = process.env.SystemRoot || "C:\\Windows";
|
|
44
|
+
const powershell = path.join(
|
|
45
|
+
systemRoot,
|
|
46
|
+
"System32",
|
|
47
|
+
"WindowsPowerShell",
|
|
48
|
+
"v1.0",
|
|
49
|
+
"powershell.exe",
|
|
50
|
+
);
|
|
51
|
+
return execFileSync(
|
|
52
|
+
powershell,
|
|
53
|
+
[
|
|
54
|
+
"-NoProfile",
|
|
55
|
+
"-Command",
|
|
56
|
+
`Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -like ${JSON.stringify(`*--user-data-dir=${userData}*`)} } | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress`,
|
|
57
|
+
],
|
|
58
|
+
{ encoding: "utf8", windowsHide: true, timeout: 15000 },
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
function grokPid() {
|
|
45
63
|
if (platform === "win32") return grokPidWin32();
|
|
46
64
|
const out = execFileSync("/bin/ps", ["-axo", "pid=,command="], {
|
|
@@ -74,10 +92,18 @@ function notify(text) {
|
|
|
74
92
|
} catch {}
|
|
75
93
|
}
|
|
76
94
|
|
|
95
|
+
function desktopCommand(app) {
|
|
96
|
+
if (/\.(mjs|cjs|js)$/i.test(app)) {
|
|
97
|
+
return { command: process.execPath, args: [app, ...desktopLaunchArgs(userData)] };
|
|
98
|
+
}
|
|
99
|
+
return { command: app, args: desktopLaunchArgs(userData) };
|
|
100
|
+
}
|
|
101
|
+
|
|
77
102
|
function startWin32(wrapper) {
|
|
78
103
|
const app = resolveDesktopApp();
|
|
104
|
+
const { command, args } = desktopCommand(app);
|
|
79
105
|
log(`start win32 app=${app} wrapper=${wrapper}`);
|
|
80
|
-
const child = spawn(
|
|
106
|
+
const child = spawn(command, args, {
|
|
81
107
|
stdio: "inherit",
|
|
82
108
|
env: {
|
|
83
109
|
...process.env,
|
|
@@ -156,15 +182,18 @@ try {
|
|
|
156
182
|
const running = grokPid();
|
|
157
183
|
if (running) {
|
|
158
184
|
log(`activate pid=${running}`);
|
|
159
|
-
if (platform
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
185
|
+
if (platform === "darwin") {
|
|
186
|
+
try {
|
|
187
|
+
activate(running);
|
|
188
|
+
notify("Codex Grok 창을 앞으로 가져왔습니다.");
|
|
189
|
+
process.exit(0);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
log(`activate failed: ${error.message}`);
|
|
192
|
+
}
|
|
193
|
+
} else if (platform === "linux") {
|
|
194
|
+
// Fall through to startLinux so Electron can focus the existing window.
|
|
195
|
+
} else {
|
|
165
196
|
process.exit(0);
|
|
166
|
-
} catch (error) {
|
|
167
|
-
log(`activate failed: ${error.message}`);
|
|
168
197
|
}
|
|
169
198
|
}
|
|
170
199
|
if (platform === "linux") startLinux(wrapper);
|
package/src/models.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export const DEFAULT_GROK_MODEL = "grok-4.
|
|
1
|
+
export const DEFAULT_GROK_MODEL = "grok-4.7";
|
|
2
2
|
export const GROK_PROVIDER = "grok_build_cli";
|
|
3
|
+
const RETAINED_GROK_MODELS = ["grok-4.6"];
|
|
3
4
|
|
|
4
5
|
export function isGrokModel(model) {
|
|
5
6
|
return typeof model === "string" && model.startsWith("grok-");
|
|
@@ -27,10 +28,10 @@ export function extraGrokModels(env = process.env) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export function catalogModelIds(env = process.env) {
|
|
30
|
-
const ids = [
|
|
31
|
-
const seen = new Set(
|
|
32
|
-
for (const id of extraGrokModels(env)) {
|
|
33
|
-
if (seen.has(id)) continue;
|
|
31
|
+
const ids = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
for (const id of [DEFAULT_GROK_MODEL, ...RETAINED_GROK_MODELS, ...extraGrokModels(env)]) {
|
|
34
|
+
if (!isGrokModel(id) || seen.has(id)) continue;
|
|
34
35
|
seen.add(id);
|
|
35
36
|
ids.push(id);
|
|
36
37
|
}
|
package/src/paths.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import { homedir } from "node:os";
|
|
|
4
4
|
|
|
5
5
|
export const LINUX_CODEX_BINARY = "/usr/lib/chatgpt/resources/codex";
|
|
6
6
|
export const LINUX_CHATGPT_BIN = "/usr/lib/chatgpt/ChatGPT";
|
|
7
|
+
export const DARWIN_CODEX_CLI_BINARY =
|
|
8
|
+
"/Applications/Codex.app/Contents/Resources/codex-cli/bin/codex";
|
|
7
9
|
export const DARWIN_CODEX_BINARY = "/Applications/Codex.app/Contents/Resources/codex";
|
|
8
10
|
export const DARWIN_CODEX_APP = "/Applications/Codex.app";
|
|
9
11
|
export const LINUX_STOCK_PREFIX = "/usr/lib/chatgpt";
|
|
@@ -54,6 +56,8 @@ export function resolveCodexBinary(options = {}) {
|
|
|
54
56
|
if (platform === "win32") {
|
|
55
57
|
return resolveWin32Binary("codex.exe", "store-codex.txt", { ...options, env });
|
|
56
58
|
}
|
|
59
|
+
const exists = options.existsSync ?? existsSync;
|
|
60
|
+
if (exists(DARWIN_CODEX_CLI_BINARY)) return DARWIN_CODEX_CLI_BINARY;
|
|
57
61
|
return DARWIN_CODEX_BINARY;
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -93,14 +97,17 @@ export function linuxDesktopEntryPath(home = homedir(), xdgDataHome) {
|
|
|
93
97
|
return path.join(dataHome, "applications", "codex-grok.desktop");
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
export function desktopLaunchEnv({ wrapper, userData, pathPrefix }) {
|
|
97
|
-
const
|
|
100
|
+
export function desktopLaunchEnv({ wrapper, userData, pathPrefix, env = process.env }) {
|
|
101
|
+
const next = {
|
|
98
102
|
CODEX_CLI_PATH: wrapper,
|
|
99
103
|
CODEX_APP_SERVER_FORCE_CLI: "1",
|
|
100
104
|
CODEX_ELECTRON_USER_DATA_PATH: userData,
|
|
101
105
|
};
|
|
102
|
-
if (pathPrefix)
|
|
103
|
-
|
|
106
|
+
if (pathPrefix) {
|
|
107
|
+
const inherited = env.PATH ?? env.Path ?? "";
|
|
108
|
+
next.PATH = inherited ? `${pathPrefix}${path.delimiter}${inherited}` : pathPrefix;
|
|
109
|
+
}
|
|
110
|
+
return next;
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
export function desktopLaunchArgs(userData) {
|
|
@@ -120,6 +127,25 @@ export function isGrokDesktopProcess(line, userData, options = {}) {
|
|
|
120
127
|
return /MacOS\/(ChatGPT|Codex)(\s|$)/.test(line);
|
|
121
128
|
}
|
|
122
129
|
|
|
130
|
+
export function parseWin32ProcessJson(text) {
|
|
131
|
+
const raw = String(text ?? "").trim();
|
|
132
|
+
if (!raw) return [];
|
|
133
|
+
const data = JSON.parse(raw);
|
|
134
|
+
return Array.isArray(data) ? data : [data];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function selectGrokDesktopPid(rows, userData, options = {}) {
|
|
138
|
+
const probePid = options.probePid;
|
|
139
|
+
for (const row of rows ?? []) {
|
|
140
|
+
const pid = Number(row?.ProcessId);
|
|
141
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
142
|
+
if (probePid != null && pid === Number(probePid)) continue;
|
|
143
|
+
const line = String(row?.CommandLine ?? "");
|
|
144
|
+
if (isGrokDesktopProcess(line, userData, options)) return pid;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
123
149
|
export function isForbiddenInstallDir(app) {
|
|
124
150
|
const resolved = path.resolve(app);
|
|
125
151
|
const norm = resolved.replace(/\\/g, "/").toLowerCase();
|
package/src/tools.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
isImageGenerationItem,
|
|
5
5
|
saveGeneratedImage,
|
|
6
6
|
} from "./imagegen.mjs";
|
|
7
|
-
import { resolveGrokModel } from "./models.mjs";
|
|
7
|
+
import { DEFAULT_GROK_MODEL, resolveGrokModel } from "./models.mjs";
|
|
8
8
|
|
|
9
9
|
const OBJECT_SCHEMA = { type: "object", properties: {} };
|
|
10
10
|
|
|
@@ -124,12 +124,11 @@ function mergeObjectFragments(fragments) {
|
|
|
124
124
|
for (const fragment of fragments) {
|
|
125
125
|
if (fragment.properties && typeof fragment.properties === "object")
|
|
126
126
|
Object.assign(properties, fragment.properties);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
127
|
+
const keys = Array.isArray(fragment.required) ? fragment.required : [];
|
|
128
|
+
required =
|
|
129
|
+
required === undefined
|
|
130
|
+
? keys.slice()
|
|
131
|
+
: required.filter((key) => keys.includes(key));
|
|
133
132
|
}
|
|
134
133
|
return { properties, required };
|
|
135
134
|
}
|
|
@@ -180,12 +179,14 @@ function usableParameters(parameters) {
|
|
|
180
179
|
: {}),
|
|
181
180
|
...merged.properties,
|
|
182
181
|
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
const intersection = merged.required ?? [];
|
|
183
|
+
const root = Array.isArray(parameters.required) ? parameters.required : [];
|
|
184
|
+
const required = [];
|
|
185
|
+
const seen = new Set();
|
|
186
|
+
for (const key of [...root, ...intersection]) {
|
|
187
|
+
if (seen.has(key)) continue;
|
|
188
|
+
seen.add(key);
|
|
189
|
+
required.push(key);
|
|
189
190
|
}
|
|
190
191
|
const first = fragments[0];
|
|
191
192
|
return emitObjectParameters(
|
|
@@ -407,11 +408,16 @@ function toProxyInput(input, map) {
|
|
|
407
408
|
// no way to answer "is Grok actually attached?" and either hedges or goes
|
|
408
409
|
// hunting through config files. Stated once, in the instructions, it costs a
|
|
409
410
|
// few tokens and removes the whole class of question.
|
|
410
|
-
export
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
411
|
+
export function transportProvenance(model = DEFAULT_GROK_MODEL) {
|
|
412
|
+
return (
|
|
413
|
+
"Transport: this request is served by the local Codex-Grok bridge — model " +
|
|
414
|
+
`${model} via the grok_build_cli provider, with Codex owning tools, history ` +
|
|
415
|
+
"and permissions. If asked whether Grok is attached to the Codex harness, " +
|
|
416
|
+
"this line is the authoritative answer and no tool call is needed to confirm it."
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export const TRANSPORT_PROVENANCE = transportProvenance();
|
|
415
421
|
|
|
416
422
|
// Codex still injects its imagegen skill. That skill's fallback is OpenAI
|
|
417
423
|
// (`image_gen` or a Python CLI). The model will read the skill and send the
|
|
@@ -447,9 +453,10 @@ export function toProxyRequest(body) {
|
|
|
447
453
|
request.tool_choice = proxyToolChoice(body.tool_choice, map);
|
|
448
454
|
request.parallel_tool_calls = body.parallel_tool_calls !== false;
|
|
449
455
|
}
|
|
456
|
+
const provenanceLine = transportProvenance(request.model);
|
|
450
457
|
const provenance = imageGenOn
|
|
451
|
-
? `${
|
|
452
|
-
:
|
|
458
|
+
? `${provenanceLine}\n\n${IMAGE_GENERATION_PROVENANCE}`
|
|
459
|
+
: provenanceLine;
|
|
453
460
|
request.instructions =
|
|
454
461
|
typeof body.instructions === "string" && body.instructions
|
|
455
462
|
? `${body.instructions}\n\n${provenance}`
|