ai-sdk-provider-codex-cli 2.1.0 → 2.1.2

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/dist/index.d.ts CHANGED
@@ -900,7 +900,21 @@ declare class ExecLanguageModel implements LanguageModelV4 {
900
900
  private mergeSettings;
901
901
  private getItemType;
902
902
  private buildArgs;
903
+ /**
904
+ * Apply MCP server settings as `-c` config overrides.
905
+ *
906
+ * Returns environment variables that must be set on the spawned codex
907
+ * process. Inline HTTP `bearerToken` secrets are routed through the child
908
+ * environment (via `bearer_token_env_var`) instead of argv, because command
909
+ * lines are readable by any local process (`ps`, /proc/<pid>/cmdline).
910
+ */
903
911
  private applyMcpSettings;
912
+ /**
913
+ * Pick a child-env variable name to carry an inline MCP bearer token,
914
+ * avoiding collisions between server names that normalize identically
915
+ * (e.g. 'foo-bar' and 'foo_bar').
916
+ */
917
+ private allocateBearerTokenEnvVar;
904
918
  private addConfigOverride;
905
919
  /**
906
920
  * Serialize a config override value into a CLI-safe string.
package/dist/index.js CHANGED
@@ -1443,7 +1443,7 @@ var ExecLanguageModel = class {
1443
1443
  if (settings.webSearch) {
1444
1444
  args.push("-c", "tools.web_search=true");
1445
1445
  }
1446
- this.applyMcpSettings(args, settings);
1446
+ const mcpSecretEnv = this.applyMcpSettings(args, settings);
1447
1447
  if (settings.color) {
1448
1448
  args.push("--color", settings.color);
1449
1449
  }
@@ -1491,6 +1491,7 @@ var ExecLanguageModel = class {
1491
1491
  const env = {
1492
1492
  ...process.env,
1493
1493
  ...settings.env || {},
1494
+ ...mcpSecretEnv,
1494
1495
  RUST_LOG: process.env.RUST_LOG || "error"
1495
1496
  };
1496
1497
  let lastMessagePath = settings.outputLastMessageFile;
@@ -1516,11 +1517,20 @@ var ExecLanguageModel = class {
1516
1517
  tempImagePaths: tempImagePaths.length > 0 ? tempImagePaths : void 0
1517
1518
  };
1518
1519
  }
1520
+ /**
1521
+ * Apply MCP server settings as `-c` config overrides.
1522
+ *
1523
+ * Returns environment variables that must be set on the spawned codex
1524
+ * process. Inline HTTP `bearerToken` secrets are routed through the child
1525
+ * environment (via `bearer_token_env_var`) instead of argv, because command
1526
+ * lines are readable by any local process (`ps`, /proc/<pid>/cmdline).
1527
+ */
1519
1528
  applyMcpSettings(args, settings) {
1520
1529
  if (settings.rmcpClient) {
1521
1530
  this.addConfigOverride(args, "features.rmcp_client", true);
1522
1531
  }
1523
- if (!settings.mcpServers) return;
1532
+ if (!settings.mcpServers) return void 0;
1533
+ const secretEnv = {};
1524
1534
  for (const [rawName, server] of Object.entries(settings.mcpServers)) {
1525
1535
  const name = assertValidMcpServerName(rawName);
1526
1536
  const prefix = `mcp_servers.${name}`;
@@ -1546,15 +1556,43 @@ var ExecLanguageModel = class {
1546
1556
  if (server.cwd) this.addConfigOverride(args, `${prefix}.cwd`, server.cwd);
1547
1557
  } else {
1548
1558
  this.addConfigOverride(args, `${prefix}.url`, server.url);
1549
- if (server.bearerTokenEnvVar)
1559
+ const hasExplicitAuthorizationHeader = Object.keys(server.httpHeaders ?? {}).some(
1560
+ (key) => key.toLowerCase() === "authorization"
1561
+ );
1562
+ if (server.bearerTokenEnvVar) {
1550
1563
  this.addConfigOverride(args, `${prefix}.bearer_token_env_var`, server.bearerTokenEnvVar);
1551
- const httpHeaders = mcpHttpHeadersWithBearerToken(server);
1552
- if (httpHeaders !== void 0)
1553
- this.addConfigOverride(args, `${prefix}.http_headers`, httpHeaders);
1564
+ if (server.bearerToken !== void 0) {
1565
+ this.logger.warn(
1566
+ `[codex-cli] MCP server '${name}': both bearerToken and bearerTokenEnvVar are set; using bearerTokenEnvVar and ignoring the inline token.`
1567
+ );
1568
+ }
1569
+ } else if (server.bearerToken !== void 0 && !hasExplicitAuthorizationHeader) {
1570
+ const envVarName = this.allocateBearerTokenEnvVar(name, secretEnv);
1571
+ secretEnv[envVarName] = server.bearerToken;
1572
+ this.addConfigOverride(args, `${prefix}.bearer_token_env_var`, envVarName);
1573
+ }
1574
+ if (server.httpHeaders !== void 0)
1575
+ this.addConfigOverride(args, `${prefix}.http_headers`, server.httpHeaders);
1554
1576
  if (server.envHttpHeaders !== void 0)
1555
1577
  this.addConfigOverride(args, `${prefix}.env_http_headers`, server.envHttpHeaders);
1556
1578
  }
1557
1579
  }
1580
+ return Object.keys(secretEnv).length > 0 ? secretEnv : void 0;
1581
+ }
1582
+ /**
1583
+ * Pick a child-env variable name to carry an inline MCP bearer token,
1584
+ * avoiding collisions between server names that normalize identically
1585
+ * (e.g. 'foo-bar' and 'foo_bar').
1586
+ */
1587
+ allocateBearerTokenEnvVar(serverName, secretEnv) {
1588
+ const base = `CODEX_MCP_${serverName.toUpperCase().replace(/-/g, "_")}_BEARER_TOKEN`;
1589
+ let candidate = base;
1590
+ let suffix = 2;
1591
+ while (candidate in secretEnv) {
1592
+ candidate = `${base}_${suffix}`;
1593
+ suffix += 1;
1594
+ }
1595
+ return candidate;
1558
1596
  }
1559
1597
  addConfigOverride(args, key, value) {
1560
1598
  assertValidConfigOverrideKey(key);
@@ -4827,6 +4865,7 @@ var AppServerRpcClient = class extends EventEmitter {
4827
4865
  activeRequestContextsByTurn = /* @__PURE__ */ new Map();
4828
4866
  completedTurnIds = /* @__PURE__ */ new Set();
4829
4867
  lastStderr = "";
4868
+ lastCrashHadStderr = false;
4830
4869
  idleTimer;
4831
4870
  serverCapabilities;
4832
4871
  expectedExitSignal;
@@ -5033,6 +5072,7 @@ var AppServerRpcClient = class extends EventEmitter {
5033
5072
  const base = resolveCodexPath2(this.settings.codexPath);
5034
5073
  const args = [...base.args, "app-server", "--listen", "stdio://"];
5035
5074
  this.lastStderr = "";
5075
+ this.lastCrashHadStderr = false;
5036
5076
  this.expectedExitSignal = void 0;
5037
5077
  this.child = spawn(base.cmd, args, {
5038
5078
  stdio: ["pipe", "pipe", "pipe"],
@@ -5043,18 +5083,23 @@ var AppServerRpcClient = class extends EventEmitter {
5043
5083
  },
5044
5084
  cwd: this.settings.cwd
5045
5085
  });
5046
- this.child.stderr.setEncoding("utf8");
5047
- this.child.stderr.on("data", (chunk) => {
5048
- this.lastStderr += String(chunk);
5049
- if (this.lastStderr.length > 4e3) {
5050
- this.lastStderr = this.lastStderr.slice(-4e3);
5086
+ const child = this.child;
5087
+ let stderrBuf = "";
5088
+ child.stderr.setEncoding("utf8");
5089
+ child.stderr.on("data", (chunk) => {
5090
+ stderrBuf = (stderrBuf + String(chunk)).slice(-4e3);
5091
+ if (this.child === child) {
5092
+ this.lastStderr = stderrBuf;
5051
5093
  }
5052
5094
  });
5053
- this.child.on("error", (error) => {
5095
+ child.on("error", (error) => {
5054
5096
  this.logger.error(`[codex-app-server] process error: ${String(error)}`);
5055
- this.handleCrash(error);
5097
+ const base2 = String(error);
5098
+ const message = this.withStderrTail(base2, stderrBuf);
5099
+ this.lastCrashHadStderr = message !== base2;
5100
+ this.handleCrash(new Error(message));
5056
5101
  });
5057
- this.child.on("exit", (code, signal) => {
5102
+ child.on("exit", (code, signal) => {
5058
5103
  const message = `codex app-server exited (code=${String(code)}, signal=${String(signal)})`;
5059
5104
  const expected = this.state === "closed" || signal !== null && signal === this.expectedExitSignal;
5060
5105
  this.expectedExitSignal = void 0;
@@ -5062,11 +5107,29 @@ var AppServerRpcClient = class extends EventEmitter {
5062
5107
  this.logger.info(`[codex-app-server] ${message}`);
5063
5108
  return;
5064
5109
  }
5065
- this.logger.warn(`[codex-app-server] ${message}`);
5066
- this.handleCrash(new Error(message));
5110
+ const crashedPending = this.markCrashed();
5111
+ if (!crashedPending) return;
5112
+ let settled = false;
5113
+ const finish = () => {
5114
+ if (settled) return;
5115
+ settled = true;
5116
+ child.off("close", finish);
5117
+ clearTimeout(timer);
5118
+ const base2 = `codex app-server exited (code=${String(code)}, signal=${String(signal)})`;
5119
+ const crashMessage = this.withStderrTail(base2, stderrBuf);
5120
+ if (this.child === void 0) {
5121
+ this.lastStderr = stderrBuf;
5122
+ this.lastCrashHadStderr = crashMessage !== base2;
5123
+ }
5124
+ this.logger.warn(`[codex-app-server] ${crashMessage}`);
5125
+ this.rejectCrashedPending(crashedPending, new Error(crashMessage));
5126
+ };
5127
+ child.once("close", finish);
5128
+ const timer = setTimeout(finish, 120);
5129
+ timer.unref?.();
5067
5130
  });
5068
5131
  this.stdoutReader = readline.createInterface({
5069
- input: this.child.stdout,
5132
+ input: child.stdout,
5070
5133
  crlfDelay: Infinity
5071
5134
  });
5072
5135
  this.stdoutReader.on("line", (line) => this.handleLine(line));
@@ -5088,12 +5151,22 @@ var AppServerRpcClient = class extends EventEmitter {
5088
5151
  this.settings.connectionTimeoutMs ?? this.requestTimeoutMs
5089
5152
  );
5090
5153
  } catch (error) {
5091
- const message = String(error?.message ?? error);
5092
- if (message.includes("ENOENT") || message.includes("unknown subcommand")) {
5154
+ const raw = String(error?.message ?? error);
5155
+ if (raw.includes("unknown subcommand") || this.lastStderr.includes("unknown subcommand")) {
5156
+ throw new Error(
5157
+ this.withStderrTail(
5158
+ "codex app-server requires codex CLI >= 0.144.0. Run 'codex --version' to check."
5159
+ )
5160
+ );
5161
+ }
5162
+ if (raw.includes("ENOENT") || this.lastStderr.includes("ENOENT")) {
5093
5163
  throw new Error(
5094
- "codex app-server requires codex CLI >= 0.144.0. Run 'codex --version' to check."
5164
+ this.withStderrTail(
5165
+ "codex app-server failed to start: codex executable not found (ENOENT). Check that the codex CLI is installed and its native binary is intact \u2014 a corrupted @openai/codex npm install can cause this. Run 'codex --version' to verify."
5166
+ )
5095
5167
  );
5096
5168
  }
5169
+ const message = this.lastCrashHadStderr ? raw : this.withStderrTail(raw);
5097
5170
  throw createAPICallError({
5098
5171
  message: `Failed to initialize codex app-server: ${message}`,
5099
5172
  stderr: this.lastStderr,
@@ -5155,8 +5228,8 @@ var AppServerRpcClient = class extends EventEmitter {
5155
5228
  }
5156
5229
  this.writeQueue = Promise.resolve();
5157
5230
  }
5158
- handleCrash(error) {
5159
- if (this.state === "closed") return;
5231
+ markCrashed() {
5232
+ if (this.state === "closed" || this.state === "error") return void 0;
5160
5233
  this.state = "error";
5161
5234
  this.clearIdleTimer();
5162
5235
  this.stdoutReader?.close();
@@ -5166,11 +5239,9 @@ var AppServerRpcClient = class extends EventEmitter {
5166
5239
  this.child.kill("SIGTERM");
5167
5240
  this.child = void 0;
5168
5241
  }
5169
- for (const [id, pending] of this.pending.entries()) {
5170
- clearTimeout(pending.timer);
5171
- pending.reject(
5172
- new Error(`Request ${String(id)} failed after app-server crash: ${String(error)}`)
5173
- );
5242
+ const pending = new Map(this.pending);
5243
+ for (const [, entry] of pending) {
5244
+ clearTimeout(entry.timer);
5174
5245
  }
5175
5246
  this.pending.clear();
5176
5247
  this.threadLocks.clear();
@@ -5180,6 +5251,41 @@ var AppServerRpcClient = class extends EventEmitter {
5180
5251
  this.completedTurnIds.clear();
5181
5252
  this.serverCapabilities = void 0;
5182
5253
  this.writeQueue = Promise.resolve();
5254
+ return pending;
5255
+ }
5256
+ rejectCrashedPending(pending, error) {
5257
+ for (const [id, entry] of pending) {
5258
+ entry.reject(
5259
+ new Error(`Request ${String(id)} failed after app-server crash: ${String(error)}`)
5260
+ );
5261
+ }
5262
+ }
5263
+ handleCrash(error) {
5264
+ const pending = this.markCrashed();
5265
+ if (!pending) return;
5266
+ this.rejectCrashedPending(pending, error);
5267
+ }
5268
+ stderrExcerpt(raw) {
5269
+ if (!raw) return "";
5270
+ const escape = String.fromCharCode(27);
5271
+ const ansiEscapeSequence = new RegExp(
5272
+ `${escape}(?:\\[[0-?]*[ -/]*[@-~]|\\][^\\u0007]*(?:\\u0007|${escape}\\\\))`,
5273
+ "g"
5274
+ );
5275
+ const withoutAnsi = raw.replace(ansiEscapeSequence, "");
5276
+ const withoutControls = Array.from(withoutAnsi).filter((character) => {
5277
+ const code = character.charCodeAt(0);
5278
+ return character === "\n" || code >= 32 && (code < 127 || code > 159);
5279
+ }).join("");
5280
+ const excerpt = withoutControls.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-5).join("; ");
5281
+ if (excerpt.length > 600) {
5282
+ return `\u2026${excerpt.slice(-600)}`;
5283
+ }
5284
+ return excerpt;
5285
+ }
5286
+ withStderrTail(message, raw = this.lastStderr) {
5287
+ const excerpt = this.stderrExcerpt(raw);
5288
+ return excerpt ? `${message} | stderr (tail): ${excerpt}` : message;
5183
5289
  }
5184
5290
  handleLine(line) {
5185
5291
  const trimmed = line.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-sdk-provider-codex-cli",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "AI SDK v7 provider for OpenAI Codex CLI (use ChatGPT Plus/Pro subscription)",
5
5
  "keywords": [
6
6
  "ai-sdk",