codex-grok-bridge 1.0.5 → 1.5.0

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 1.5.0 — 2026-09-12
6
+
7
+ First cut that accepts **Windows** (`"os": ["darwin", "linux", "win32"]`).
8
+
9
+ - Isolated win32 wrapper under `%LOCALAPPDATA%\codex-grok-bridge`. Never writes WindowsApps.
10
+ - Launch reads a Store ChatGPT / `resources/codex.exe` pointer when the isolated copies are missing.
11
+ - Grok 4.7 drop prep: any `grok-*` id uses the Grok provider. Extra catalog slugs via `GROK_BRIDGE_MODELS`.
12
+ - UTM Windows 11 ARM64 guest: Node 22.23.2, `node --test` 157/0/3 on 1.5.0, official ChatGPT ARM64 MSIX `OpenAI.Codex_26.903.8094.0`, bundled CLI `codex-cli 0.153.4`, grok 1.0.25, loopback `/v1/models` 200. See `docs/windows-arm64-guest.md`.
13
+
14
+ Install: `npm install -g codex-grok-bridge@1.5.0`
15
+
3
16
  ## 1.0.5 — 2026-09-12
4
17
 
5
18
  First npm cut that accepts **Linux** (`"os": ["darwin", "linux"]`). Windows stays rejected.
package/README.md CHANGED
@@ -11,10 +11,10 @@ Codex UI/CLI → app-server → scripts/codex-wrapper.mjs (adds grok-4.6 to the
11
11
  → Codex executes every tool call; results return as the next input
12
12
  ```
13
13
 
14
- The published npm package installs on **macOS and Linux** (`"os": ["darwin",
15
- "linux"]`). Windows installs are rejected by npm. The official ChatGPT/Codex
16
- `.deb` stays untouched; `scripts/install-codex-grok-app.sh` writes a separate
17
- wrapper.
14
+ The published npm package installs on **macOS, Linux, and Windows** (`"os":
15
+ ["darwin", "linux", "win32"]`) starting at 1.5.0. Official ChatGPT/Codex
16
+ prefixes stay untouched; `scripts/install-codex-grok-app.sh` and
17
+ `scripts/install-codex-grok-app.ps1` write a separate wrapper.
18
18
 
19
19
 
20
20
  ---
@@ -29,18 +29,20 @@ The bridge does **not** execute Grok-native tools. It translates Codex tools int
29
29
  function calling, streams the upstream Responses events, and rewrites names back
30
30
  so Codex still recognizes them.
31
31
 
32
- Thirteen files under `src/`. Zero runtime dependencies. Node.js ≥ 22.
32
+ Fourteen files under `src/`. Zero runtime dependencies. Node.js ≥ 22.
33
33
 
34
- This is not a second-opinion review product, not a Grok 4.7 adapter, and not a
35
- Grok-native search product. If Codex exposes a web-search tool, Grok can call
34
+ This is not a second-opinion review product and not a Grok-native search
35
+ product. New Grok ids, including 4.7 when the CLI lists them, use the same
36
+ `grok-*` route. Set `GROK_BRIDGE_MODELS=grok-4.7` to show extras in the catalog
37
+ before they are the default. If Codex exposes a web-search tool, Grok can call
36
38
  that tool the same way it calls any other Codex tool.
37
39
 
38
40
  ## Requirements
39
41
 
40
- - macOS, or Linux with the official ChatGPT/Codex desktop package
42
+ - macOS, Linux, or Windows 11 with the official ChatGPT/Codex desktop package
41
43
  - Node.js ≥ 22
42
- - `/Applications/Codex.app` (macOS) or `/usr/lib/chatgpt/ChatGPT` (Linux)
43
- - `grok` CLI at `~/.grok/bin/grok`
44
+ - `/Applications/Codex.app` (macOS), `/usr/lib/chatgpt/ChatGPT` (Linux), or the official ChatGPT MSIX (Windows)
45
+ - `grok` CLI at `~/.grok/bin/grok` (Windows: `%USERPROFILE%\.grok\bin\grok.exe`)
44
46
  - a completed `grok login`
45
47
 
46
48
  The `.command` launcher resolves paths from its own location, so moving the
@@ -56,7 +58,7 @@ re-verification.
56
58
  ### Terminal (npm)
57
59
 
58
60
  ```sh
59
- npm install -g codex-grok-bridge # macOS or Linux, Node ≥ 22
61
+ npm install -g codex-grok-bridge # macOS, Linux, or Windows; Node ≥ 22
60
62
  codex-grok # launches Codex with Grok 4.6 available
61
63
  codex-grok exec --skip-git-repo-check --sandbox workspace-write 'your task'
62
64
  ```
@@ -69,7 +71,7 @@ starts, and tears the provider down with that process.
69
71
  ```sh
70
72
  git clone https://github.com/deximple/codex-grok-bridge.git
71
73
  cd codex-grok-bridge
72
- npm test # 150 tests, no network, no inference
74
+ npm test # 160 tests, no network, no inference
73
75
  node scripts/codex-grok.mjs
74
76
  ```
75
77
 
@@ -79,7 +81,8 @@ Double-click **Open Codex with Grok.command** in this folder, or keep a
79
81
  **separate** desktop wrapper in sync with
80
82
  `scripts/install-codex-grok-app.sh` (see [Desktop install and update](#desktop-install-and-update)).
81
83
  On Linux that wrapper is `~/.local/share/codex-grok-bridge/app` plus a user
82
- `.desktop` entry; the stock `/usr/lib/chatgpt` tree is not patched.
84
+ `.desktop` entry; the stock `/usr/lib/chatgpt` tree is not patched. On Windows
85
+ it is `%LOCALAPPDATA%\codex-grok-bridge`; `WindowsApps` is never written.
83
86
 
84
87
  In the new Codex window, **select Grok 4.6 / xAI before starting a new
85
88
  thread**. Existing GPT models stay on the list. A Codex window that was already
@@ -277,13 +280,14 @@ Start here when something breaks. Do not open `~/.grok/auth.json` or
277
280
  | `GROK_BRIDGE_TRANSPORT=fetch` | Use Node `fetch` instead of `node:http(s)` |
278
281
  | `GROK_BRIDGE_INFERENCE=cli` | Fall back to the CLI envelope path |
279
282
  | `GROK_BRIDGE_DIAGNOSTICS=off` | Do not write the JSONL log |
283
+ | `GROK_BRIDGE_MODELS` | Extra `grok-*` catalog ids (comma or space), e.g. `grok-4.7` |
280
284
  | `NODE` | Absolute `node` binary for the `.command` launcher / desktop scripts |
281
- | `CODEX_GROK_APP` | Alternate app path for `install-codex-grok-app.sh` (macOS: `/Applications/Codex Grok.app`; Linux: `~/.local/share/codex-grok-bridge/app`) |
285
+ | `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`) |
282
286
 
283
287
  ## Verify
284
288
 
285
289
  ```sh
286
- npm test # 150 tests, no remote inference
290
+ npm test # 160 tests, no remote inference
287
291
  npm run test:coverage # 80% line / branch / function gate
288
292
  npm run verify:app-server # real app-server routing; also runs against an installed bundle
289
293
  npm audit --omit=dev
@@ -298,6 +302,7 @@ inference. A live CLI check spends the user’s quota.
298
302
  ```sh
299
303
  sh scripts/install-codex-grok-app.sh # sync bridge JS only (default)
300
304
  sh scripts/install-codex-grok-app.sh --full # macOS: rebuild and sign the launcher applet
305
+ powershell -File scripts/install-codex-grok-app.ps1
301
306
  ```
302
307
 
303
308
  The default copies this checkout’s `src/` and `scripts/*.mjs` into the bundle,
@@ -310,7 +315,10 @@ On macOS the script updates an existing `Codex Grok.app`. It does not create
310
315
  one, and it does not touch `/Applications/Codex.app`. On Linux it creates
311
316
  `~/.local/share/codex-grok-bridge/app` and
312
317
  `~/.local/share/applications/codex-grok.desktop`, and it does not write
313
- `/usr/lib/chatgpt` or `/usr/share/applications/chatgpt.desktop`.
318
+ `/usr/lib/chatgpt` or `/usr/share/applications/chatgpt.desktop`. On Windows
319
+ `scripts/install-codex-grok-app.ps1` writes
320
+ `%LOCALAPPDATA%\codex-grok-bridge` and may point at a Store `ChatGPT.exe` /
321
+ `resources\codex.exe`; it never writes `WindowsApps`.
314
322
 
315
323
  ## Security notes
316
324
 
@@ -348,18 +356,20 @@ Grok 4.6을 Codex 모델 목록에 넣고, Codex의 `/v1/responses`를
348
356
  calling으로 옮기고, 상류 Responses 스트림을 전달한 뒤, Codex가 알아보는
349
357
  이름으로 되돌립니다.
350
358
 
351
- `src/` 아래 파일 13개. 런타임 의존성 없음. Node.js 22 이상.
359
+ `src/` 아래 파일 14개. 런타임 의존성 없음. Node.js 22 이상. 1.5.0부터 npm
360
+ `"os"`는 `darwin` / `linux` / `win32`입니다.
352
361
 
353
- 코드 리뷰 전용 제품이 아니고, Grok 4.7 어댑터도 아니며, Grok 네이티브 검색
354
- 제품도 아닙니다. Codex가 웹 검색 도구를 노출하면 Grok은 다른 Codex 도구와 같이
355
- 그 도구를 호출할 수 있습니다.
362
+ 코드 리뷰 전용 제품이 아니고 Grok 네이티브 검색 제품도 아닙니다. CLI가 새
363
+ id(4.7 포함)를 내놓으면 같은 `grok-*` 경로로 붙습니다. 기본 카탈로그에 먼저
364
+ 보이게 하려면 `GROK_BRIDGE_MODELS=grok-4.7`을 씁니다. Codex가 웹 검색 도구를
365
+ 노출하면 Grok은 다른 Codex 도구와 같이 그 도구를 호출할 수 있습니다.
356
366
 
357
367
  ## 필요한 것
358
368
 
359
- - macOS, 또는 공식 ChatGPT/Codex 데스크톱 패키지가 있는 Linux
369
+ - macOS, Linux, 또는 공식 ChatGPT/Codex 데스크톱이 있는 Windows 11
360
370
  - Node.js 22 이상
361
- - `/Applications/Codex.app` (macOS) 또는 `/usr/lib/chatgpt/ChatGPT` (Linux)
362
- - `~/.grok/bin/grok`
371
+ - `/Applications/Codex.app` (macOS), `/usr/lib/chatgpt/ChatGPT` (Linux), 또는 공식 ChatGPT MSIX (Windows)
372
+ - `~/.grok/bin/grok` (Windows: `%USERPROFILE%\.grok\bin\grok.exe`)
363
373
  - 완료된 `grok login`
364
374
 
365
375
  `.command` 런처는 자기 위치를 기준으로 경로를 잡으므로 폴더를 옮겨도 수정할
@@ -374,7 +384,7 @@ calling으로 옮기고, 상류 Responses 스트림을 전달한 뒤, Codex가
374
384
  ### 터미널 (npm)
375
385
 
376
386
  ```sh
377
- npm install -g codex-grok-bridge # macOS or Linux, Node ≥ 22
387
+ npm install -g codex-grok-bridge # macOS, Linux, Windows; Node ≥ 22
378
388
  codex-grok # Grok 4.6이 있는 Codex를 띄움
379
389
  codex-grok exec --skip-git-repo-check --sandbox workspace-write '작업 내용'
380
390
  ```
@@ -387,7 +397,7 @@ codex-grok exec --skip-git-repo-check --sandbox workspace-write '작업 내용'
387
397
  ```sh
388
398
  git clone https://github.com/deximple/codex-grok-bridge.git
389
399
  cd codex-grok-bridge
390
- npm test # 150건, 네트워크·추론 없음
400
+ npm test # 160건, 네트워크·추론 없음
391
401
  node scripts/codex-grok.mjs
392
402
  ```
393
403
 
@@ -397,7 +407,8 @@ node scripts/codex-grok.mjs
397
407
  `scripts/install-codex-grok-app.sh`로 **별도의** 데스크톱 래퍼를 체크아웃과
398
408
  맞춥니다([데스크톱 설치와 갱신](#데스크톱-설치와-갱신)).
399
409
  Linux에서는 `~/.local/share/codex-grok-bridge/app`과 사용자 `.desktop`이고,
400
- 정품 `/usr/lib/chatgpt`는 패치하지 않습니다.
410
+ 정품 `/usr/lib/chatgpt`는 패치하지 않습니다. Windows에서는
411
+ `%LOCALAPPDATA%\codex-grok-bridge`이고 `WindowsApps`는 쓰지 않습니다.
401
412
 
402
413
  새로 열린 Codex 창에서 **새 작업을 시작하기 전에 Grok 4.6 / xAI를 선택**하세요.
403
414
  기존 GPT 모델도 목록에 남습니다. 이미 열려 있던 일반 Codex 창에는 이 확장이
@@ -584,13 +595,14 @@ provider에 Codex `request_max_retries` / `stream_max_retries`를 2로 두어
584
595
  | `GROK_BRIDGE_TRANSPORT=fetch` | `node:http(s)` 대신 Node `fetch` |
585
596
  | `GROK_BRIDGE_INFERENCE=cli` | CLI 봉투 경로로 폴백 |
586
597
  | `GROK_BRIDGE_DIAGNOSTICS=off` | JSONL 로그를 쓰지 않음 |
598
+ | `GROK_BRIDGE_MODELS` | 카탈로그에 더할 `grok-*` id (쉼표/공백). 예: `grok-4.7` |
587
599
  | `NODE` | `.command` / 데스크톱 스크립트가 쓸 `node` 절대 경로 |
588
- | `CODEX_GROK_APP` | `install-codex-grok-app.sh`가 쓸 앱 경로 (macOS: `/Applications/Codex Grok.app`; Linux: `~/.local/share/codex-grok-bridge/app`) |
600
+ | `CODEX_GROK_APP` | 데스크톱 설치기가 쓸 앱 경로 (macOS: `/Applications/Codex Grok.app`; Linux: `~/.local/share/codex-grok-bridge/app`; Windows: `%LOCALAPPDATA%\codex-grok-bridge\app`) |
589
601
 
590
602
  ## 검증
591
603
 
592
604
  ```sh
593
- npm test # 150건, 외부 추론 없음
605
+ npm test # 160건, 외부 추론 없음
594
606
  npm run test:coverage # line/branch/function 80% 게이트
595
607
  npm run verify:app-server # 실제 app-server 라우팅. 설치된 앱 번들에서도 실행
596
608
  npm audit --omit=dev
@@ -605,6 +617,7 @@ CLI 검증은 사용자 계정 사용량을 씁니다.
605
617
  ```sh
606
618
  sh scripts/install-codex-grok-app.sh # 브리지 JS만 동기화 (기본)
607
619
  sh scripts/install-codex-grok-app.sh --full # macOS: 런처 applet 재빌드 + 서명
620
+ powershell -File scripts/install-codex-grok-app.ps1
608
621
  ```
609
622
 
610
623
  기본 동작은 이 체크아웃의 `src/`와 `scripts/*.mjs`를 번들에 복사하고, 저장소에
@@ -615,7 +628,10 @@ sh scripts/install-codex-grok-app.sh --full # macOS: 런처 applet 재빌드 +
615
628
  macOS에서는 이미 있는 `Codex Grok.app`을 갱신하며 `/Applications/Codex.app`은
616
629
  건드리지 않습니다. Linux에서는 `~/.local/share/codex-grok-bridge/app`과
617
630
  `~/.local/share/applications/codex-grok.desktop`을 만들고 `/usr/lib/chatgpt`와
618
- 정품 `chatgpt.desktop`은 쓰지 않습니다.
631
+ 정품 `chatgpt.desktop`은 쓰지 않습니다. Windows에서는
632
+ `scripts/install-codex-grok-app.ps1`이 `%LOCALAPPDATA%\codex-grok-bridge`를
633
+ 쓰고 Store `ChatGPT.exe` / `resources\codex.exe`를 가리킬 수 있으며,
634
+ `WindowsApps`는 쓰지 않습니다.
619
635
 
620
636
  ## 보안
621
637
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-grok-bridge",
3
- "version": "1.0.5",
3
+ "version": "1.5.0",
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",
@@ -10,7 +10,8 @@
10
10
  },
11
11
  "os": [
12
12
  "darwin",
13
- "linux"
13
+ "linux",
14
+ "win32"
14
15
  ],
15
16
  "bin": {
16
17
  "codex-grok": "scripts/codex-grok.mjs"
@@ -36,6 +37,7 @@
36
37
  "scripts/codex-grok.mjs",
37
38
  "scripts/codex-wrapper.mjs",
38
39
  "scripts/install-codex-grok-app.sh",
40
+ "scripts/install-codex-grok-app.ps1",
39
41
  "scripts/launch-desktop.mjs",
40
42
  "Open Codex with Grok.command",
41
43
  "README.md",
@@ -0,0 +1,67 @@
1
+ # Install or update the dedicated Codex Grok desktop wrapper on Windows.
2
+ # Writes %LOCALAPPDATA%\codex-grok-bridge\app only. Never writes WindowsApps.
3
+ param(
4
+ [switch]$Force
5
+ )
6
+
7
+ $ErrorActionPreference = "Stop"
8
+ $Root = Split-Path -Parent $PSScriptRoot
9
+ $App = if ($env:CODEX_GROK_APP) { $env:CODEX_GROK_APP } else { Join-Path $env:LOCALAPPDATA "codex-grok-bridge\app" }
10
+ $UserData = Join-Path $env:LOCALAPPDATA "codex-grok-bridge\desktop"
11
+
12
+ if ($App -match "(?i)WindowsApps") {
13
+ Write-Error "refusing to install into the stock ChatGPT/Codex prefix: $App"
14
+ }
15
+
16
+ if (-not $Force) {
17
+ $hit = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
18
+ Where-Object { $_.CommandLine -and $_.CommandLine -like "*--user-data-dir=$UserData*" }
19
+ if ($hit) {
20
+ Write-Error "Codex Grok is running. Close its window first, or pass -Force."
21
+ }
22
+ }
23
+
24
+ New-Item -ItemType Directory -Force -Path (Join-Path $App "scripts"), (Join-Path $App "src") | Out-Null
25
+ Copy-Item (Join-Path $Root "scripts\*.mjs") (Join-Path $App "scripts") -Force
26
+ Copy-Item (Join-Path $Root "src\*.mjs") (Join-Path $App "src") -Force
27
+
28
+ $Node = if ($env:NODE) { $env:NODE } else { (Get-Command node -ErrorAction Stop).Source }
29
+ $Launcher = Join-Path $App "codex-grok-desktop.cmd"
30
+ @"
31
+ @echo off
32
+ set ROOT=%~dp0
33
+ "$Node" "%ROOT%scripts\launch-desktop.mjs" %*
34
+ "@ | Set-Content -Path $Launcher -Encoding ASCII
35
+
36
+ $StartDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
37
+ New-Item -ItemType Directory -Force -Path $StartDir | Out-Null
38
+ Copy-Item $Launcher (Join-Path $StartDir "Codex Grok.cmd") -Force
39
+
40
+ function Write-StorePointer([string]$Name, [string[]]$Filters) {
41
+ $pointerDir = Split-Path -Parent $App
42
+ if ($pointerDir -match "(?i)WindowsApps") { return }
43
+ $pkgs = @(Get-AppxPackage -AllUsers -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "OpenAI\.(ChatGPT|Codex)|ChatGPT" })
44
+ if (-not $pkgs) {
45
+ $pkgs = @(Get-AppxPackage -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "OpenAI\.(ChatGPT|Codex)|ChatGPT" })
46
+ }
47
+ foreach ($filter in $Filters) {
48
+ foreach ($pkg in $pkgs) {
49
+ if (-not $pkg.InstallLocation) { continue }
50
+ $hits = @(Get-ChildItem -Path $pkg.InstallLocation -Filter $filter -Recurse -ErrorAction SilentlyContinue)
51
+ $hit = $hits | Where-Object { $_.FullName -match '\\resources\\codex\.exe$' } | Select-Object -First 1
52
+ if (-not $hit) { $hit = $hits | Select-Object -First 1 }
53
+ if (-not $hit) { continue }
54
+ New-Item -ItemType Directory -Force -Path $pointerDir | Out-Null
55
+ Set-Content -Path (Join-Path $pointerDir $Name) -Value $hit.FullName -Encoding ASCII
56
+ Write-Output "store pointer $Name=$($hit.FullName)"
57
+ return
58
+ }
59
+ }
60
+ }
61
+
62
+ Write-StorePointer "store-app.txt" @("ChatGPT.exe", "Codex.exe")
63
+ Write-StorePointer "store-codex.txt" @("codex.exe")
64
+
65
+ Write-Output "win32 wrapper $Launcher"
66
+ Write-Output "bridge in $App matches $Root"
67
+ Write-Output "restart any open Codex Grok window to load it"
@@ -3,7 +3,8 @@
3
3
  #
4
4
  # macOS: sync JS into /Applications/Codex Grok.app (must already exist).
5
5
  # Linux: create ~/.local/share/codex-grok-bridge/app and a user .desktop.
6
- # Never writes /Applications/Codex.app or /usr/lib/chatgpt.
6
+ # Windows: create %LOCALAPPDATA%\codex-grok-bridge\app and a Start Menu .cmd.
7
+ # Never writes /Applications/Codex.app, /usr/lib/chatgpt, or WindowsApps.
7
8
  #
8
9
  # install-codex-grok-app.sh sync bridge JS (safe, fast)
9
10
  # install-codex-grok-app.sh --full macOS: rebuild applet; Linux: rewrite launcher
@@ -14,16 +15,24 @@
14
15
  set -eu
15
16
 
16
17
  ROOT="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)"
17
- USER_DATA="$HOME/.local/share/codex-grok-bridge/desktop"
18
18
  PLATFORM="${CODEX_GROK_PLATFORM:-$(uname -s)}"
19
19
  case "$PLATFORM" in
20
20
  Linux|linux) PLATFORM=linux ;;
21
21
  Darwin|darwin) PLATFORM=darwin ;;
22
+ MINGW*|MSYS*|CYGWIN*|Windows_NT|win32|windows) PLATFORM=win32 ;;
22
23
  esac
24
+ if [ "$PLATFORM" = win32 ]; then
25
+ USER_DATA="${LOCALAPPDATA:-$HOME/AppData/Local}/codex-grok-bridge/desktop"
26
+ else
27
+ USER_DATA="$HOME/.local/share/codex-grok-bridge/desktop"
28
+ fi
23
29
 
24
30
  if [ "$PLATFORM" = linux ]; then
25
31
  APP="${CODEX_GROK_APP:-$HOME/.local/share/codex-grok-bridge/app}"
26
32
  BRIDGE="$APP"
33
+ elif [ "$PLATFORM" = win32 ]; then
34
+ APP="${CODEX_GROK_APP:-${LOCALAPPDATA:-$HOME/AppData/Local}/codex-grok-bridge/app}"
35
+ BRIDGE="$APP"
27
36
  else
28
37
  APP="${CODEX_GROK_APP:-/Applications/Codex Grok.app}"
29
38
  BRIDGE="$APP/Contents/Resources/bridge"
@@ -41,18 +50,18 @@ for arg in "$@"; do
41
50
  done
42
51
 
43
52
  case "$APP" in
44
- /usr/lib/chatgpt|/usr/lib/chatgpt/*|/usr/bin/chatgpt|/Applications/Codex.app|/Applications/Codex.app/*)
53
+ /usr/lib/chatgpt|/usr/lib/chatgpt/*|/usr/bin/chatgpt|/Applications/Codex.app|/Applications/Codex.app/*|*WindowsApps*|*windowsapps*)
45
54
  echo "refusing to install into the stock ChatGPT/Codex prefix: $APP" >&2
46
55
  exit 1
47
56
  ;;
48
57
  esac
49
58
 
50
- if [ "$PLATFORM" != linux ] && [ ! -d "$APP/Contents" ]; then
59
+ if [ "$PLATFORM" != linux ] && [ "$PLATFORM" != win32 ] && [ ! -d "$APP/Contents" ]; then
51
60
  echo "missing $APP" >&2
52
61
  exit 1
53
62
  fi
54
63
 
55
- if [ "$FORCE" -eq 0 ] && pgrep -f -- "--user-data-dir=$USER_DATA" >/dev/null 2>&1; then
64
+ if [ "$FORCE" -eq 0 ] && [ "$PLATFORM" != win32 ] && pgrep -f -- "--user-data-dir=$USER_DATA" >/dev/null 2>&1; then
56
65
  echo "Codex Grok is running. Close its window first, or pass --force to swap" >&2
57
66
  echo "the files under it (the running window keeps its loaded code either way)." >&2
58
67
  exit 1
@@ -120,8 +129,28 @@ EOF
120
129
  echo "desktop entry $DESKTOP"
121
130
  }
122
131
 
132
+ write_win32_wrapper() {
133
+ NODE="${NODE:-$(command -v node || true)}"
134
+ [ -n "$NODE" ] || { echo "no node on PATH; set NODE=/path/to/node" >&2; exit 1; }
135
+ mkdir -p "$APP"
136
+ LAUNCHER="$APP/codex-grok-desktop.cmd"
137
+ NODE_WIN=$(printf "%s" "$NODE" | tr '/' '\\')
138
+ {
139
+ echo "@echo off"
140
+ echo "set ROOT=%~dp0"
141
+ echo "\"$NODE_WIN\" \"%ROOT%scripts\\launch-desktop.mjs\" %*"
142
+ } > "$LAUNCHER"
143
+ START_DIR="${APPDATA:-$HOME/AppData/Roaming}/Microsoft/Windows/Start Menu/Programs"
144
+ mkdir -p "$START_DIR"
145
+ cp "$LAUNCHER" "$START_DIR/Codex Grok.cmd"
146
+ echo "win32 wrapper $LAUNCHER"
147
+ echo "start menu $START_DIR/Codex Grok.cmd"
148
+ }
149
+
123
150
  if [ "$PLATFORM" = linux ]; then
124
151
  write_linux_wrapper
152
+ elif [ "$PLATFORM" = win32 ]; then
153
+ write_win32_wrapper
125
154
  elif [ "$FULL" -eq 1 ]; then
126
155
  NODE="${NODE:-$(command -v node || true)}"
127
156
  [ -n "$NODE" ] || { echo "no node on PATH; set NODE=/path/to/node" >&2; exit 1; }
@@ -21,7 +21,28 @@ function log(message) {
21
21
  appendFileSync(logFile, `${new Date().toISOString()} ${message}\n`);
22
22
  }
23
23
 
24
+ function grokPidWin32() {
25
+ const systemRoot = process.env.SystemRoot || "C:\\Windows";
26
+ const powershell = `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
27
+ try {
28
+ const out = execFileSync(
29
+ powershell,
30
+ [
31
+ "-NoProfile",
32
+ "-Command",
33
+ `Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like ${JSON.stringify(`*--user-data-dir=${userData}*`)} } | Select-Object -First 1 -ExpandProperty ProcessId`,
34
+ ],
35
+ { encoding: "utf8", windowsHide: true, timeout: 15000 },
36
+ );
37
+ const pid = Number(String(out).trim());
38
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
24
44
  function grokPid() {
45
+ if (platform === "win32") return grokPidWin32();
25
46
  const out = execFileSync("/bin/ps", ["-axo", "pid=,command="], {
26
47
  encoding: "utf8",
27
48
  });
@@ -53,6 +74,26 @@ function notify(text) {
53
74
  } catch {}
54
75
  }
55
76
 
77
+ function startWin32(wrapper) {
78
+ const app = resolveDesktopApp();
79
+ log(`start win32 app=${app} wrapper=${wrapper}`);
80
+ const child = spawn(app, desktopLaunchArgs(userData), {
81
+ stdio: "inherit",
82
+ env: {
83
+ ...process.env,
84
+ ...desktopLaunchEnv({ wrapper, userData }),
85
+ },
86
+ });
87
+ child.on("error", (error) => {
88
+ log(`spawn failed: ${error.message}`);
89
+ process.exit(1);
90
+ });
91
+ child.on("exit", (code) => {
92
+ log(`chatgpt exit=${code}`);
93
+ process.exit(code ?? 1);
94
+ });
95
+ }
96
+
56
97
  function startLinux(wrapper) {
57
98
  const app = resolveDesktopApp();
58
99
  const pathPrefix = `${path.dirname(process.execPath)}:/usr/local/bin:/usr/bin:/bin`;
@@ -127,6 +168,7 @@ try {
127
168
  }
128
169
  }
129
170
  if (platform === "linux") startLinux(wrapper);
171
+ else if (platform === "win32") startWin32(wrapper);
130
172
  else startDarwin(wrapper);
131
173
  } catch (error) {
132
174
  log(`fatal: ${error.message}`);
package/src/bridge.mjs CHANGED
@@ -13,35 +13,9 @@ import { toProxyRequest } from "./tools.mjs";
13
13
  import { classifyBridgeError, errorSignature, bridgeErrorMessage } from "./errors.mjs";
14
14
  import { createDiagnostics } from "./diagnostics.mjs";
15
15
  import { createSlots } from "./slots.mjs";
16
+ import { catalogModelInfos, isGrokModel, MODEL_INFO } from "./models.mjs";
16
17
 
17
- export const MODEL_INFO = {
18
- slug: "grok-4.6",
19
- display_name: "Grok 4.6 / xAI",
20
- description: "Grok 4.6 via grok login; Codex executes tools",
21
- default_reasoning_level: "high",
22
- supported_reasoning_levels: ["low", "medium", "high", "xhigh"].map(
23
- (effort) => ({ effort, description: effort }),
24
- ),
25
- shell_type: "unified_exec",
26
- visibility: "list",
27
- supported_in_api: true,
28
- priority: 50,
29
- availability_nux: null,
30
- upgrade: null,
31
- support_verbosity: false,
32
- default_verbosity: null,
33
- apply_patch_tool_type: "freeform",
34
- truncation_policy: { mode: "tokens", limit: 10000 },
35
- experimental_supported_tools: [],
36
- context_window: 500000,
37
- input_modalities: ["text", "image"],
38
- tool_mode: "direct",
39
- node_repl_disabled: true,
40
- model_messages: {
41
- instructions_template:
42
- "You are Grok 4.6 by xAI, running as the Codex model. Use the tools provided by Codex and respect its permissions. Complete the user request accurately.",
43
- },
44
- };
18
+ export { MODEL_INFO };
45
19
 
46
20
  export function publicBridgeError(error, context = {}) {
47
21
  if (error instanceof GrokAuthError) return error.message;
@@ -69,7 +43,7 @@ export function createBridgeServer(options = {}) {
69
43
  };
70
44
  const route = new URL(req.url, "http://127.0.0.1").pathname;
71
45
  if (req.method === "GET" && route === "/v1/models")
72
- return json(200, { models: [MODEL_INFO] });
46
+ return json(200, { models: catalogModelInfos() });
73
47
  if (req.method !== "POST" || route !== "/v1/responses")
74
48
  return json(404, { error: "Not found" });
75
49
  const auth = Buffer.from(req.headers.authorization ?? "");
@@ -95,7 +69,7 @@ export function createBridgeServer(options = {}) {
95
69
  }
96
70
  requestBytes = size;
97
71
  body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
98
- if (body?.model !== "grok-4.6" || !Array.isArray(body.input))
72
+ if (!isGrokModel(body?.model) || !Array.isArray(body.input))
99
73
  throw new Error();
100
74
  } catch {
101
75
  return json(400, { error: "Invalid request" });
@@ -5,6 +5,8 @@ import { mkdtemp, writeFile, rm } from "node:fs/promises";
5
5
  import { tmpdir, homedir } from "node:os";
6
6
  import path from "node:path";
7
7
  import { toImageBlocks } from "./images.mjs";
8
+ import { resolveGrokBinary } from "./paths.mjs";
9
+ import { resolveGrokModel } from "./models.mjs";
8
10
 
9
11
  // The GROK_BRIDGE_INFERENCE=cli fallback. It drives the Grok CLI once per turn
10
12
  // with the whole Codex request as a prompt and parses a JSON envelope back out.
@@ -59,7 +61,7 @@ export function buildGrokInvocation(body, options = {}) {
59
61
  "--single",
60
62
  extractLatestUserPrompt(body.input),
61
63
  "--model",
62
- "grok-4.6",
64
+ resolveGrokModel(body.model),
63
65
  "--output-format",
64
66
  "json",
65
67
  "--json-schema",
@@ -117,7 +119,7 @@ export function buildGrokInvocation(body, options = {}) {
117
119
  "You are a model backend for Codex. Return exactly the JSON envelope requested in the input. Do not execute your own tools. Tool calls in the envelope are executed by Codex.",
118
120
  ];
119
121
  return {
120
- binary: options.grokBinary ?? path.join(homedir(), ".grok/bin/grok"),
122
+ binary: options.grokBinary ?? resolveGrokBinary(homedir()),
121
123
  args,
122
124
  // Run the CLI where Codex is working, not where the bridge happens to live.
123
125
  cwd:
package/src/models.mjs ADDED
@@ -0,0 +1,121 @@
1
+ export const DEFAULT_GROK_MODEL = "grok-4.6";
2
+ export const GROK_PROVIDER = "grok_build_cli";
3
+
4
+ export function isGrokModel(model) {
5
+ return typeof model === "string" && model.startsWith("grok-");
6
+ }
7
+
8
+ export function resolveGrokModel(model) {
9
+ return isGrokModel(model) ? model : DEFAULT_GROK_MODEL;
10
+ }
11
+
12
+ export function grokDisplayName(id) {
13
+ return `Grok ${String(id).slice("grok-".length)}`;
14
+ }
15
+
16
+ export function extraGrokModels(env = process.env) {
17
+ const raw = env.GROK_BRIDGE_MODELS ?? "";
18
+ const seen = new Set();
19
+ const ids = [];
20
+ for (const part of raw.split(/[,\s]+/)) {
21
+ const id = part.trim();
22
+ if (!isGrokModel(id) || seen.has(id)) continue;
23
+ seen.add(id);
24
+ ids.push(id);
25
+ }
26
+ return ids;
27
+ }
28
+
29
+ export function catalogModelIds(env = process.env) {
30
+ const ids = [DEFAULT_GROK_MODEL];
31
+ const seen = new Set(ids);
32
+ for (const id of extraGrokModels(env)) {
33
+ if (seen.has(id)) continue;
34
+ seen.add(id);
35
+ ids.push(id);
36
+ }
37
+ return ids;
38
+ }
39
+
40
+ export function parseGrokCliModels(text) {
41
+ const ids = [];
42
+ const seen = new Set();
43
+ for (const line of String(text).split(/\r?\n/)) {
44
+ const match = line.match(/^\s*[*+-]\s+(grok-[^\s(]+)/);
45
+ if (!match || seen.has(match[1])) continue;
46
+ seen.add(match[1]);
47
+ ids.push(match[1]);
48
+ }
49
+ return ids;
50
+ }
51
+
52
+ export function modelEntry(id = DEFAULT_GROK_MODEL) {
53
+ const label = grokDisplayName(id);
54
+ return {
55
+ id,
56
+ model: id,
57
+ displayName: `${label} / xAI`,
58
+ description: `${label} · Codex tools`,
59
+ hidden: false,
60
+ isDefault: false,
61
+ upgrade: null,
62
+ upgradeInfo: null,
63
+ availabilityNux: null,
64
+ supportedReasoningEfforts: ["low", "medium", "high", "xhigh"].map(
65
+ (reasoningEffort) => ({ reasoningEffort, description: reasoningEffort }),
66
+ ),
67
+ defaultReasoningEffort: "high",
68
+ inputModalities: ["text", "image"],
69
+ supportsPersonality: false,
70
+ multiAgentVersion: null,
71
+ additionalSpeedTiers: [],
72
+ serviceTiers: [],
73
+ };
74
+ }
75
+
76
+ export function modelInfo(id = DEFAULT_GROK_MODEL) {
77
+ const label = grokDisplayName(id);
78
+ return {
79
+ slug: id,
80
+ display_name: `${label} / xAI`,
81
+ description: `${label} via grok login; Codex executes tools`,
82
+ default_reasoning_level: "high",
83
+ supported_reasoning_levels: ["low", "medium", "high", "xhigh"].map(
84
+ (effort) => ({ effort, description: effort }),
85
+ ),
86
+ shell_type: "unified_exec",
87
+ visibility: "list",
88
+ supported_in_api: true,
89
+ priority: 50,
90
+ availability_nux: null,
91
+ upgrade: null,
92
+ support_verbosity: false,
93
+ default_verbosity: null,
94
+ apply_patch_tool_type: "freeform",
95
+ truncation_policy: { mode: "tokens", limit: 10000 },
96
+ experimental_supported_tools: [],
97
+ context_window: 500000,
98
+ input_modalities: ["text", "image"],
99
+ tool_mode: "direct",
100
+ node_repl_disabled: true,
101
+ model_messages: {
102
+ instructions_template:
103
+ `You are ${label} by xAI, running as the Codex model. Use the tools provided by Codex and respect its permissions. Complete the user request accurately.`,
104
+ },
105
+ };
106
+ }
107
+
108
+ export const MODEL_ENTRY = modelEntry();
109
+ export const MODEL_INFO = modelInfo();
110
+
111
+ export function catalogModelEntries(env = process.env) {
112
+ return catalogModelIds(env).map((id) =>
113
+ id === DEFAULT_GROK_MODEL ? MODEL_ENTRY : modelEntry(id),
114
+ );
115
+ }
116
+
117
+ export function catalogModelInfos(env = process.env) {
118
+ return catalogModelIds(env).map((id) =>
119
+ id === DEFAULT_GROK_MODEL ? MODEL_INFO : modelInfo(id),
120
+ );
121
+ }
package/src/paths.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
2
3
  import { homedir } from "node:os";
3
4
 
4
5
  export const LINUX_CODEX_BINARY = "/usr/lib/chatgpt/resources/codex";
@@ -6,29 +7,80 @@ export const LINUX_CHATGPT_BIN = "/usr/lib/chatgpt/ChatGPT";
6
7
  export const DARWIN_CODEX_BINARY = "/Applications/Codex.app/Contents/Resources/codex";
7
8
  export const DARWIN_CODEX_APP = "/Applications/Codex.app";
8
9
  export const LINUX_STOCK_PREFIX = "/usr/lib/chatgpt";
10
+ export const WIN32_WINDOWSAPPS = "WindowsApps";
11
+ export const WIN32_BRIDGE_APP = "codex-grok-bridge";
9
12
 
10
13
  export function resolvePlatform(options = {}) {
11
14
  const env = options.env ?? process.env;
12
15
  return options.platform ?? env.CODEX_GROK_PLATFORM ?? process.platform;
13
16
  }
14
17
 
18
+ export function localAppData(home = homedir(), options = {}) {
19
+ const env = options.env ?? process.env;
20
+ if (env.LOCALAPPDATA) return env.LOCALAPPDATA;
21
+ return path.join(home, "AppData", "Local");
22
+ }
23
+
24
+ export function win32AppDir(home = homedir(), options = {}) {
25
+ return path.join(localAppData(home, options), WIN32_BRIDGE_APP, "app");
26
+ }
27
+
28
+ export function win32StorePointer(home = homedir(), options = {}, name = "store-app.txt") {
29
+ return path.join(localAppData(home, options), WIN32_BRIDGE_APP, name);
30
+ }
31
+
32
+ function readPointer(file, options = {}) {
33
+ const exists = options.existsSync ?? existsSync;
34
+ const read = options.readFileSync ?? ((p) => readFileSync(p, "utf8"));
35
+ if (!exists(file)) return "";
36
+ return String(read(file)).trim();
37
+ }
38
+
39
+ function resolveWin32Binary(fileName, pointerName, options = {}) {
40
+ const env = options.env ?? process.env;
41
+ const home = options.home ?? homedir();
42
+ const exists = options.existsSync ?? existsSync;
43
+ const isolated = path.join(win32AppDir(home, { env }), fileName);
44
+ if (exists(isolated)) return isolated;
45
+ const pointed = readPointer(win32StorePointer(home, { env }, pointerName), options);
46
+ return pointed || isolated;
47
+ }
48
+
15
49
  export function resolveCodexBinary(options = {}) {
16
50
  const env = options.env ?? process.env;
17
51
  if (env.CODEX_BINARY) return env.CODEX_BINARY;
18
- return resolvePlatform({ ...options, env }) === "linux"
19
- ? LINUX_CODEX_BINARY
20
- : DARWIN_CODEX_BINARY;
52
+ const platform = resolvePlatform({ ...options, env });
53
+ if (platform === "linux") return LINUX_CODEX_BINARY;
54
+ if (platform === "win32") {
55
+ return resolveWin32Binary("codex.exe", "store-codex.txt", { ...options, env });
56
+ }
57
+ return DARWIN_CODEX_BINARY;
21
58
  }
22
59
 
23
60
  export function resolveDesktopApp(options = {}) {
24
61
  const env = options.env ?? process.env;
25
62
  if (env.CODEX_DESKTOP_APP) return env.CODEX_DESKTOP_APP;
26
- return resolvePlatform({ ...options, env }) === "linux"
27
- ? LINUX_CHATGPT_BIN
28
- : DARWIN_CODEX_APP;
63
+ const platform = resolvePlatform({ ...options, env });
64
+ if (platform === "linux") return LINUX_CHATGPT_BIN;
65
+ if (platform === "win32") {
66
+ return resolveWin32Binary("ChatGPT.exe", "store-app.txt", { ...options, env });
67
+ }
68
+ return DARWIN_CODEX_APP;
29
69
  }
30
70
 
31
- export function desktopUserDataDir(home = homedir()) {
71
+ export function resolveGrokBinary(home = homedir(), options = {}) {
72
+ const env = options.env ?? process.env;
73
+ if (env.GROK_BINARY) return env.GROK_BINARY;
74
+ const platform = resolvePlatform({ ...options, env });
75
+ const name = platform === "win32" ? "grok.exe" : "grok";
76
+ return path.join(home, ".grok", "bin", name);
77
+ }
78
+
79
+ export function desktopUserDataDir(home = homedir(), options = {}) {
80
+ const env = options.env ?? process.env;
81
+ if (resolvePlatform({ ...options, env }) === "win32") {
82
+ return path.join(localAppData(home, { env }), WIN32_BRIDGE_APP, "desktop");
83
+ }
32
84
  return path.join(home, ".local/share/codex-grok-bridge/desktop");
33
85
  }
34
86
 
@@ -60,22 +112,39 @@ export function isGrokDesktopProcess(line, userData, options = {}) {
60
112
  if (line.includes("Helper") || line.includes("crashpad")) return false;
61
113
  const app = resolveDesktopApp(options);
62
114
  if (line.includes(app)) return true;
63
- return resolvePlatform(options) === "linux"
64
- ? line.includes(LINUX_CHATGPT_BIN)
65
- : /MacOS\/(ChatGPT|Codex)(\s|$)/.test(line);
115
+ const platform = resolvePlatform(options);
116
+ if (platform === "linux") return line.includes(LINUX_CHATGPT_BIN);
117
+ if (platform === "win32") {
118
+ return /ChatGPT\.exe|Codex\.exe/i.test(line);
119
+ }
120
+ return /MacOS\/(ChatGPT|Codex)(\s|$)/.test(line);
66
121
  }
67
122
 
68
123
  export function isForbiddenInstallDir(app) {
69
124
  const resolved = path.resolve(app);
125
+ const norm = resolved.replace(/\\/g, "/").toLowerCase();
126
+ const asPosix = String(app).replace(/\\/g, "/");
70
127
  const forbidden = [
71
128
  LINUX_STOCK_PREFIX,
72
129
  "/usr/bin/chatgpt",
73
130
  "/usr/share/applications/chatgpt.desktop",
74
131
  DARWIN_CODEX_APP,
75
132
  ];
76
- return forbidden.some(
77
- (prefix) => resolved === prefix || resolved.startsWith(`${prefix}/`),
78
- );
133
+ if (
134
+ forbidden.some((prefix) => {
135
+ const p = prefix.toLowerCase();
136
+ return (
137
+ asPosix === prefix ||
138
+ asPosix.startsWith(`${prefix}/`) ||
139
+ norm === p ||
140
+ norm.endsWith(p) ||
141
+ norm.includes(`${p}/`)
142
+ );
143
+ })
144
+ ) {
145
+ return true;
146
+ }
147
+ return norm.includes("/windowsapps/") || norm.endsWith("/windowsapps");
79
148
  }
80
149
 
81
150
  export function linuxDesktopEntry({ exec, icon = "chatgpt" }) {
package/src/proxy.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { homedir } from "node:os";
3
- import { join } from "node:path";
3
+ import { resolveGrokBinary } from "./paths.mjs";
4
4
  import { createSseRewriter } from "./tools.mjs";
5
5
  import { requestStream } from "./transport.mjs";
6
6
  import { BRIDGE_ERROR, classifyBridgeError } from "./errors.mjs";
@@ -15,7 +15,7 @@ export function detectGrokClientVersion(home = homedir()) {
15
15
  try {
16
16
  // Synchronous, and on the first request's critical path: a grok binary that
17
17
  // hangs would freeze the whole bridge event loop without a timeout.
18
- const output = execFileSync(join(home, ".grok/bin/grok"), ["--version"], {
18
+ const output = execFileSync(resolveGrokBinary(home), ["--version"], {
19
19
  encoding: "utf8",
20
20
  timeout: 5000,
21
21
  });
package/src/router.mjs CHANGED
@@ -1,25 +1,7 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
+ import { catalogModelEntries, isGrokModel, MODEL_ENTRY } from "./models.mjs";
2
3
 
3
- export const MODEL_ENTRY = {
4
- id: "grok-4.6",
5
- model: "grok-4.6",
6
- displayName: "Grok 4.6 / xAI",
7
- description: "Grok 4.6 · Codex tools",
8
- hidden: false,
9
- isDefault: false,
10
- upgrade: null,
11
- upgradeInfo: null,
12
- availabilityNux: null,
13
- supportedReasoningEfforts: ["low", "medium", "high", "xhigh"].map(
14
- (reasoningEffort) => ({ reasoningEffort, description: reasoningEffort }),
15
- ),
16
- defaultReasoningEffort: "high",
17
- inputModalities: ["text", "image"],
18
- supportsPersonality: false,
19
- multiAgentVersion: null,
20
- additionalSpeedTiers: [],
21
- serviceTiers: [],
22
- };
4
+ export { MODEL_ENTRY };
23
5
 
24
6
  export class Router {
25
7
  constructor(catalogPath) {
@@ -45,7 +27,7 @@ export class Router {
45
27
  const fresh = this.unstarted.get(p.threadId);
46
28
  const snapshot = fresh ?? await rpc("thread/resume", { threadId: p.threadId, excludeTurns: true });
47
29
  const model = selected ?? snapshot.model;
48
- const provider = model === "grok-4.6"
30
+ const provider = isGrokModel(model)
49
31
  ? "grok_build_cli"
50
32
  : model?.startsWith("gpt-") && snapshot.modelProvider === "grok_build_cli"
51
33
  ? "openai" : snapshot.modelProvider;
@@ -73,7 +55,7 @@ export class Router {
73
55
  const msg = structuredClone(message),
74
56
  p = msg.params ?? {};
75
57
  const model = p.collaborationMode?.settings?.model ?? p.model;
76
- const grok = model === "grok-4.6";
58
+ const grok = isGrokModel(model);
77
59
  if (
78
60
  ["thread/start", "thread/resume", "thread/fork"].includes(
79
61
  msg.method,
@@ -95,12 +77,12 @@ export class Router {
95
77
  this.pending.delete(msg.id);
96
78
  if (request.method === "thread/start" && msg.result?.thread?.id)
97
79
  this.unstarted.set(msg.result.thread.id, structuredClone(msg.result));
98
- if (
99
- request.method === "model/list" &&
100
- Array.isArray(msg.result?.data) &&
101
- !msg.result.data.some((m) => m.id === "grok-4.6")
102
- )
103
- msg.result.data.push(MODEL_ENTRY);
80
+ if (request.method === "model/list" && Array.isArray(msg.result?.data)) {
81
+ for (const entry of catalogModelEntries()) {
82
+ if (!msg.result.data.some((m) => m.id === entry.id))
83
+ msg.result.data.push(entry);
84
+ }
85
+ }
104
86
  if (
105
87
  ["thread/start", "thread/resume", "thread/fork"].includes(
106
88
  request.method,
package/src/runtime.mjs CHANGED
@@ -3,13 +3,14 @@ import { mkdtemp, writeFile, rm } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import path from "node:path";
5
5
  import { once } from "node:events";
6
- import { createBridgeServer, MODEL_INFO } from "./bridge.mjs";
6
+ import { createBridgeServer } from "./bridge.mjs";
7
+ import { catalogModelInfos } from "./models.mjs";
7
8
 
8
9
  export async function startRuntime(options = {}) {
9
10
  const token = randomBytes(32).toString("hex");
10
11
  const dir = await mkdtemp(path.join(tmpdir(), "codex-grok-runtime-"));
11
12
  const catalogPath = path.join(dir, "models.json");
12
- await writeFile(catalogPath, JSON.stringify({ models: [MODEL_INFO] }), {
13
+ await writeFile(catalogPath, JSON.stringify({ models: catalogModelInfos() }), {
13
14
  mode: 0o600,
14
15
  });
15
16
  const server = createBridgeServer({
package/src/tools.mjs CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  isImageGenerationItem,
5
5
  saveGeneratedImage,
6
6
  } from "./imagegen.mjs";
7
+ import { resolveGrokModel } from "./models.mjs";
7
8
 
8
9
  const OBJECT_SCHEMA = { type: "object", properties: {} };
9
10
 
@@ -426,7 +427,7 @@ export function toProxyRequest(body) {
426
427
  const { tools, map } = flattenCodexTools(body.tools ?? []);
427
428
  const effort = EFFORT[body.reasoning?.effort] ?? "high";
428
429
  const request = {
429
- model: "grok-4.6",
430
+ model: resolveGrokModel(body.model),
430
431
  input: toProxyInput(body.input, map),
431
432
  tools,
432
433
  reasoning: { effort },