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