@jacobbd/relay-ai 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,9 +17,9 @@
17
17
 
18
18
  📺 **Watch the Demos**
19
19
 
20
- | **Claude Code / Cowork / Desktop** | **Codex CLI & Desktop App** | **Gemini CLI** |
21
- |:---:|:---:|:---:|
22
- | [![Claude Demo](https://img.youtube.com/vi/IvsUPHLhX0o/mqdefault.jpg)](https://youtu.be/IvsUPHLhX0o) | [![Codex Demo](https://img.youtube.com/vi/42oiOB8IAu4/mqdefault.jpg)](https://youtu.be/42oiOB8IAu4) | [![Gemini Demo](https://img.youtube.com/vi/g7JKvqOHJl4/mqdefault.jpg)](https://www.youtube.com/watch?v=g7JKvqOHJl4) |
20
+ | **Claude Code / Cowork / Desktop** | **Codex CLI & Desktop App** | **Gemini CLI** | **v0.4.1: UI & Antigravity** |
21
+ |:---:|:---:|:---:|:---:|
22
+ | [![Claude Demo](https://img.youtube.com/vi/IvsUPHLhX0o/mqdefault.jpg)](https://youtu.be/IvsUPHLhX0o) | [![Codex Demo](https://img.youtube.com/vi/42oiOB8IAu4/mqdefault.jpg)](https://youtu.be/42oiOB8IAu4) | [![Gemini Demo](https://img.youtube.com/vi/g7JKvqOHJl4/mqdefault.jpg)](https://www.youtube.com/watch?v=g7JKvqOHJl4) | [![UI & Antigravity Demo](https://img.youtube.com/vi/8vXJ0LfpdoY/mqdefault.jpg)](https://www.youtube.com/watch?v=8vXJ0LfpdoY) |
23
23
 
24
24
  **relay-ai** is an interactive CLI — and now a **visual launcher** — that connects AI coding tools to any provider and runs local API gateways on your machine. It supports **Claude Code**, **Claude Desktop (Cowork + Code)**, the **OpenAI Codex CLI**, the **ChatGPT desktop app in Codex mode (macOS + Windows)**, **Google Gemini CLI**, and experimental **Antigravity CLI / IDE** support.
25
25
 
@@ -453,7 +453,9 @@ Patches `~/.codex/config.toml` with backup; **Ctrl+C** in the relay-ai terminal
453
453
 
454
454
  See **[docs/CODEX.md](docs/CODEX.md)** for CLI vs app differences, file ownership, and troubleshooting.
455
455
 
456
- > **Known limitation — MCP tools (Context7, chrome-devtools, etc.) don't work with non-native models.** Codex wraps local `[mcp_servers.*]` tools in a proprietary, undocumented format that only Codex's own ChatGPT backend can dispatch. When routed through relay-ai (or *any* non-native model provider this also affects Ollama, OpenRouter, LiteLLM, LM Studio identically), the model can see and call the tools, but Codex's own dispatcher rejects every call with `unsupported call: ...`. This is a confirmed, currently open upstream bug ([openai/codex#20652](https://github.com/openai/codex/issues/20652)) there is no workaround on relay-ai's side. MCP tools work normally with Codex's native OpenAI/ChatGPT models. See the [MCP troubleshooting row in docs/CODEX.md](docs/CODEX.md#troubleshooting) for details.
456
+ > **MCP tools (Context7, chrome-devtools, the built-in browser, etc.) now work with non-native models.** Codex wraps local `[mcp_servers.*]` tools — and the built-in browser, which is itself exposed as an MCP tool — in a proprietary, undocumented format that only Codex's own ChatGPT backend used to be able to dispatch. relay-ai now translates this format on both the request and response side (splitting the model's flattened tool-call name back into Codex's expected `{namespace, name}` shape), so MCP tools and the browser work through any provider relay-ai routes to, not just Codex's native OpenAI/ChatGPT models.
457
+ >
458
+ > **Known limitation — image generation (`image_gen`) doesn't work with non-image-capable models.** Unlike MCP tools, which Codex executes locally, `image_gen` calls out to an OpenAI image backend (`POST /v1/images/generations`). relay-ai's proxy doesn't implement that endpoint, and even if it did, most registry models (DeepSeek, Grok, Gemini text models, etc.) can't generate images at all — the call fails with a 404. There is no workaround short of relay-ai adding a dedicated image-generation route to a genuinely image-capable provider, which isn't currently planned. Image generation works normally with Codex's native OpenAI/ChatGPT models.
457
459
 
458
460
  **Reasoning effort:** Capable models show Codex's native reasoning picker (low/medium/high, etc.). relay-ai maps your choice to each provider's SDK options and preserves existing `model_reasoning_effort` in Codex config. Claude Code `/effort` and the `relay-ai server` gateway use the same mapping — see the [reasoning section in docs/CODEX.md](docs/CODEX.md#reasoning-effort).
459
461
 
@@ -11,7 +11,7 @@ import { join } from "path";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@jacobbd/relay-ai",
14
- version: "0.4.3",
14
+ version: "0.4.5",
15
15
  publishConfig: {
16
16
  access: "public"
17
17
  },
@@ -1235,7 +1235,7 @@ function thinkingProviderOptions(npm) {
1235
1235
  // src/codex/app-profile.ts
1236
1236
  var CODEX_APP_PROVIDER_ID = "relay-ai-launch-codex-app";
1237
1237
  var PREVIEW_PROXY_PORT = 54321;
1238
- var CODEX_APP_AUTO_COMPACT_RATIO = 0.55;
1238
+ var CODEX_APP_AUTO_COMPACT_RATIO = 0.9;
1239
1239
  function codexAppModelSlug(rawModelId) {
1240
1240
  return rawModelId.startsWith("models/") ? rawModelId.slice("models/".length) : rawModelId;
1241
1241
  }
@@ -3830,6 +3830,7 @@ var FILE_MODE4 = 384;
3830
3830
  var CLAUDE_DEBUG_LOG = "claude-debug.log";
3831
3831
  var PROXY_DEBUG_LOG = "proxy-debug.log";
3832
3832
  var CODEX_PROXY_DEBUG_LOG = "codex-proxy-debug.log";
3833
+ var CODEX_BODY_DUMP_LOG = "codex-body-dump.jsonl";
3833
3834
  var GEMINI_PROXY_DEBUG_LOG = "gemini-proxy-debug.log";
3834
3835
  var PROVIDER_DEBUG_LOG = "provider-debug.log";
3835
3836
  var UI_DEBUG_LOG = "ui-debug.log";
@@ -3856,6 +3857,23 @@ function getProxyDebugLogPath() {
3856
3857
  function getCodexProxyDebugLogPath() {
3857
3858
  return join8(ensureLogsDir(), CODEX_PROXY_DEBUG_LOG);
3858
3859
  }
3860
+ function getCodexBodyDumpLogPath() {
3861
+ return join8(ensureLogsDir(), CODEX_BODY_DUMP_LOG);
3862
+ }
3863
+ function resetCodexBodyDumpLog() {
3864
+ resetTraceLog(getCodexBodyDumpLogPath());
3865
+ }
3866
+ function appendCodexBodyDump(entry) {
3867
+ ensureLogsDir();
3868
+ const path = getCodexBodyDumpLogPath();
3869
+ const redacted = redactTraceLine(JSON.stringify(entry));
3870
+ try {
3871
+ writeFileSync4(path, `${redacted}
3872
+ `, { flag: "a", mode: FILE_MODE4 });
3873
+ chmodSync4(path, FILE_MODE4);
3874
+ } catch {
3875
+ }
3876
+ }
3859
3877
  function getGeminiProxyDebugLogPath() {
3860
3878
  return join8(ensureLogsDir(), GEMINI_PROXY_DEBUG_LOG);
3861
3879
  }
@@ -8079,6 +8097,128 @@ async function runServerCommand(options = {}) {
8079
8097
  return 0;
8080
8098
  }
8081
8099
 
8100
+ // src/update-check.ts
8101
+ import {
8102
+ chmodSync as chmodSync5,
8103
+ mkdirSync as mkdirSync6,
8104
+ readFileSync as readFileSync10,
8105
+ renameSync as renameSync3,
8106
+ unlinkSync as unlinkSync2,
8107
+ writeFileSync as writeFileSync5
8108
+ } from "fs";
8109
+ import { join as join10 } from "path";
8110
+ var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
8111
+ var UPDATE_CHECK_TIMEOUT_MS = 2e3;
8112
+ var UPDATE_COMMAND = "npm install -g @jacobbd/relay-ai@latest";
8113
+ var REGISTRY_URL = "https://registry.npmjs.org/@jacobbd%2Frelay-ai/latest";
8114
+ var SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
8115
+ function parseVersion(version) {
8116
+ const match = SEMVER_PATTERN.exec(version);
8117
+ if (!match) return null;
8118
+ return {
8119
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
8120
+ prerelease: match[4]?.split(".") ?? []
8121
+ };
8122
+ }
8123
+ function comparePrerelease(current, latest) {
8124
+ if (current.length === 0 || latest.length === 0) {
8125
+ if (current.length === latest.length) return 0;
8126
+ return current.length === 0 ? -1 : 1;
8127
+ }
8128
+ const length = Math.max(current.length, latest.length);
8129
+ for (let i = 0; i < length; i++) {
8130
+ const currentPart = current[i];
8131
+ const latestPart = latest[i];
8132
+ if (currentPart === void 0) return 1;
8133
+ if (latestPart === void 0) return -1;
8134
+ if (currentPart === latestPart) continue;
8135
+ const currentNumber = /^\d+$/.test(currentPart) ? Number(currentPart) : null;
8136
+ const latestNumber = /^\d+$/.test(latestPart) ? Number(latestPart) : null;
8137
+ if (currentNumber !== null && latestNumber !== null) return latestNumber > currentNumber ? 1 : -1;
8138
+ if (currentNumber !== null) return 1;
8139
+ if (latestNumber !== null) return -1;
8140
+ return latestPart > currentPart ? 1 : -1;
8141
+ }
8142
+ return 0;
8143
+ }
8144
+ function isNewerVersion(currentVersion, latestVersion) {
8145
+ const current = parseVersion(currentVersion);
8146
+ const latest = parseVersion(latestVersion);
8147
+ if (!current || !latest) return false;
8148
+ for (let i = 0; i < current.core.length; i++) {
8149
+ if (current.core[i] === latest.core[i]) continue;
8150
+ return latest.core[i] > current.core[i];
8151
+ }
8152
+ return comparePrerelease(current.prerelease, latest.prerelease) > 0;
8153
+ }
8154
+ function cachePath() {
8155
+ return join10(getAppHome(), "update-check.json");
8156
+ }
8157
+ function readFreshCache(now) {
8158
+ try {
8159
+ const parsed = JSON.parse(readFileSync10(cachePath(), "utf8"));
8160
+ if (typeof parsed.latestVersion !== "string" || !parseVersion(parsed.latestVersion)) return null;
8161
+ if (typeof parsed.checkedAt !== "number" || !Number.isFinite(parsed.checkedAt)) return null;
8162
+ const age = now - parsed.checkedAt;
8163
+ if (age < 0 || age >= UPDATE_CHECK_TTL_MS) return null;
8164
+ return { latestVersion: parsed.latestVersion, checkedAt: parsed.checkedAt };
8165
+ } catch {
8166
+ return null;
8167
+ }
8168
+ }
8169
+ function writeCache(cache) {
8170
+ const directory = getAppHome();
8171
+ const path = cachePath();
8172
+ const temporaryPath = `${path}.${process.pid}.tmp`;
8173
+ try {
8174
+ mkdirSync6(directory, { recursive: true, mode: 448 });
8175
+ writeFileSync5(temporaryPath, `${JSON.stringify(cache)}
8176
+ `, { mode: 384 });
8177
+ renameSync3(temporaryPath, path);
8178
+ try {
8179
+ chmodSync5(path, 384);
8180
+ } catch {
8181
+ }
8182
+ } catch {
8183
+ try {
8184
+ unlinkSync2(temporaryPath);
8185
+ } catch {
8186
+ }
8187
+ }
8188
+ }
8189
+ function statusFor(latestVersion) {
8190
+ return {
8191
+ currentVersion: VERSION,
8192
+ latestVersion,
8193
+ updateAvailable: latestVersion !== null && isNewerVersion(VERSION, latestVersion)
8194
+ };
8195
+ }
8196
+ async function checkForUpdates(options = {}) {
8197
+ const now = options.now ?? Date.now();
8198
+ const cached = readFreshCache(now);
8199
+ if (cached) return statusFor(cached.latestVersion);
8200
+ const controller = new AbortController();
8201
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS);
8202
+ try {
8203
+ const response = await (options.fetchImpl ?? fetch)(REGISTRY_URL, {
8204
+ headers: { Accept: "application/json", "User-Agent": `relay-ai/${VERSION}` },
8205
+ signal: controller.signal
8206
+ });
8207
+ if (!response.ok) return statusFor(null);
8208
+ const body = await response.json();
8209
+ if (typeof body.version !== "string" || !parseVersion(body.version)) return statusFor(null);
8210
+ writeCache({ latestVersion: body.version, checkedAt: now });
8211
+ return statusFor(body.version);
8212
+ } catch {
8213
+ return statusFor(null);
8214
+ } finally {
8215
+ clearTimeout(timer);
8216
+ }
8217
+ }
8218
+ function formatUpdateNotification(currentVersion, latestVersion) {
8219
+ return `\u{1F514} Update available: ${currentVersion} \u2192 ${latestVersion}. Run ${UPDATE_COMMAND} to update.`;
8220
+ }
8221
+
8082
8222
  // src/favorite-provider-display.ts
8083
8223
  var OAUTH_FAVORITE_NAMES = {
8084
8224
  "claude-code": "Claude Code OAuth (Anthropic subscription)",
@@ -8099,16 +8239,16 @@ function favoriteProviderDisplayName(provider) {
8099
8239
  import { execSync as execSync2, spawn as spawn2 } from "child_process";
8100
8240
  import { existsSync as existsSync10 } from "fs";
8101
8241
  import { homedir as homedir7 } from "os";
8102
- import { join as join10 } from "path";
8242
+ import { join as join11 } from "path";
8103
8243
  var isWindows2 = process.platform === "win32";
8104
8244
  var OPENCODE_FALLBACK_PATHS = isWindows2 ? [
8105
- join10(process.env["APPDATA"] ?? homedir7(), "npm", "opencode.cmd"),
8106
- join10(process.env["APPDATA"] ?? homedir7(), "npm", "opencode"),
8107
- join10(homedir7(), "AppData", "Roaming", "npm", "opencode.cmd")
8245
+ join11(process.env["APPDATA"] ?? homedir7(), "npm", "opencode.cmd"),
8246
+ join11(process.env["APPDATA"] ?? homedir7(), "npm", "opencode"),
8247
+ join11(homedir7(), "AppData", "Roaming", "npm", "opencode.cmd")
8108
8248
  ] : [
8109
- join10(homedir7(), ".opencode", "bin", "opencode"),
8110
- join10(homedir7(), ".local", "bin", "opencode"),
8111
- join10(homedir7(), ".npm", "bin", "opencode"),
8249
+ join11(homedir7(), ".opencode", "bin", "opencode"),
8250
+ join11(homedir7(), ".local", "bin", "opencode"),
8251
+ join11(homedir7(), ".npm", "bin", "opencode"),
8112
8252
  "/usr/local/bin/opencode",
8113
8253
  "/opt/homebrew/bin/opencode"
8114
8254
  ];
@@ -9789,7 +9929,7 @@ ${pc6.bold("Device code (works on SSH/VPS):")}
9789
9929
  import { execSync as execSync3, spawn as spawn4 } from "child_process";
9790
9930
  import { existsSync as existsSync11, readdirSync, statSync as statSync3 } from "fs";
9791
9931
  import { homedir as homedir8 } from "os";
9792
- import { join as join11 } from "path";
9932
+ import { join as join12 } from "path";
9793
9933
  import * as p6 from "@clack/prompts";
9794
9934
  var CODEX_BUNDLE_ID = "com.openai.codex";
9795
9935
  var DARWIN_APP_NAMES = ["ChatGPT", "Codex"];
@@ -9808,33 +9948,33 @@ function runPowerShell(script) {
9808
9948
  function darwinAppCandidates() {
9809
9949
  return DARWIN_APP_NAMES.flatMap((name) => [
9810
9950
  `/Applications/${name}.app`,
9811
- join11(homedir8(), "Applications", `${name}.app`)
9951
+ join12(homedir8(), "Applications", `${name}.app`)
9812
9952
  ]);
9813
9953
  }
9814
9954
  function winLocalAppData() {
9815
- return process.env.LOCALAPPDATA ?? join11(homedir8(), "AppData", "Local");
9955
+ return process.env.LOCALAPPDATA ?? join12(homedir8(), "AppData", "Local");
9816
9956
  }
9817
9957
  function winCodexExeCandidates() {
9818
9958
  const local = winLocalAppData();
9819
9959
  const bases = WIN_APP_NAMES.flatMap((name) => [
9820
- join11(local, "Programs", name),
9821
- join11(local, "Programs", `OpenAI ${name}`),
9822
- join11(local, name),
9823
- join11(local, `OpenAI ${name}`),
9824
- join11(local, "OpenAI", name)
9960
+ join12(local, "Programs", name),
9961
+ join12(local, "Programs", `OpenAI ${name}`),
9962
+ join12(local, name),
9963
+ join12(local, `OpenAI ${name}`),
9964
+ join12(local, "OpenAI", name)
9825
9965
  ]);
9826
- bases.push(join11(local, "openai-codex-electron"), join11(local, "openai-chatgpt-electron"));
9966
+ bases.push(join12(local, "openai-codex-electron"), join12(local, "openai-chatgpt-electron"));
9827
9967
  const out = [];
9828
9968
  for (const base of bases) {
9829
9969
  for (const name of WIN_APP_NAMES) {
9830
- out.push(join11(base, `${name}.exe`));
9970
+ out.push(join12(base, `${name}.exe`));
9831
9971
  }
9832
9972
  try {
9833
9973
  if (existsSync11(base)) {
9834
9974
  for (const dir of readdirSync(base)) {
9835
9975
  if (dir.startsWith("app-")) {
9836
9976
  for (const name of WIN_APP_NAMES) {
9837
- out.push(join11(base, dir, `${name}.exe`));
9977
+ out.push(join12(base, dir, `${name}.exe`));
9838
9978
  }
9839
9979
  }
9840
9980
  }
@@ -10011,7 +10151,7 @@ function codexAppInstallHint() {
10011
10151
  import { execSync as execSync4, spawn as spawn5 } from "child_process";
10012
10152
  import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
10013
10153
  import { homedir as homedir9 } from "os";
10014
- import { join as join12 } from "path";
10154
+ import { join as join13 } from "path";
10015
10155
  import * as p7 from "@clack/prompts";
10016
10156
  var CLAUDE_BUNDLE_ID = "com.anthropic.claudefordesktop";
10017
10157
  function claudeAppSupported() {
@@ -10028,26 +10168,26 @@ function runPowerShell2(script) {
10028
10168
  function darwinAppCandidates2() {
10029
10169
  return [
10030
10170
  "/Applications/Claude.app",
10031
- join12(homedir9(), "Applications", "Claude.app")
10171
+ join13(homedir9(), "Applications", "Claude.app")
10032
10172
  ];
10033
10173
  }
10034
10174
  function winLocalAppData2() {
10035
- return process.env.LOCALAPPDATA ?? join12(homedir9(), "AppData", "Local");
10175
+ return process.env.LOCALAPPDATA ?? join13(homedir9(), "AppData", "Local");
10036
10176
  }
10037
10177
  function winClaudeExeCandidates() {
10038
10178
  const local = winLocalAppData2();
10039
10179
  const bases = [
10040
- join12(local, "Programs", "Claude"),
10041
- join12(local, "Claude")
10180
+ join13(local, "Programs", "Claude"),
10181
+ join13(local, "Claude")
10042
10182
  ];
10043
10183
  const out = [];
10044
10184
  for (const base of bases) {
10045
- out.push(join12(base, "Claude.exe"));
10185
+ out.push(join13(base, "Claude.exe"));
10046
10186
  try {
10047
10187
  if (existsSync12(base)) {
10048
10188
  for (const name of readdirSync2(base)) {
10049
10189
  if (name.startsWith("app-")) {
10050
- out.push(join12(base, name, "Claude.exe"));
10190
+ out.push(join13(base, name, "Claude.exe"));
10051
10191
  }
10052
10192
  }
10053
10193
  }
@@ -10332,6 +10472,8 @@ export {
10332
10472
  prepareClaudeTraceLog,
10333
10473
  getProxyDebugLogPath,
10334
10474
  getCodexProxyDebugLogPath,
10475
+ resetCodexBodyDumpLog,
10476
+ appendCodexBodyDump,
10335
10477
  getGeminiProxyDebugLogPath,
10336
10478
  getUiDebugLogPath,
10337
10479
  makeTraceLogger,
@@ -10385,6 +10527,8 @@ export {
10385
10527
  loadServerModels,
10386
10528
  resolveServerUpstreamApiKey,
10387
10529
  runServerCommand,
10530
+ checkForUpdates,
10531
+ formatUpdateNotification,
10388
10532
  favoriteProviderDisplayName,
10389
10533
  addProviderFromTemplate,
10390
10534
  removeProviderFromRegistry,
@@ -10408,4 +10552,4 @@ export {
10408
10552
  quitClaudeAppGracefully,
10409
10553
  launchOrRestartClaudeApp
10410
10554
  };
10411
- //# sourceMappingURL=chunk-VSXPAZX4.js.map
10555
+ //# sourceMappingURL=chunk-UTPRIJFE.js.map