codex-grok-bridge 1.0.5 → 1.6.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 +18 -0
- package/Open Codex with Grok.command +1 -1
- package/README.md +60 -42
- package/package.json +5 -3
- package/scripts/codex-grok.mjs +2 -1
- package/scripts/install-codex-grok-app.ps1 +93 -0
- package/scripts/install-codex-grok-app.sh +34 -5
- package/scripts/launch-desktop.mjs +79 -8
- package/src/bridge.mjs +4 -30
- package/src/cli-inference.mjs +4 -2
- package/src/models.mjs +122 -0
- package/src/paths.mjs +108 -17
- package/src/proxy.mjs +2 -2
- package/src/router.mjs +10 -28
- package/src/runtime.mjs +3 -2
- package/src/tools.mjs +28 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 1.6.0 — 2026-09-27
|
|
6
|
+
|
|
7
|
+
- Default catalog model is `grok-4.7` (`Grok 4.7 / xAI`). `grok-4.6` stays listed so existing threads still resolve.
|
|
8
|
+
- `grok-*` still routes to `grok_build_cli`. No other model ids were added. `GROK_BRIDGE_MODELS` still appends extra `grok-*` ids.
|
|
9
|
+
|
|
10
|
+
## 1.5.0 — 2026-09-12
|
|
11
|
+
|
|
12
|
+
First cut that accepts **Windows** (`"os": ["darwin", "linux", "win32"]`).
|
|
13
|
+
|
|
14
|
+
- Isolated win32 wrapper under `%LOCALAPPDATA%\codex-grok-bridge`. Never writes WindowsApps.
|
|
15
|
+
- Launch reads a Store ChatGPT / `resources/codex.exe` pointer when the isolated copies are missing.
|
|
16
|
+
- Grok 4.7 drop prep: any `grok-*` id uses the Grok provider. Extra catalog slugs via `GROK_BRIDGE_MODELS`.
|
|
17
|
+
- 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`.
|
|
18
|
+
|
|
19
|
+
Install: `npm install -g codex-grok-bridge@1.5.0`
|
|
20
|
+
|
|
3
21
|
## 1.0.5 — 2026-09-12
|
|
4
22
|
|
|
5
23
|
First npm cut that accepts **Linux** (`"os": ["darwin", "linux"]`). Windows stays rejected.
|
|
@@ -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,26 +1,27 @@
|
|
|
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
|
|
12
13
|
```
|
|
13
14
|
|
|
14
|
-
The published npm package installs on **macOS and
|
|
15
|
-
"linux"]`)
|
|
16
|
-
|
|
17
|
-
wrapper.
|
|
15
|
+
The published npm package installs on **macOS, Linux, and Windows** (`"os":
|
|
16
|
+
["darwin", "linux", "win32"]`) starting at 1.5.0. Official ChatGPT/Codex
|
|
17
|
+
prefixes stay untouched; `scripts/install-codex-grok-app.sh` and
|
|
18
|
+
`scripts/install-codex-grok-app.ps1` write a separate wrapper.
|
|
18
19
|
|
|
19
20
|
|
|
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.
|
|
@@ -29,18 +30,20 @@ The bridge does **not** execute Grok-native tools. It translates Codex tools int
|
|
|
29
30
|
function calling, streams the upstream Responses events, and rewrites names back
|
|
30
31
|
so Codex still recognizes them.
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
Fourteen files under `src/`. Zero runtime dependencies. Node.js ≥ 22.
|
|
33
34
|
|
|
34
|
-
This is not a second-opinion review product
|
|
35
|
-
|
|
35
|
+
This is not a second-opinion review product and not a Grok-native search
|
|
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
|
|
36
39
|
that tool the same way it calls any other Codex tool.
|
|
37
40
|
|
|
38
41
|
## Requirements
|
|
39
42
|
|
|
40
|
-
- macOS, or
|
|
43
|
+
- macOS, Linux, or Windows 11 with the official ChatGPT/Codex desktop package
|
|
41
44
|
- Node.js ≥ 22
|
|
42
|
-
- `/Applications/Codex.app` (macOS)
|
|
43
|
-
- `grok` CLI at `~/.grok/bin/grok`
|
|
45
|
+
- `/Applications/Codex.app` (macOS), `/usr/lib/chatgpt/ChatGPT` (Linux), or the official ChatGPT MSIX (Windows)
|
|
46
|
+
- `grok` CLI at `~/.grok/bin/grok` (Windows: `%USERPROFILE%\.grok\bin\grok.exe`)
|
|
44
47
|
- a completed `grok login`
|
|
45
48
|
|
|
46
49
|
The `.command` launcher resolves paths from its own location, so moving the
|
|
@@ -56,8 +59,8 @@ re-verification.
|
|
|
56
59
|
### Terminal (npm)
|
|
57
60
|
|
|
58
61
|
```sh
|
|
59
|
-
npm install -g codex-grok-bridge # macOS
|
|
60
|
-
codex-grok # launches Codex with Grok 4.
|
|
62
|
+
npm install -g codex-grok-bridge # macOS, Linux, or Windows; Node ≥ 22
|
|
63
|
+
codex-grok # launches Codex with Grok 4.7 available
|
|
61
64
|
codex-grok exec --skip-git-repo-check --sandbox workspace-write 'your task'
|
|
62
65
|
```
|
|
63
66
|
|
|
@@ -69,7 +72,7 @@ starts, and tears the provider down with that process.
|
|
|
69
72
|
```sh
|
|
70
73
|
git clone https://github.com/deximple/codex-grok-bridge.git
|
|
71
74
|
cd codex-grok-bridge
|
|
72
|
-
npm test #
|
|
75
|
+
npm test # 160 tests, no network, no inference
|
|
73
76
|
node scripts/codex-grok.mjs
|
|
74
77
|
```
|
|
75
78
|
|
|
@@ -79,11 +82,12 @@ Double-click **Open Codex with Grok.command** in this folder, or keep a
|
|
|
79
82
|
**separate** desktop wrapper in sync with
|
|
80
83
|
`scripts/install-codex-grok-app.sh` (see [Desktop install and update](#desktop-install-and-update)).
|
|
81
84
|
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.
|
|
85
|
+
`.desktop` entry; the stock `/usr/lib/chatgpt` tree is not patched. On Windows
|
|
86
|
+
it is `%LOCALAPPDATA%\codex-grok-bridge`; `WindowsApps` is never written.
|
|
83
87
|
|
|
84
|
-
In the new Codex window, **select Grok 4.
|
|
85
|
-
thread**. Existing GPT models stay on the list
|
|
86
|
-
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.
|
|
87
91
|
|
|
88
92
|
The dedicated window stores UI data under
|
|
89
93
|
`~/.local/share/codex-grok-bridge/desktop` and **shares** the normal Codex home
|
|
@@ -277,13 +281,14 @@ Start here when something breaks. Do not open `~/.grok/auth.json` or
|
|
|
277
281
|
| `GROK_BRIDGE_TRANSPORT=fetch` | Use Node `fetch` instead of `node:http(s)` |
|
|
278
282
|
| `GROK_BRIDGE_INFERENCE=cli` | Fall back to the CLI envelope path |
|
|
279
283
|
| `GROK_BRIDGE_DIAGNOSTICS=off` | Do not write the JSONL log |
|
|
284
|
+
| `GROK_BRIDGE_MODELS` | Extra `grok-*` catalog ids (comma or space) beyond `grok-4.7` and `grok-4.6` |
|
|
280
285
|
| `NODE` | Absolute `node` binary for the `.command` launcher / desktop scripts |
|
|
281
|
-
| `CODEX_GROK_APP` | Alternate app path for
|
|
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`) |
|
|
282
287
|
|
|
283
288
|
## Verify
|
|
284
289
|
|
|
285
290
|
```sh
|
|
286
|
-
npm test #
|
|
291
|
+
npm test # 160 tests, no remote inference
|
|
287
292
|
npm run test:coverage # 80% line / branch / function gate
|
|
288
293
|
npm run verify:app-server # real app-server routing; also runs against an installed bundle
|
|
289
294
|
npm audit --omit=dev
|
|
@@ -298,6 +303,7 @@ inference. A live CLI check spends the user’s quota.
|
|
|
298
303
|
```sh
|
|
299
304
|
sh scripts/install-codex-grok-app.sh # sync bridge JS only (default)
|
|
300
305
|
sh scripts/install-codex-grok-app.sh --full # macOS: rebuild and sign the launcher applet
|
|
306
|
+
powershell -File scripts/install-codex-grok-app.ps1
|
|
301
307
|
```
|
|
302
308
|
|
|
303
309
|
The default copies this checkout’s `src/` and `scripts/*.mjs` into the bundle,
|
|
@@ -310,7 +316,10 @@ On macOS the script updates an existing `Codex Grok.app`. It does not create
|
|
|
310
316
|
one, and it does not touch `/Applications/Codex.app`. On Linux it creates
|
|
311
317
|
`~/.local/share/codex-grok-bridge/app` and
|
|
312
318
|
`~/.local/share/applications/codex-grok.desktop`, and it does not write
|
|
313
|
-
`/usr/lib/chatgpt` or `/usr/share/applications/chatgpt.desktop`.
|
|
319
|
+
`/usr/lib/chatgpt` or `/usr/share/applications/chatgpt.desktop`. On Windows
|
|
320
|
+
`scripts/install-codex-grok-app.ps1` writes
|
|
321
|
+
`%LOCALAPPDATA%\codex-grok-bridge` and may point at a Store `ChatGPT.exe` /
|
|
322
|
+
`resources\codex.exe`; it never writes `WindowsApps`.
|
|
314
323
|
|
|
315
324
|
## Security notes
|
|
316
325
|
|
|
@@ -339,7 +348,7 @@ responses. Codex’s and Grok’s own retention policies still apply.
|
|
|
339
348
|
|
|
340
349
|
## 이게 뭔가
|
|
341
350
|
|
|
342
|
-
Grok 4.
|
|
351
|
+
Grok 4.7을 Codex 모델 목록에 넣고, Codex의 `/v1/responses`를
|
|
343
352
|
`cli-chat-proxy.grok.com`으로 넘기는 로컬 브리지입니다. 추론은 Grok이 합니다.
|
|
344
353
|
도구 호출(셸, 패치, MCP, …)은 Codex가 실행하고, 결과는 다음 요청의 `input`으로
|
|
345
354
|
돌아갑니다. GPT 경로와 같은 에이전트 루프입니다.
|
|
@@ -348,18 +357,21 @@ Grok 4.6을 Codex 모델 목록에 넣고, Codex의 `/v1/responses`를
|
|
|
348
357
|
calling으로 옮기고, 상류 Responses 스트림을 전달한 뒤, Codex가 알아보는
|
|
349
358
|
이름으로 되돌립니다.
|
|
350
359
|
|
|
351
|
-
`src/` 아래 파일
|
|
360
|
+
`src/` 아래 파일 14개. 런타임 의존성 없음. Node.js 22 이상. 1.5.0부터 npm
|
|
361
|
+
`"os"`는 `darwin` / `linux` / `win32`입니다.
|
|
352
362
|
|
|
353
|
-
코드 리뷰 전용 제품이
|
|
354
|
-
|
|
355
|
-
|
|
363
|
+
코드 리뷰 전용 제품이 아니고 Grok 네이티브 검색 제품도 아닙니다. 피커에는
|
|
364
|
+
`grok-4.7`(Grok 4.7 / xAI)이 기본으로 보이고 `grok-4.6`도 남습니다. 그 외
|
|
365
|
+
`grok-*` id도 같은 경로로 붙고, `GROK_BRIDGE_MODELS`로 카탈로그에 더합니다.
|
|
366
|
+
Codex가 웹 검색 도구를 노출하면 Grok은 다른 Codex 도구와 같이 그 도구를
|
|
367
|
+
호출할 수 있습니다.
|
|
356
368
|
|
|
357
369
|
## 필요한 것
|
|
358
370
|
|
|
359
|
-
- macOS, 또는 공식 ChatGPT/Codex
|
|
371
|
+
- macOS, Linux, 또는 공식 ChatGPT/Codex 데스크톱이 있는 Windows 11
|
|
360
372
|
- Node.js 22 이상
|
|
361
|
-
- `/Applications/Codex.app` (macOS)
|
|
362
|
-
- `~/.grok/bin/grok`
|
|
373
|
+
- `/Applications/Codex.app` (macOS), `/usr/lib/chatgpt/ChatGPT` (Linux), 또는 공식 ChatGPT MSIX (Windows)
|
|
374
|
+
- `~/.grok/bin/grok` (Windows: `%USERPROFILE%\.grok\bin\grok.exe`)
|
|
363
375
|
- 완료된 `grok login`
|
|
364
376
|
|
|
365
377
|
`.command` 런처는 자기 위치를 기준으로 경로를 잡으므로 폴더를 옮겨도 수정할
|
|
@@ -374,8 +386,8 @@ calling으로 옮기고, 상류 Responses 스트림을 전달한 뒤, Codex가
|
|
|
374
386
|
### 터미널 (npm)
|
|
375
387
|
|
|
376
388
|
```sh
|
|
377
|
-
npm install -g codex-grok-bridge # macOS
|
|
378
|
-
codex-grok # Grok 4.
|
|
389
|
+
npm install -g codex-grok-bridge # macOS, Linux, Windows; Node ≥ 22
|
|
390
|
+
codex-grok # Grok 4.7이 있는 Codex를 띄움
|
|
379
391
|
codex-grok exec --skip-git-repo-check --sandbox workspace-write '작업 내용'
|
|
380
392
|
```
|
|
381
393
|
|
|
@@ -387,7 +399,7 @@ codex-grok exec --skip-git-repo-check --sandbox workspace-write '작업 내용'
|
|
|
387
399
|
```sh
|
|
388
400
|
git clone https://github.com/deximple/codex-grok-bridge.git
|
|
389
401
|
cd codex-grok-bridge
|
|
390
|
-
npm test #
|
|
402
|
+
npm test # 160건, 네트워크·추론 없음
|
|
391
403
|
node scripts/codex-grok.mjs
|
|
392
404
|
```
|
|
393
405
|
|
|
@@ -397,11 +409,12 @@ node scripts/codex-grok.mjs
|
|
|
397
409
|
`scripts/install-codex-grok-app.sh`로 **별도의** 데스크톱 래퍼를 체크아웃과
|
|
398
410
|
맞춥니다([데스크톱 설치와 갱신](#데스크톱-설치와-갱신)).
|
|
399
411
|
Linux에서는 `~/.local/share/codex-grok-bridge/app`과 사용자 `.desktop`이고,
|
|
400
|
-
정품 `/usr/lib/chatgpt`는 패치하지 않습니다.
|
|
412
|
+
정품 `/usr/lib/chatgpt`는 패치하지 않습니다. Windows에서는
|
|
413
|
+
`%LOCALAPPDATA%\codex-grok-bridge`이고 `WindowsApps`는 쓰지 않습니다.
|
|
401
414
|
|
|
402
|
-
새로 열린 Codex 창에서 **새 작업을 시작하기 전에 Grok 4.
|
|
403
|
-
기존 GPT
|
|
404
|
-
주입되지 않습니다.
|
|
415
|
+
새로 열린 Codex 창에서 **새 작업을 시작하기 전에 Grok 4.7 / xAI를 선택**하세요.
|
|
416
|
+
기존 GPT 모델과 `grok-4.6`도 목록에 남습니다. 이미 열려 있던 일반 Codex 창에는
|
|
417
|
+
이 확장이 주입되지 않습니다.
|
|
405
418
|
|
|
406
419
|
전용 창은 UI 데이터를 `~/.local/share/codex-grok-bridge/desktop`에 두고,
|
|
407
420
|
계정·작업·설정은 기존 Codex 홈을 **공유**합니다. 작업 내용과 설정 변경은 다른
|
|
@@ -584,13 +597,14 @@ provider에 Codex `request_max_retries` / `stream_max_retries`를 2로 두어
|
|
|
584
597
|
| `GROK_BRIDGE_TRANSPORT=fetch` | `node:http(s)` 대신 Node `fetch` |
|
|
585
598
|
| `GROK_BRIDGE_INFERENCE=cli` | CLI 봉투 경로로 폴백 |
|
|
586
599
|
| `GROK_BRIDGE_DIAGNOSTICS=off` | JSONL 로그를 쓰지 않음 |
|
|
600
|
+
| `GROK_BRIDGE_MODELS` | `grok-4.7`과 `grok-4.6` 외에 카탈로그에 더할 `grok-*` id (쉼표/공백) |
|
|
587
601
|
| `NODE` | `.command` / 데스크톱 스크립트가 쓸 `node` 절대 경로 |
|
|
588
|
-
| `CODEX_GROK_APP` |
|
|
602
|
+
| `CODEX_GROK_APP` | 데스크톱 설치기가 쓸 앱 경로 (macOS: `/Applications/Codex Grok.app`; Linux: `~/.local/share/codex-grok-bridge/app`; Windows: `%LOCALAPPDATA%\codex-grok-bridge\app`) |
|
|
589
603
|
|
|
590
604
|
## 검증
|
|
591
605
|
|
|
592
606
|
```sh
|
|
593
|
-
npm test #
|
|
607
|
+
npm test # 160건, 외부 추론 없음
|
|
594
608
|
npm run test:coverage # line/branch/function 80% 게이트
|
|
595
609
|
npm run verify:app-server # 실제 app-server 라우팅. 설치된 앱 번들에서도 실행
|
|
596
610
|
npm audit --omit=dev
|
|
@@ -605,6 +619,7 @@ CLI 검증은 사용자 계정 사용량을 씁니다.
|
|
|
605
619
|
```sh
|
|
606
620
|
sh scripts/install-codex-grok-app.sh # 브리지 JS만 동기화 (기본)
|
|
607
621
|
sh scripts/install-codex-grok-app.sh --full # macOS: 런처 applet 재빌드 + 서명
|
|
622
|
+
powershell -File scripts/install-codex-grok-app.ps1
|
|
608
623
|
```
|
|
609
624
|
|
|
610
625
|
기본 동작은 이 체크아웃의 `src/`와 `scripts/*.mjs`를 번들에 복사하고, 저장소에
|
|
@@ -615,7 +630,10 @@ sh scripts/install-codex-grok-app.sh --full # macOS: 런처 applet 재빌드 +
|
|
|
615
630
|
macOS에서는 이미 있는 `Codex Grok.app`을 갱신하며 `/Applications/Codex.app`은
|
|
616
631
|
건드리지 않습니다. Linux에서는 `~/.local/share/codex-grok-bridge/app`과
|
|
617
632
|
`~/.local/share/applications/codex-grok.desktop`을 만들고 `/usr/lib/chatgpt`와
|
|
618
|
-
정품 `chatgpt.desktop`은 쓰지 않습니다.
|
|
633
|
+
정품 `chatgpt.desktop`은 쓰지 않습니다. Windows에서는
|
|
634
|
+
`scripts/install-codex-grok-app.ps1`이 `%LOCALAPPDATA%\codex-grok-bridge`를
|
|
635
|
+
쓰고 Store `ChatGPT.exe` / `resources\codex.exe`를 가리킬 수 있으며,
|
|
636
|
+
`WindowsApps`는 쓰지 않습니다.
|
|
619
637
|
|
|
620
638
|
## 보안
|
|
621
639
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-grok-bridge",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Run Grok 4.
|
|
3
|
+
"version": "1.6.0",
|
|
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",
|
|
@@ -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",
|
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"
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
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
|
|
55
|
+
$Launcher = Join-Path $App "codex-grok-desktop.cmd"
|
|
56
|
+
@"
|
|
57
|
+
@echo off
|
|
58
|
+
set ROOT=%~dp0
|
|
59
|
+
"$Node" "%ROOT%scripts\launch-desktop.mjs" %*
|
|
60
|
+
"@ | Set-Content -Path $Launcher -Encoding ASCII
|
|
61
|
+
|
|
62
|
+
$StartDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
|
|
63
|
+
New-Item -ItemType Directory -Force -Path $StartDir | Out-Null
|
|
64
|
+
Copy-Item $Launcher (Join-Path $StartDir "Codex Grok.cmd") -Force
|
|
65
|
+
|
|
66
|
+
function Write-StorePointer([string]$Name, [string[]]$Filters) {
|
|
67
|
+
$pointerDir = Split-Path -Parent $App
|
|
68
|
+
if ($pointerDir -match "(?i)WindowsApps") { return }
|
|
69
|
+
$pkgs = @(Get-AppxPackage -AllUsers -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "OpenAI\.(ChatGPT|Codex)|ChatGPT" })
|
|
70
|
+
if (-not $pkgs) {
|
|
71
|
+
$pkgs = @(Get-AppxPackage -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "OpenAI\.(ChatGPT|Codex)|ChatGPT" })
|
|
72
|
+
}
|
|
73
|
+
foreach ($filter in $Filters) {
|
|
74
|
+
foreach ($pkg in $pkgs) {
|
|
75
|
+
if (-not $pkg.InstallLocation) { continue }
|
|
76
|
+
$hits = @(Get-ChildItem -Path $pkg.InstallLocation -Filter $filter -Recurse -ErrorAction SilentlyContinue)
|
|
77
|
+
$hit = $hits | Where-Object { $_.FullName -match '\\resources\\codex\.exe$' } | Select-Object -First 1
|
|
78
|
+
if (-not $hit) { $hit = $hits | Select-Object -First 1 }
|
|
79
|
+
if (-not $hit) { continue }
|
|
80
|
+
New-Item -ItemType Directory -Force -Path $pointerDir | Out-Null
|
|
81
|
+
Set-Content -Path (Join-Path $pointerDir $Name) -Value $hit.FullName -Encoding ASCII
|
|
82
|
+
Write-Output "store pointer $Name=$($hit.FullName)"
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Write-StorePointer "store-app.txt" @("ChatGPT.exe", "Codex.exe")
|
|
89
|
+
Write-StorePointer "store-codex.txt" @("codex.exe")
|
|
90
|
+
|
|
91
|
+
Write-Output "win32 wrapper $Launcher"
|
|
92
|
+
Write-Output "bridge in $App matches $Root"
|
|
93
|
+
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
|
-
#
|
|
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; }
|
|
@@ -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();
|
|
@@ -21,7 +23,44 @@ function log(message) {
|
|
|
21
23
|
appendFileSync(logFile, `${new Date().toISOString()} ${message}\n`);
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
function grokPidWin32() {
|
|
27
|
+
try {
|
|
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
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
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
|
+
|
|
24
62
|
function grokPid() {
|
|
63
|
+
if (platform === "win32") return grokPidWin32();
|
|
25
64
|
const out = execFileSync("/bin/ps", ["-axo", "pid=,command="], {
|
|
26
65
|
encoding: "utf8",
|
|
27
66
|
});
|
|
@@ -53,6 +92,34 @@ function notify(text) {
|
|
|
53
92
|
} catch {}
|
|
54
93
|
}
|
|
55
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
|
+
|
|
102
|
+
function startWin32(wrapper) {
|
|
103
|
+
const app = resolveDesktopApp();
|
|
104
|
+
const { command, args } = desktopCommand(app);
|
|
105
|
+
log(`start win32 app=${app} wrapper=${wrapper}`);
|
|
106
|
+
const child = spawn(command, args, {
|
|
107
|
+
stdio: "inherit",
|
|
108
|
+
env: {
|
|
109
|
+
...process.env,
|
|
110
|
+
...desktopLaunchEnv({ wrapper, userData }),
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
child.on("error", (error) => {
|
|
114
|
+
log(`spawn failed: ${error.message}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
117
|
+
child.on("exit", (code) => {
|
|
118
|
+
log(`chatgpt exit=${code}`);
|
|
119
|
+
process.exit(code ?? 1);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
56
123
|
function startLinux(wrapper) {
|
|
57
124
|
const app = resolveDesktopApp();
|
|
58
125
|
const pathPrefix = `${path.dirname(process.execPath)}:/usr/local/bin:/usr/bin:/bin`;
|
|
@@ -115,18 +182,22 @@ try {
|
|
|
115
182
|
const running = grokPid();
|
|
116
183
|
if (running) {
|
|
117
184
|
log(`activate pid=${running}`);
|
|
118
|
-
if (platform
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 {
|
|
124
196
|
process.exit(0);
|
|
125
|
-
} catch (error) {
|
|
126
|
-
log(`activate failed: ${error.message}`);
|
|
127
197
|
}
|
|
128
198
|
}
|
|
129
199
|
if (platform === "linux") startLinux(wrapper);
|
|
200
|
+
else if (platform === "win32") startWin32(wrapper);
|
|
130
201
|
else startDarwin(wrapper);
|
|
131
202
|
} catch (error) {
|
|
132
203
|
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
|
|
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:
|
|
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
|
|
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" });
|
package/src/cli-inference.mjs
CHANGED
|
@@ -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
|
-
|
|
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 ??
|
|
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,122 @@
|
|
|
1
|
+
export const DEFAULT_GROK_MODEL = "grok-4.7";
|
|
2
|
+
export const GROK_PROVIDER = "grok_build_cli";
|
|
3
|
+
const RETAINED_GROK_MODELS = ["grok-4.6"];
|
|
4
|
+
|
|
5
|
+
export function isGrokModel(model) {
|
|
6
|
+
return typeof model === "string" && model.startsWith("grok-");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function resolveGrokModel(model) {
|
|
10
|
+
return isGrokModel(model) ? model : DEFAULT_GROK_MODEL;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function grokDisplayName(id) {
|
|
14
|
+
return `Grok ${String(id).slice("grok-".length)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function extraGrokModels(env = process.env) {
|
|
18
|
+
const raw = env.GROK_BRIDGE_MODELS ?? "";
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
const ids = [];
|
|
21
|
+
for (const part of raw.split(/[,\s]+/)) {
|
|
22
|
+
const id = part.trim();
|
|
23
|
+
if (!isGrokModel(id) || seen.has(id)) continue;
|
|
24
|
+
seen.add(id);
|
|
25
|
+
ids.push(id);
|
|
26
|
+
}
|
|
27
|
+
return ids;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function catalogModelIds(env = process.env) {
|
|
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;
|
|
35
|
+
seen.add(id);
|
|
36
|
+
ids.push(id);
|
|
37
|
+
}
|
|
38
|
+
return ids;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseGrokCliModels(text) {
|
|
42
|
+
const ids = [];
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
for (const line of String(text).split(/\r?\n/)) {
|
|
45
|
+
const match = line.match(/^\s*[*+-]\s+(grok-[^\s(]+)/);
|
|
46
|
+
if (!match || seen.has(match[1])) continue;
|
|
47
|
+
seen.add(match[1]);
|
|
48
|
+
ids.push(match[1]);
|
|
49
|
+
}
|
|
50
|
+
return ids;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function modelEntry(id = DEFAULT_GROK_MODEL) {
|
|
54
|
+
const label = grokDisplayName(id);
|
|
55
|
+
return {
|
|
56
|
+
id,
|
|
57
|
+
model: id,
|
|
58
|
+
displayName: `${label} / xAI`,
|
|
59
|
+
description: `${label} · Codex tools`,
|
|
60
|
+
hidden: false,
|
|
61
|
+
isDefault: false,
|
|
62
|
+
upgrade: null,
|
|
63
|
+
upgradeInfo: null,
|
|
64
|
+
availabilityNux: null,
|
|
65
|
+
supportedReasoningEfforts: ["low", "medium", "high", "xhigh"].map(
|
|
66
|
+
(reasoningEffort) => ({ reasoningEffort, description: reasoningEffort }),
|
|
67
|
+
),
|
|
68
|
+
defaultReasoningEffort: "high",
|
|
69
|
+
inputModalities: ["text", "image"],
|
|
70
|
+
supportsPersonality: false,
|
|
71
|
+
multiAgentVersion: null,
|
|
72
|
+
additionalSpeedTiers: [],
|
|
73
|
+
serviceTiers: [],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function modelInfo(id = DEFAULT_GROK_MODEL) {
|
|
78
|
+
const label = grokDisplayName(id);
|
|
79
|
+
return {
|
|
80
|
+
slug: id,
|
|
81
|
+
display_name: `${label} / xAI`,
|
|
82
|
+
description: `${label} via grok login; Codex executes tools`,
|
|
83
|
+
default_reasoning_level: "high",
|
|
84
|
+
supported_reasoning_levels: ["low", "medium", "high", "xhigh"].map(
|
|
85
|
+
(effort) => ({ effort, description: effort }),
|
|
86
|
+
),
|
|
87
|
+
shell_type: "unified_exec",
|
|
88
|
+
visibility: "list",
|
|
89
|
+
supported_in_api: true,
|
|
90
|
+
priority: 50,
|
|
91
|
+
availability_nux: null,
|
|
92
|
+
upgrade: null,
|
|
93
|
+
support_verbosity: false,
|
|
94
|
+
default_verbosity: null,
|
|
95
|
+
apply_patch_tool_type: "freeform",
|
|
96
|
+
truncation_policy: { mode: "tokens", limit: 10000 },
|
|
97
|
+
experimental_supported_tools: [],
|
|
98
|
+
context_window: 500000,
|
|
99
|
+
input_modalities: ["text", "image"],
|
|
100
|
+
tool_mode: "direct",
|
|
101
|
+
node_repl_disabled: true,
|
|
102
|
+
model_messages: {
|
|
103
|
+
instructions_template:
|
|
104
|
+
`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.`,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const MODEL_ENTRY = modelEntry();
|
|
110
|
+
export const MODEL_INFO = modelInfo();
|
|
111
|
+
|
|
112
|
+
export function catalogModelEntries(env = process.env) {
|
|
113
|
+
return catalogModelIds(env).map((id) =>
|
|
114
|
+
id === DEFAULT_GROK_MODEL ? MODEL_ENTRY : modelEntry(id),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function catalogModelInfos(env = process.env) {
|
|
119
|
+
return catalogModelIds(env).map((id) =>
|
|
120
|
+
id === DEFAULT_GROK_MODEL ? MODEL_INFO : modelInfo(id),
|
|
121
|
+
);
|
|
122
|
+
}
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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;
|
|
69
|
+
}
|
|
70
|
+
|
|
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);
|
|
29
77
|
}
|
|
30
78
|
|
|
31
|
-
export function desktopUserDataDir(home = homedir()) {
|
|
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
|
|
|
@@ -41,14 +93,17 @@ export function linuxDesktopEntryPath(home = homedir(), xdgDataHome) {
|
|
|
41
93
|
return path.join(dataHome, "applications", "codex-grok.desktop");
|
|
42
94
|
}
|
|
43
95
|
|
|
44
|
-
export function desktopLaunchEnv({ wrapper, userData, pathPrefix }) {
|
|
45
|
-
const
|
|
96
|
+
export function desktopLaunchEnv({ wrapper, userData, pathPrefix, env = process.env }) {
|
|
97
|
+
const next = {
|
|
46
98
|
CODEX_CLI_PATH: wrapper,
|
|
47
99
|
CODEX_APP_SERVER_FORCE_CLI: "1",
|
|
48
100
|
CODEX_ELECTRON_USER_DATA_PATH: userData,
|
|
49
101
|
};
|
|
50
|
-
if (pathPrefix)
|
|
51
|
-
|
|
102
|
+
if (pathPrefix) {
|
|
103
|
+
const inherited = env.PATH ?? env.Path ?? "";
|
|
104
|
+
next.PATH = inherited ? `${pathPrefix}${path.delimiter}${inherited}` : pathPrefix;
|
|
105
|
+
}
|
|
106
|
+
return next;
|
|
52
107
|
}
|
|
53
108
|
|
|
54
109
|
export function desktopLaunchArgs(userData) {
|
|
@@ -60,22 +115,58 @@ export function isGrokDesktopProcess(line, userData, options = {}) {
|
|
|
60
115
|
if (line.includes("Helper") || line.includes("crashpad")) return false;
|
|
61
116
|
const app = resolveDesktopApp(options);
|
|
62
117
|
if (line.includes(app)) return true;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
118
|
+
const platform = resolvePlatform(options);
|
|
119
|
+
if (platform === "linux") return line.includes(LINUX_CHATGPT_BIN);
|
|
120
|
+
if (platform === "win32") {
|
|
121
|
+
return /ChatGPT\.exe|Codex\.exe/i.test(line);
|
|
122
|
+
}
|
|
123
|
+
return /MacOS\/(ChatGPT|Codex)(\s|$)/.test(line);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function parseWin32ProcessJson(text) {
|
|
127
|
+
const raw = String(text ?? "").trim();
|
|
128
|
+
if (!raw) return [];
|
|
129
|
+
const data = JSON.parse(raw);
|
|
130
|
+
return Array.isArray(data) ? data : [data];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function selectGrokDesktopPid(rows, userData, options = {}) {
|
|
134
|
+
const probePid = options.probePid;
|
|
135
|
+
for (const row of rows ?? []) {
|
|
136
|
+
const pid = Number(row?.ProcessId);
|
|
137
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
138
|
+
if (probePid != null && pid === Number(probePid)) continue;
|
|
139
|
+
const line = String(row?.CommandLine ?? "");
|
|
140
|
+
if (isGrokDesktopProcess(line, userData, options)) return pid;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
66
143
|
}
|
|
67
144
|
|
|
68
145
|
export function isForbiddenInstallDir(app) {
|
|
69
146
|
const resolved = path.resolve(app);
|
|
147
|
+
const norm = resolved.replace(/\\/g, "/").toLowerCase();
|
|
148
|
+
const asPosix = String(app).replace(/\\/g, "/");
|
|
70
149
|
const forbidden = [
|
|
71
150
|
LINUX_STOCK_PREFIX,
|
|
72
151
|
"/usr/bin/chatgpt",
|
|
73
152
|
"/usr/share/applications/chatgpt.desktop",
|
|
74
153
|
DARWIN_CODEX_APP,
|
|
75
154
|
];
|
|
76
|
-
|
|
77
|
-
(prefix) =>
|
|
78
|
-
|
|
155
|
+
if (
|
|
156
|
+
forbidden.some((prefix) => {
|
|
157
|
+
const p = prefix.toLowerCase();
|
|
158
|
+
return (
|
|
159
|
+
asPosix === prefix ||
|
|
160
|
+
asPosix.startsWith(`${prefix}/`) ||
|
|
161
|
+
norm === p ||
|
|
162
|
+
norm.endsWith(p) ||
|
|
163
|
+
norm.includes(`${p}/`)
|
|
164
|
+
);
|
|
165
|
+
})
|
|
166
|
+
) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return norm.includes("/windowsapps/") || norm.endsWith("/windowsapps");
|
|
79
170
|
}
|
|
80
171
|
|
|
81
172
|
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 {
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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:
|
|
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 { DEFAULT_GROK_MODEL, resolveGrokModel } from "./models.mjs";
|
|
7
8
|
|
|
8
9
|
const OBJECT_SCHEMA = { type: "object", properties: {} };
|
|
9
10
|
|
|
@@ -123,12 +124,11 @@ function mergeObjectFragments(fragments) {
|
|
|
123
124
|
for (const fragment of fragments) {
|
|
124
125
|
if (fragment.properties && typeof fragment.properties === "object")
|
|
125
126
|
Object.assign(properties, fragment.properties);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
127
|
+
const keys = Array.isArray(fragment.required) ? fragment.required : [];
|
|
128
|
+
required =
|
|
129
|
+
required === undefined
|
|
130
|
+
? keys.slice()
|
|
131
|
+
: required.filter((key) => keys.includes(key));
|
|
132
132
|
}
|
|
133
133
|
return { properties, required };
|
|
134
134
|
}
|
|
@@ -179,12 +179,14 @@ function usableParameters(parameters) {
|
|
|
179
179
|
: {}),
|
|
180
180
|
...merged.properties,
|
|
181
181
|
};
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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);
|
|
188
190
|
}
|
|
189
191
|
const first = fragments[0];
|
|
190
192
|
return emitObjectParameters(
|
|
@@ -406,11 +408,16 @@ function toProxyInput(input, map) {
|
|
|
406
408
|
// no way to answer "is Grok actually attached?" and either hedges or goes
|
|
407
409
|
// hunting through config files. Stated once, in the instructions, it costs a
|
|
408
410
|
// few tokens and removes the whole class of question.
|
|
409
|
-
export
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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();
|
|
414
421
|
|
|
415
422
|
// Codex still injects its imagegen skill. That skill's fallback is OpenAI
|
|
416
423
|
// (`image_gen` or a Python CLI). The model will read the skill and send the
|
|
@@ -426,7 +433,7 @@ export function toProxyRequest(body) {
|
|
|
426
433
|
const { tools, map } = flattenCodexTools(body.tools ?? []);
|
|
427
434
|
const effort = EFFORT[body.reasoning?.effort] ?? "high";
|
|
428
435
|
const request = {
|
|
429
|
-
model:
|
|
436
|
+
model: resolveGrokModel(body.model),
|
|
430
437
|
input: toProxyInput(body.input, map),
|
|
431
438
|
tools,
|
|
432
439
|
reasoning: { effort },
|
|
@@ -446,9 +453,10 @@ export function toProxyRequest(body) {
|
|
|
446
453
|
request.tool_choice = proxyToolChoice(body.tool_choice, map);
|
|
447
454
|
request.parallel_tool_calls = body.parallel_tool_calls !== false;
|
|
448
455
|
}
|
|
456
|
+
const provenanceLine = transportProvenance(request.model);
|
|
449
457
|
const provenance = imageGenOn
|
|
450
|
-
? `${
|
|
451
|
-
:
|
|
458
|
+
? `${provenanceLine}\n\n${IMAGE_GENERATION_PROVENANCE}`
|
|
459
|
+
: provenanceLine;
|
|
452
460
|
request.instructions =
|
|
453
461
|
typeof body.instructions === "string" && body.instructions
|
|
454
462
|
? `${body.instructions}\n\n${provenance}`
|