@nyssance/codex-acp-v2 0.4.0 → 0.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/index.js +432 -84
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 — 2026-09-11
4
+
5
+ ### Added
6
+
7
+ - Several gateways at once in catalog mode: `providers/set` takes `_meta.codex.id`
8
+ (default `custom-gateway`); each gateway keeps its own base URL, key, config and models,
9
+ writes its own `model_providers.<id>` entry, and gets its own select group in every
10
+ session's `model` option. Model ids must be unique across gateways (`-32602` otherwise).
11
+ `providers/disable` with `_meta.codex.id` removes one gateway; `providers/list` reports
12
+ `_meta.codex.gateways`. Route mode is unchanged (one gateway).
13
+ - Pass-throughs: `_codex/session_rename` (`thread/name/set`), `_codex/account_read`
14
+ (`account/read`), `_codex/rate_limits` (`account/rateLimits/read`) and
15
+ `_codex/fuzzy_file_search` (`fuzzyFileSearch`); notifications
16
+ `_codex/rate_limits_updated`, `_codex/fuzzy_file_search_updated` and
17
+ `_codex/fuzzy_file_search_completed` carry Codex's payloads verbatim. Advertised as
18
+ `capabilities._meta.codex.rename` / `.account` / `.fuzzyFileSearch`.
19
+
20
+ ### Fixed
21
+
22
+ - A thread moved from one gateway to another no longer keeps the previous gateway's
23
+ `model_providers` entry (and bearer token) in its config.
24
+ - Re-registering one gateway no longer re-resumes sessions that sit on other gateways.
25
+
26
+ ## 0.5.0 — 2026-09-11
27
+
28
+ ### Added
29
+
30
+ - `providers/set` hints `_meta.codex.bearerToken` (written as `experimental_bearer_token`)
31
+ and `_meta.codex.config` (top-level Codex thread-config overrides that ride with the
32
+ gateway; a `model_catalog_json` file is read so gateway models carry their real reasoning
33
+ levels and modalities). `providers/disable` removes both from live threads.
34
+ - Catalog mode (`_meta.codex.mode: "catalog"`): the gateway's models are offered next to
35
+ Codex's in every session's `model` option as their own select group; selecting one moves
36
+ only that session to the gateway (unsubscribe + resume, materializing an empty thread
37
+ first), selecting a Codex model moves it back. `session/new` / `session/fork` honour
38
+ `_meta.alwith.model`; `session/resume` follows the provider Codex recorded for the thread.
39
+ Advertised as `capabilities._meta.codex.providerCatalog`.
40
+
41
+ ### Fixed
42
+
43
+ - A thread another Codex client is writing ("already has an active writer", a file lock
44
+ across the Codex home) opens as a read-only session through `thread/read`, like Codex's
45
+ TUI: history replays, the response carries `_meta.codex.readOnly`, prompts and config
46
+ changes are refused with `-32600`, and a later resume retries the real thing.
47
+
3
48
  ## 0.4.0 — 2026-09-10
4
49
 
5
50
  ### Added
package/dist/index.js CHANGED
@@ -3406,8 +3406,8 @@ var require_main = __commonJS(function(exports) {
3406
3406
  exports.createMessageConnection = createMessageConnection;
3407
3407
  var ril_1 = __importDefault(require_ril());
3408
3408
  ril_1.default.install();
3409
- var path7 = __importStar(__require("path"));
3410
- var os2 = __importStar(__require("os"));
3409
+ var path8 = __importStar(__require("path"));
3410
+ var os3 = __importStar(__require("os"));
3411
3411
  var fs7 = __importStar(__require("fs"));
3412
3412
  var crypto_1 = __require("crypto");
3413
3413
  var net_1 = __require("net");
@@ -3555,7 +3555,7 @@ var require_main = __commonJS(function(exports) {
3555
3555
  }
3556
3556
  let randomLength = 32;
3557
3557
  const fixedLength = "/lsp-.sock".length;
3558
- const tmpDir = fs7.realpathSync(XDG_RUNTIME_DIR ?? os2.tmpdir());
3558
+ const tmpDir = fs7.realpathSync(XDG_RUNTIME_DIR ?? os3.tmpdir());
3559
3559
  const limit = safeIpcPathLengths.get(process.platform);
3560
3560
  if (limit !== undefined) {
3561
3561
  randomLength = Math.min(limit - tmpDir.length - fixedLength, randomLength);
@@ -3564,7 +3564,7 @@ var require_main = __commonJS(function(exports) {
3564
3564
  throw new Error(`Unable to generate a random pipe name with ${randomLength} characters.`);
3565
3565
  }
3566
3566
  const randomSuffix = (0, crypto_1.randomBytes)(Math.floor(randomLength / 2)).toString("hex");
3567
- return path7.join(tmpDir, `lsp-${randomSuffix}.sock`);
3567
+ return path8.join(tmpDir, `lsp-${randomSuffix}.sock`);
3568
3568
  }
3569
3569
  function createClientPipeTransport(pipeName, encoding = "utf-8") {
3570
3570
  let connectResolve;
@@ -21721,7 +21721,7 @@ var package_default = {
21721
21721
  publishConfig: {
21722
21722
  access: "public"
21723
21723
  },
21724
- version: "0.4.0",
21724
+ version: "0.6.0",
21725
21725
  description: "Native ACP v2 agent for the OpenAI Codex app-server",
21726
21726
  packageManager: "bun@1.4.0",
21727
21727
  engines: {
@@ -21838,7 +21838,7 @@ function classifyTurnError(info) {
21838
21838
  }
21839
21839
 
21840
21840
  // src/agent/CodexAgent.ts
21841
- import path6 from "path";
21841
+ import path7 from "path";
21842
21842
 
21843
21843
  // src/util/logger.ts
21844
21844
  import fs from "fs";
@@ -23982,7 +23982,7 @@ function modelLacksReasoning(model) {
23982
23982
  const options = supportedEfforts(model);
23983
23983
  return options.length > 0 && options.every((option) => option.reasoningEffort === "none");
23984
23984
  }
23985
- function modelConfigOption(catalog, current) {
23985
+ function modelConfigOption(catalog, current, gatewayGroups = []) {
23986
23986
  const visible = catalog.filter((model) => !model.hidden || model.id === current);
23987
23987
  const options = visible.map((model) => ({
23988
23988
  value: model.id,
@@ -23992,14 +23992,27 @@ function modelConfigOption(catalog, current) {
23992
23992
  if (!options.some((option) => option.value === current)) {
23993
23993
  options.unshift({ value: current, name: current, description: null });
23994
23994
  }
23995
- return {
23995
+ const base = {
23996
23996
  configId: MODEL_CONFIG_ID,
23997
23997
  name: "Model",
23998
23998
  description: "Model Codex uses for this session",
23999
23999
  category: "model",
24000
24000
  type: "select",
24001
- currentValue: current,
24002
- options
24001
+ currentValue: current
24002
+ };
24003
+ if (gatewayGroups.length === 0)
24004
+ return { ...base, options };
24005
+ const owner = new Map;
24006
+ for (const group of gatewayGroups)
24007
+ for (const id of group.modelIds)
24008
+ owner.set(id, group.id);
24009
+ const native = options.filter((option) => !owner.has(option.value));
24010
+ return {
24011
+ ...base,
24012
+ options: [
24013
+ { groupId: "codex", name: "Codex", options: native },
24014
+ ...gatewayGroups.map((group) => ({ groupId: group.id, name: group.name, options: options.filter((option) => owner.get(option.value) === group.id) }))
24015
+ ]
24003
24016
  };
24004
24017
  }
24005
24018
  function effortConfigOption(efforts, current) {
@@ -25955,7 +25968,7 @@ function sessionConfigOptions(session) {
25955
25968
  const model = findModel(session.catalog, session.model.model);
25956
25969
  const options = [
25957
25970
  modeConfigOption(session.mode),
25958
- modelConfigOption(session.catalog, session.model.model),
25971
+ modelConfigOption(session.catalog, session.model.model, session.gatewayGroups),
25959
25972
  effortConfigOption(supportedEfforts(model), session.model.effort),
25960
25973
  collaborationModeConfigOption(session.collaborationMode)
25961
25974
  ];
@@ -26057,33 +26070,47 @@ function firstLine(text2) {
26057
26070
  }
26058
26071
 
26059
26072
  // src/agent/providers.ts
26073
+ import { readFileSync } from "fs";
26074
+ import os2 from "os";
26075
+ import path6 from "path";
26060
26076
  var OPENAI_PROVIDER_ID = "openai";
26061
26077
  var GATEWAY_MODEL_PROVIDER = "custom-gateway";
26062
26078
  var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
26079
+ var GATEWAY_ID_PATTERN = /^[a-z0-9-]+$/;
26063
26080
 
26064
26081
  class ProviderRouting {
26065
26082
  baseConfig;
26066
26083
  configuredProvider;
26067
- gateway = null;
26084
+ gateways = new Map;
26068
26085
  constructor(baseConfig, configuredProvider) {
26069
26086
  this.baseConfig = baseConfig;
26070
26087
  this.configuredProvider = configuredProvider;
26071
26088
  }
26072
26089
  copy() {
26073
26090
  const copy = new ProviderRouting(this.baseConfig, this.configuredProvider);
26074
- copy.gateway = this.gateway;
26091
+ copy.gateways = new Map(this.gateways);
26075
26092
  return copy;
26076
26093
  }
26077
26094
  get active() {
26078
- return this.gateway;
26095
+ return this.gateways.values().next().value ?? null;
26096
+ }
26097
+ gateway(id) {
26098
+ return id === null ? null : this.gateways.get(id) ?? null;
26099
+ }
26100
+ hasGateway(id) {
26101
+ return id !== null && this.gateways.has(id);
26079
26102
  }
26080
26103
  list() {
26104
+ const first = this.active;
26105
+ const routed = first !== null && first.mode === "route";
26106
+ const summary = (gateway) => ({ id: gateway.id, name: gateway.name, baseUrl: gateway.baseUrl, models: gateway.models.map((model) => model.id) });
26081
26107
  return {
26082
26108
  providers: [{
26083
26109
  providerId: OPENAI_PROVIDER_ID,
26084
26110
  supported: ["openai"],
26085
26111
  required: false,
26086
- current: this.gateway ? { apiType: "openai", baseUrl: this.gateway.baseUrl } : { apiType: "openai", baseUrl: this.nativeBaseUrl() }
26112
+ current: routed && first ? { apiType: "openai", baseUrl: first.baseUrl } : { apiType: "openai", baseUrl: this.nativeBaseUrl() },
26113
+ ...first && !routed ? { _meta: { codex: { mode: "catalog", gateway: summary(first), gateways: [...this.gateways.values()].map(summary) } } } : {}
26087
26114
  }]
26088
26115
  };
26089
26116
  }
@@ -26099,42 +26126,133 @@ class ProviderRouting {
26099
26126
  throw RequestError.invalidParams({ baseUrl: request.baseUrl }, "baseUrl must be an http(s) URL");
26100
26127
  }
26101
26128
  const hints = readHints(request._meta);
26102
- this.gateway = {
26129
+ if (hints.id === OPENAI_PROVIDER_ID || hints.id === this.configuredProvider) {
26130
+ throw RequestError.invalidParams({ id: hints.id }, `_meta.codex.id "${hints.id}" is Codex's own provider; pick another gateway id`);
26131
+ }
26132
+ if (hints.mode === "route" || this.mode !== hints.mode)
26133
+ this.gateways.clear();
26134
+ for (const other of this.gateways.values()) {
26135
+ if (other.id === hints.id)
26136
+ continue;
26137
+ const duplicate = hints.models.find((model) => other.models.some((candidate) => candidate.id === model.id));
26138
+ if (duplicate) {
26139
+ throw RequestError.invalidParams({ id: hints.id, model: duplicate.id }, `Model "${duplicate.id}" is already served by gateway "${other.id}"; model ids must be unique across gateways`);
26140
+ }
26141
+ }
26142
+ this.gateways.set(hints.id, {
26143
+ id: hints.id,
26103
26144
  baseUrl,
26104
26145
  headers: { ...request.headers ?? {} },
26105
26146
  name: hints.name ?? "Client-configured gateway",
26106
26147
  models: hints.models,
26107
- model: hints.model
26108
- };
26148
+ model: hints.model,
26149
+ bearerToken: hints.bearerToken,
26150
+ config: hints.config,
26151
+ mode: hints.mode,
26152
+ catalogEntries: hints.catalogEntries
26153
+ });
26154
+ }
26155
+ get mode() {
26156
+ return this.active?.mode ?? "route";
26157
+ }
26158
+ gatewayFor(modelId) {
26159
+ for (const gateway of this.gateways.values()) {
26160
+ if (gateway.models.some((model) => model.id === modelId))
26161
+ return gateway;
26162
+ }
26163
+ return null;
26164
+ }
26165
+ gatewayIdFor(modelId) {
26166
+ return this.gatewayFor(modelId)?.id ?? null;
26167
+ }
26168
+ isGatewayModel(modelId) {
26169
+ return this.gatewayFor(modelId) !== null;
26170
+ }
26171
+ gatewayGroups() {
26172
+ if (this.mode !== "catalog")
26173
+ return [];
26174
+ return [...this.gateways.values()].filter((gateway) => gateway.models.length > 0).map((gateway) => ({ id: gateway.id, name: gateway.name, modelIds: gateway.models.map((model) => model.id) }));
26109
26175
  }
26110
26176
  disable(request) {
26111
- if (request.providerId === OPENAI_PROVIDER_ID)
26112
- this.gateway = null;
26177
+ if (request.providerId !== OPENAI_PROVIDER_ID)
26178
+ return;
26179
+ const id = readGatewayId(request._meta);
26180
+ if (id === null)
26181
+ this.gateways.clear();
26182
+ else
26183
+ this.gateways.delete(id);
26113
26184
  }
26114
26185
  modelProvider() {
26115
- return this.gateway ? GATEWAY_MODEL_PROVIDER : this.configuredProvider;
26186
+ return this.defaultGatewayId() ?? this.configuredProvider;
26187
+ }
26188
+ routesByDefault() {
26189
+ return this.defaultGatewayId() !== null;
26116
26190
  }
26117
- threadConfig() {
26118
- if (!this.gateway)
26191
+ defaultGatewayId() {
26192
+ const first = this.active;
26193
+ return first !== null && first.mode === "route" ? first.id : null;
26194
+ }
26195
+ threadConfig(gatewayId = this.defaultGatewayId()) {
26196
+ const gateway = this.gateway(gatewayId);
26197
+ if (!gateway)
26119
26198
  return this.baseConfig;
26120
- const existing = isRecord2(this.baseConfig["model_providers"]) ? this.baseConfig["model_providers"] : {};
26199
+ return this.applyGateway(this.baseConfig, gateway);
26200
+ }
26201
+ routeConfig(config2, gatewayId) {
26202
+ return this.rebind(config2, this, gatewayId);
26203
+ }
26204
+ overrideKeys() {
26205
+ const keys = new Set;
26206
+ for (const gateway of this.gateways.values())
26207
+ for (const key of Object.keys(gateway.config))
26208
+ keys.add(key);
26209
+ return [...keys];
26210
+ }
26211
+ rebind(config2, previous, gatewayId = this.defaultGatewayId()) {
26212
+ const next = { ...config2 };
26213
+ for (const key of previous.overrideKeys()) {
26214
+ if (key in this.baseConfig)
26215
+ next[key] = this.baseConfig[key];
26216
+ else
26217
+ delete next[key];
26218
+ }
26219
+ delete next["model_providers"];
26220
+ if (isRecord2(this.baseConfig["model_providers"]))
26221
+ next["model_providers"] = this.baseConfig["model_providers"];
26222
+ const gateway = this.gateway(gatewayId);
26223
+ return gateway ? this.applyGateway(next, gateway) : next;
26224
+ }
26225
+ applyGateway(config2, gateway) {
26226
+ const existing = isRecord2(config2["model_providers"]) ? config2["model_providers"] : {};
26227
+ const entry = {
26228
+ name: gateway.name,
26229
+ base_url: gateway.baseUrl,
26230
+ wire_api: "responses",
26231
+ http_headers: { "X-Client-Feature-ID": "codex", ...gateway.headers }
26232
+ };
26233
+ if (gateway.bearerToken !== null)
26234
+ entry["experimental_bearer_token"] = gateway.bearerToken;
26121
26235
  return {
26122
- ...this.baseConfig,
26123
- model_providers: {
26124
- ...existing,
26125
- [GATEWAY_MODEL_PROVIDER]: {
26126
- name: this.gateway.name,
26127
- base_url: this.gateway.baseUrl,
26128
- wire_api: "responses",
26129
- http_headers: { "X-Client-Feature-ID": "codex", ...this.gateway.headers }
26130
- }
26131
- }
26236
+ ...config2,
26237
+ ...gateway.config,
26238
+ model_providers: { ...existing, [gateway.id]: entry }
26132
26239
  };
26133
26240
  }
26134
26241
  catalog(codexCatalog) {
26135
- if (!this.gateway || this.gateway.models.length === 0)
26242
+ const first = this.active;
26243
+ if (first === null)
26136
26244
  return codexCatalog;
26137
- return this.gateway.models.map((model, index) => gatewayModel(model, index === 0, codexCatalog[0]));
26245
+ if (first.mode === "route") {
26246
+ if (first.models.length === 0)
26247
+ return codexCatalog;
26248
+ return first.models.map((model, index) => gatewayModel(model, index === 0, codexCatalog[0], first.catalogEntries.get(model.id)));
26249
+ }
26250
+ const own = [];
26251
+ for (const gateway of this.gateways.values()) {
26252
+ for (const model of gateway.models)
26253
+ own.push(gatewayModel(model, false, codexCatalog[0], gateway.catalogEntries.get(model.id)));
26254
+ }
26255
+ return [...codexCatalog, ...own];
26138
26256
  }
26139
26257
  nativeBaseUrl() {
26140
26258
  const providers = this.baseConfig["model_providers"];
@@ -26151,6 +26269,7 @@ function readHints(meta) {
26151
26269
  const root = isRecord2(meta) ? meta : {};
26152
26270
  const alwith = isRecord2(root["alwith"]) ? root["alwith"] : {};
26153
26271
  const codex = isRecord2(root["codex"]) ? root["codex"] : {};
26272
+ const id = readGatewayId(meta) ?? GATEWAY_MODEL_PROVIDER;
26154
26273
  const models = [];
26155
26274
  if (Array.isArray(alwith["models"])) {
26156
26275
  for (const entry of alwith["models"]) {
@@ -26165,22 +26284,91 @@ function readHints(meta) {
26165
26284
  }
26166
26285
  const model = typeof alwith["model"] === "string" && alwith["model"].length > 0 ? alwith["model"] : null;
26167
26286
  const name = typeof codex["name"] === "string" && codex["name"].length > 0 ? codex["name"] : null;
26168
- return { models, model, name };
26287
+ const bearerToken = typeof codex["bearerToken"] === "string" && codex["bearerToken"].length > 0 ? codex["bearerToken"] : null;
26288
+ const rawConfig = codex["config"];
26289
+ if (rawConfig !== undefined && !isRecord2(rawConfig)) {
26290
+ throw RequestError.invalidParams({ config: rawConfig }, "_meta.codex.config must be an object of top-level Codex config keys");
26291
+ }
26292
+ const config2 = {};
26293
+ if (isRecord2(rawConfig)) {
26294
+ for (const [key, value] of Object.entries(rawConfig)) {
26295
+ if (key === "model_providers")
26296
+ throw RequestError.invalidParams({ config: rawConfig }, "_meta.codex.config cannot set model_providers; that entry is derived from baseUrl and headers");
26297
+ config2[key] = value;
26298
+ }
26299
+ }
26300
+ const rawMode = codex["mode"];
26301
+ if (rawMode !== undefined && rawMode !== "route" && rawMode !== "catalog") {
26302
+ throw RequestError.invalidParams({ mode: rawMode }, '_meta.codex.mode must be "route" or "catalog"');
26303
+ }
26304
+ const mode = rawMode === "catalog" ? "catalog" : "route";
26305
+ if (mode === "catalog" && models.length === 0) {
26306
+ throw RequestError.invalidParams(undefined, `_meta.codex.mode "catalog" needs _meta.alwith.models: the models offered next to Codex's`);
26307
+ }
26308
+ const catalogPath = config2["model_catalog_json"];
26309
+ const catalogEntries = typeof catalogPath === "string" ? readCatalogFile(catalogPath) : new Map;
26310
+ return { id, models, model, name, bearerToken, config: config2, mode, catalogEntries };
26311
+ }
26312
+ function readGatewayId(meta) {
26313
+ const root = isRecord2(meta) ? meta : {};
26314
+ const codex = isRecord2(root["codex"]) ? root["codex"] : {};
26315
+ const raw = codex["id"];
26316
+ if (raw === undefined)
26317
+ return null;
26318
+ if (typeof raw !== "string" || !GATEWAY_ID_PATTERN.test(raw)) {
26319
+ throw RequestError.invalidParams({ id: raw }, "_meta.codex.id must match [a-z0-9-]+");
26320
+ }
26321
+ return raw;
26322
+ }
26323
+ function readCatalogFile(catalogPath) {
26324
+ const resolved = catalogPath.startsWith("~/") ? path6.join(os2.homedir(), catalogPath.slice(2)) : catalogPath;
26325
+ let parsed;
26326
+ try {
26327
+ parsed = JSON.parse(readFileSync(resolved, "utf8"));
26328
+ } catch (error40) {
26329
+ throw RequestError.invalidParams({ model_catalog_json: catalogPath }, `model_catalog_json could not be read as JSON: ${error40 instanceof Error ? error40.message : String(error40)}`);
26330
+ }
26331
+ const models = isRecord2(parsed) && Array.isArray(parsed["models"]) ? parsed["models"] : null;
26332
+ if (models === null)
26333
+ throw RequestError.invalidParams({ model_catalog_json: catalogPath }, "model_catalog_json must be {models: [...]} as Codex expects");
26334
+ const entries = new Map;
26335
+ for (const entry of models) {
26336
+ if (!isRecord2(entry) || typeof entry["slug"] !== "string")
26337
+ continue;
26338
+ const levels = Array.isArray(entry["supported_reasoning_levels"]) ? entry["supported_reasoning_levels"] : [];
26339
+ const efforts = [];
26340
+ for (const level of levels) {
26341
+ if (!isRecord2(level) || typeof level["effort"] !== "string")
26342
+ continue;
26343
+ efforts.push({ reasoningEffort: level["effort"], description: typeof level["description"] === "string" ? level["description"] : "" });
26344
+ }
26345
+ const modalities = Array.isArray(entry["input_modalities"]) ? entry["input_modalities"].filter((value) => value === "text" || value === "image") : null;
26346
+ entries.set(entry["slug"], {
26347
+ displayName: typeof entry["display_name"] === "string" ? entry["display_name"] : null,
26348
+ description: typeof entry["description"] === "string" ? entry["description"] : null,
26349
+ efforts,
26350
+ defaultEffort: typeof entry["default_reasoning_level"] === "string" ? entry["default_reasoning_level"] : null,
26351
+ inputModalities: modalities
26352
+ });
26353
+ }
26354
+ return entries;
26169
26355
  }
26170
- function gatewayModel(model, isDefault, template) {
26356
+ function gatewayModel(model, isDefault, template, entry) {
26357
+ const efforts = entry && entry.efforts.length > 0 ? entry.efforts : template?.supportedReasoningEfforts ?? [];
26358
+ const defaultEffort = entry?.defaultEffort ?? template?.defaultReasoningEffort ?? "medium";
26171
26359
  return {
26172
26360
  id: model.id,
26173
26361
  model: model.id,
26174
26362
  upgrade: null,
26175
26363
  upgradeInfo: null,
26176
26364
  availabilityNux: null,
26177
- displayName: model.name,
26178
- description: model.description ?? "",
26365
+ displayName: model.name !== model.id ? model.name : entry?.displayName ?? model.name,
26366
+ description: model.description ?? entry?.description ?? "",
26179
26367
  modelSpecialty: null,
26180
26368
  hidden: false,
26181
- supportedReasoningEfforts: template?.supportedReasoningEfforts ?? [],
26182
- defaultReasoningEffort: template?.defaultReasoningEffort ?? "medium",
26183
- inputModalities: ["text", "image"],
26369
+ supportedReasoningEfforts: efforts,
26370
+ defaultReasoningEffort: defaultEffort,
26371
+ inputModalities: entry?.inputModalities ?? ["text", "image"],
26184
26372
  supportsPersonality: false,
26185
26373
  multiAgentVersion: null,
26186
26374
  additionalSpeedTiers: [],
@@ -26287,6 +26475,12 @@ class CodexAgent {
26287
26475
  }
26288
26476
  if (notification.method === "account/updated")
26289
26477
  this.refreshAccounts().catch((error40) => logger.error("refreshing account failed", error40));
26478
+ if (notification.method === "account/rateLimits/updated")
26479
+ this.forward("_codex/rate_limits_updated", notification.params);
26480
+ if (notification.method === "fuzzyFileSearch/sessionUpdated")
26481
+ this.forward("_codex/fuzzy_file_search_updated", notification.params);
26482
+ if (notification.method === "fuzzyFileSearch/sessionCompleted")
26483
+ this.forward("_codex/fuzzy_file_search_completed", notification.params);
26290
26484
  });
26291
26485
  this.codex.disconnectSignal.addEventListener("abort", stopObserving, { once: true });
26292
26486
  }
@@ -26323,7 +26517,7 @@ class CodexAgent {
26323
26517
  additionalDirectories: {}
26324
26518
  },
26325
26519
  providers: {},
26326
- _meta: { codex: { archive: true, seedHistory: true, skills: true, plugins: true } }
26520
+ _meta: { codex: { archive: true, seedHistory: true, skills: true, plugins: true, providerCatalog: true, rename: true, account: true, fuzzyFileSearch: true } }
26327
26521
  },
26328
26522
  authMethods: authMethods(this.capabilities, this.env)
26329
26523
  };
@@ -26408,23 +26602,33 @@ class CodexAgent {
26408
26602
  try {
26409
26603
  change(candidate);
26410
26604
  const catalog = await this.withCodex(() => this.codex.allModels(true));
26411
- const modelProvider = await this.resolveModelProvider(candidate);
26412
- for (const runtime of this.sessions.values()) {
26605
+ const nativeProvider = await this.resolveModelProvider(candidate);
26606
+ const wholesale = this.providers.active !== null && this.providers.mode === "route" || candidate.active !== null && candidate.mode === "route";
26607
+ const moving = [...this.sessions.values()].filter((runtime) => wholesale || this.providers.hasGateway(runtime.modelProvider) && candidate.gateway(runtime.modelProvider) !== this.providers.gateway(runtime.modelProvider));
26608
+ const targets = new Map(moving.map((runtime) => {
26609
+ if (candidate.active === null)
26610
+ return [runtime, null];
26611
+ if (candidate.mode === "route")
26612
+ return [runtime, candidate.defaultGatewayId()];
26613
+ return [runtime, candidate.hasGateway(runtime.modelProvider) ? runtime.modelProvider : null];
26614
+ }));
26615
+ for (const runtime of moving) {
26413
26616
  try {
26414
26617
  await this.codex.threadTurnsList({ threadId: runtime.session.id, limit: 1, itemsView: "notLoaded" });
26415
26618
  } catch (error40) {
26416
- throw RequestError.invalidRequest({ sessionId: runtime.session.id, details: errorMessage(error40) }, "Cannot reload this session's history for a provider change; close empty sessions and configure the provider before creating them, or retry after history is available");
26619
+ if (wholesale) {
26620
+ throw RequestError.invalidRequest({ sessionId: runtime.session.id, details: errorMessage(error40) }, "Cannot reload this session's history for a provider change; close empty sessions and configure the provider before creating them, or retry after history is available");
26621
+ }
26622
+ await this.withCodex(() => this.codex.threadInjectItems({ threadId: runtime.session.id, items: [providerChangeNote()] }));
26417
26623
  }
26418
26624
  }
26419
- for (const runtime of this.sessions.values()) {
26420
- const config2 = { ...runtime.config };
26421
- const providers = candidate.threadConfig()["model_providers"];
26422
- if (providers === undefined)
26423
- delete config2["model_providers"];
26424
- else
26425
- config2["model_providers"] = providers;
26426
- const nextCatalog = candidate.catalog(catalog);
26427
- const requestedModel = candidate.active?.model ?? (findModel(nextCatalog, runtime.session.model.model) ? runtime.session.model.model : null);
26625
+ const nextCatalog = candidate.catalog(catalog);
26626
+ for (const runtime of moving) {
26627
+ const target = targets.get(runtime) ?? null;
26628
+ const config2 = candidate.rebind(runtime.config, this.providers, target);
26629
+ const modelProvider = target ?? nativeProvider;
26630
+ const keep = findModel(nextCatalog, runtime.session.model.model) && candidate.gatewayIdFor(runtime.session.model.model) === target;
26631
+ const requestedModel = candidate.mode === "route" ? candidate.active?.model ?? (keep ? runtime.session.model.model : null) : keep ? runtime.session.model.model : null;
26428
26632
  const selection = resolveModelSelection(nextCatalog, requestedModel, requestedModel === runtime.session.model.model ? runtime.session.model.effort : null);
26429
26633
  attempted.push(runtime);
26430
26634
  await this.codex.threadUnsubscribe({ threadId: runtime.session.id });
@@ -26436,15 +26640,21 @@ class CodexAgent {
26436
26640
  model: selection.model,
26437
26641
  excludeTurns: true
26438
26642
  }));
26439
- staged.set(runtime, { config: config2, catalog: nextCatalog, model: resolveModelSelection(nextCatalog, candidate.active?.model ?? thread.model, thread.reasoningEffort) });
26643
+ staged.set(runtime, { config: config2, catalog: nextCatalog, model: resolveModelSelection(nextCatalog, requestedModel ?? thread.model, thread.reasoningEffort) });
26440
26644
  }
26441
26645
  this.providers = candidate;
26442
- for (const [runtime, next] of staged) {
26443
- runtime.config = next.config;
26444
- runtime.modelProvider = modelProvider;
26445
- runtime.session.catalog = next.catalog;
26446
- runtime.session.model = next.model;
26447
- runtime.stale = false;
26646
+ for (const runtime of this.sessions.values()) {
26647
+ const next = staged.get(runtime);
26648
+ if (next) {
26649
+ runtime.config = next.config;
26650
+ runtime.modelProvider = targets.get(runtime) ?? nativeProvider;
26651
+ runtime.session.model = next.model;
26652
+ runtime.stale = false;
26653
+ }
26654
+ runtime.session.catalog = nextCatalog;
26655
+ runtime.session.gatewayGroups = candidate.gatewayGroups();
26656
+ if (!next)
26657
+ runtime.session.model = resolveModelSelection(nextCatalog, findModel(nextCatalog, runtime.session.model.model) ? runtime.session.model.model : null, runtime.session.model.effort);
26448
26658
  runtime.client.update({ sessionUpdate: "config_option_update", configOptions: sessionConfigOptions(runtime.session), _meta: { codex: { routing: { stale: false } } } }).catch((error40) => logger.error("Provider config notification failed", error40));
26449
26659
  }
26450
26660
  } catch (error40) {
@@ -26483,7 +26693,8 @@ class CodexAgent {
26483
26693
  }
26484
26694
  async resumeSession(params, signal) {
26485
26695
  const runtime = await this.openSession({ kind: "resume", request: params }, signal);
26486
- return { configOptions: sessionConfigOptions(runtime.session) };
26696
+ const configOptions = sessionConfigOptions(runtime.session);
26697
+ return runtime.readOnly ? { configOptions, _meta: { codex: { readOnly: true, reason: "active_writer" } } } : { configOptions };
26487
26698
  }
26488
26699
  async forkSession(params, signal) {
26489
26700
  const runtime = await this.openSession({ kind: "fork", request: params }, signal);
@@ -26519,7 +26730,7 @@ class CodexAgent {
26519
26730
  if (open2.kind !== "new")
26520
26731
  this.assertUnsubscribeSettled(open2.request.sessionId);
26521
26732
  const { request } = open2;
26522
- if (typeof request.cwd !== "string" || !path6.isAbsolute(request.cwd)) {
26733
+ if (typeof request.cwd !== "string" || !path7.isAbsolute(request.cwd)) {
26523
26734
  throw RequestError.invalidParams({ cwd: request.cwd }, "cwd must be an absolute path");
26524
26735
  }
26525
26736
  if (open2.kind === "resume" && open2.request.replayFrom != null && open2.request.replayFrom.type !== "start") {
@@ -26532,25 +26743,45 @@ class CodexAgent {
26532
26743
  this.assertUnsubscribeSettled(open2.request.sessionId);
26533
26744
  }
26534
26745
  const mcpServers = request.mcpServers ?? [];
26746
+ const requestedModel = requestedModelOf(request._meta);
26747
+ let gatewayId = this.providers.defaultGatewayId();
26748
+ if (this.providers.active !== null && this.providers.mode === "catalog") {
26749
+ if (requestedModel !== null)
26750
+ gatewayId = this.providers.gatewayIdFor(requestedModel);
26751
+ else if (open2.kind === "resume") {
26752
+ const known = await this.withCodex(() => this.codex.threadRead({ threadId: open2.request.sessionId, includeTurns: false }));
26753
+ gatewayId = this.providers.hasGateway(known.thread.modelProvider) ? known.thread.modelProvider : null;
26754
+ }
26755
+ }
26756
+ const onGateway = gatewayId !== null;
26535
26757
  let accountVersion = this.accountGeneration;
26536
26758
  const account = await this.withCodex(() => this.codex.accountRead({ refreshToken: false }));
26537
- if (this.providers.active === null && account.requiresOpenaiAuth && account.account === null) {
26759
+ if (!onGateway && account.requiresOpenaiAuth && account.account === null) {
26538
26760
  throw RequestError.authRequired(undefined, "Log in to Codex first (auth/login)");
26539
26761
  }
26540
26762
  const existingMcp = mcpServers.length > 0 ? await this.withCodex(() => this.configuredMcpServerNames(request.cwd)) : new Set;
26541
- const config2 = buildThreadConfig(this.providers.threadConfig(), request.cwd, additionalDirectories, mcpServers, existingMcp);
26763
+ const config2 = buildThreadConfig(this.providers.threadConfig(gatewayId), request.cwd, additionalDirectories, mcpServers, existingMcp);
26542
26764
  const mcpStartupGeneration = this.codex.mcpStartupGeneration;
26543
- const modelProvider = await this.withCodex(() => this.resolveModelProvider());
26765
+ const modelProvider = gatewayId ?? await this.withCodex(() => this.resolveModelProvider());
26766
+ const startModel = onGateway ? requestedModel ?? this.providers.gateway(gatewayId)?.model ?? null : requestedModel;
26767
+ let readOnly = false;
26544
26768
  const { thread, skills } = await this.withSkillsContext(request.cwd, additionalDirectories, async (skills2) => ({ skills: skills2, thread: await this.withCodex(async () => {
26545
26769
  if (signal?.aborted)
26546
26770
  throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
26547
26771
  switch (open2.kind) {
26548
26772
  case "new":
26549
- return await this.codex.threadStart({ config: config2, cwd: request.cwd, modelProvider });
26773
+ return await this.codex.threadStart({ config: config2, cwd: request.cwd, modelProvider, model: startModel });
26550
26774
  case "resume":
26551
- return await this.codex.threadResume({ threadId: open2.request.sessionId, config: config2, cwd: request.cwd, modelProvider, excludeTurns: true });
26775
+ try {
26776
+ return await this.codex.threadResume({ threadId: open2.request.sessionId, config: config2, cwd: request.cwd, modelProvider, model: startModel, excludeTurns: true });
26777
+ } catch (error40) {
26778
+ if (!errorMessage(error40).includes("already has an active writer"))
26779
+ throw error40;
26780
+ readOnly = true;
26781
+ return readOnlyThread((await this.codex.threadRead({ threadId: open2.request.sessionId, includeTurns: false })).thread, request.cwd);
26782
+ }
26552
26783
  case "fork":
26553
- return await this.codex.threadFork({ threadId: open2.request.sessionId, config: config2, cwd: request.cwd, modelProvider, excludeTurns: true });
26784
+ return await this.codex.threadFork({ threadId: open2.request.sessionId, config: config2, cwd: request.cwd, modelProvider, model: startModel, excludeTurns: true });
26554
26785
  }
26555
26786
  }) }));
26556
26787
  const sessionId = thread.thread.id;
@@ -26578,15 +26809,15 @@ class CodexAgent {
26578
26809
  accountVersion = this.accountGeneration;
26579
26810
  openedAccount = (await this.withCodex(() => this.codex.accountRead({ refreshToken: false }))).account;
26580
26811
  }
26581
- const gateway = this.providers.active;
26582
26812
  const catalog = this.providers.catalog(codexCatalog);
26583
- const model = resolveModelSelection(catalog, gateway?.model ?? thread.model, thread.reasoningEffort);
26813
+ const model = resolveModelSelection(catalog, startModel ?? thread.model, thread.reasoningEffort);
26584
26814
  const session = {
26585
26815
  id: sessionId,
26586
26816
  cwd: request.cwd,
26587
26817
  additionalDirectories,
26588
26818
  mcpServerNames: mcpServerNames(mcpServers).filter((name) => !existingMcp.has(name)),
26589
26819
  catalog,
26820
+ gatewayGroups: this.providers.gatewayGroups(),
26590
26821
  model,
26591
26822
  mode: initialAgentMode(this.env),
26592
26823
  collaborationMode: DEFAULT_COLLABORATION_MODE,
@@ -26599,7 +26830,7 @@ class CodexAgent {
26599
26830
  contextWindow: null,
26600
26831
  closed: false
26601
26832
  };
26602
- const runtime = this.installRuntime(session, capabilities, config2, thread.modelProvider);
26833
+ const runtime = this.installRuntime(session, capabilities, config2, gatewayId ?? thread.modelProvider, readOnly);
26603
26834
  const cancelOpening = () => {
26604
26835
  runtime.session.closed = true;
26605
26836
  runtime.lifetime.abort();
@@ -26646,7 +26877,7 @@ class CodexAgent {
26646
26877
  stopObservingOpen();
26647
26878
  }
26648
26879
  }
26649
- installRuntime(session, capabilities, config2, modelProvider) {
26880
+ installRuntime(session, capabilities, config2, modelProvider, readOnly = false) {
26650
26881
  const client = new ClientSession(session.id, this.link, capabilities);
26651
26882
  const bridge = new EventBridge(client, session);
26652
26883
  const turnContext = new TurnContext(session.id);
@@ -26656,7 +26887,7 @@ class CodexAgent {
26656
26887
  };
26657
26888
  const approval = new CodexApprovalHandler(client, turnContext, signal);
26658
26889
  const elicitation = new CodexElicitationHandler(client, turnContext, signal);
26659
- const runtime = { config: config2, modelProvider, stale: false, session, client, bridge, turnContext, elicitation, lifetime: new AbortController, queue: Promise.resolve() };
26890
+ const runtime = { config: config2, modelProvider, stale: false, readOnly, session, client, bridge, turnContext, elicitation, lifetime: new AbortController, queue: Promise.resolve() };
26660
26891
  const drained = (operation) => async (params) => {
26661
26892
  await abortable(this.drain(runtime), this.codex.disconnectSignal);
26662
26893
  return await operation(params);
@@ -26897,6 +27128,27 @@ class CodexAgent {
26897
27128
  notifySkillsChanged() {
26898
27129
  this.link.notify("_codex/skills_changed", {}).catch((error40) => logger.error("skills change notification failed", error40));
26899
27130
  }
27131
+ forward(method, params) {
27132
+ this.link.notify(method, params).catch((error40) => logger.error(`${method} notification failed`, error40));
27133
+ }
27134
+ async sessionRename(params) {
27135
+ this.requireInitialized("_codex/session_rename");
27136
+ await this.withCodex(() => this.codex.threadSetName({ threadId: params.sessionId, name: params.name }));
27137
+ return {};
27138
+ }
27139
+ async accountRead() {
27140
+ this.requireInitialized("_codex/account_read");
27141
+ return await this.withCodex(() => this.codex.accountRead({ refreshToken: false }));
27142
+ }
27143
+ async rateLimits() {
27144
+ this.requireInitialized("_codex/rate_limits");
27145
+ return await this.withCodex(() => this.codex.accountRateLimitsRead());
27146
+ }
27147
+ async fuzzyFileSearch(params) {
27148
+ this.requireInitialized("_codex/fuzzy_file_search");
27149
+ const request = { query: params.query, roots: params.roots, cancellationToken: params.cancellationToken };
27150
+ return await this.withCodex(() => this.codex.fuzzyFileSearch(request));
27151
+ }
26900
27152
  async closeRuntime(sessionId) {
26901
27153
  const deadline = performance.now() + this.closeGraceMs;
26902
27154
  const runtime = this.sessions.get(sessionId);
@@ -26923,7 +27175,7 @@ class CodexAgent {
26923
27175
  runtime.session.activeTurn = null;
26924
27176
  turn.resolveFinished();
26925
27177
  }
26926
- const unsubscribe = this.unsubscribeThread(sessionId);
27178
+ const unsubscribe = runtime.readOnly ? Promise.resolve() : this.unsubscribeThread(sessionId);
26927
27179
  let remoteUnsubscribe = "confirmed";
26928
27180
  try {
26929
27181
  if (!await within(unsubscribe, Math.max(0, deadline - performance.now()))) {
@@ -26952,11 +27204,47 @@ class CodexAgent {
26952
27204
  }
26953
27205
  async setSessionConfigOptionInternal(params) {
26954
27206
  const runtime = this.runtime(params.sessionId, "session/set_config_option");
27207
+ assertWritable(runtime);
27208
+ if (params.configId === MODEL_CONFIG_ID && this.providers.mode === "catalog" && typeof params.value === "string") {
27209
+ const target = this.providers.gatewayIdFor(params.value);
27210
+ const current = this.providers.hasGateway(runtime.modelProvider) ? runtime.modelProvider : null;
27211
+ if (target !== current)
27212
+ await this.rerouteSession(runtime, target, params.value);
27213
+ }
26955
27214
  await this.withCodex(() => applyConfigOption(runtime.session, this.codex, params));
26956
27215
  const configOptions = sessionConfigOptions(runtime.session);
26957
27216
  await runtime.client.update({ sessionUpdate: "config_option_update", configOptions });
26958
27217
  return { configOptions };
26959
27218
  }
27219
+ async rerouteSession(runtime, gatewayId, modelId) {
27220
+ const { session } = runtime;
27221
+ this.assertRoutingAvailable();
27222
+ if (session.activeTurn !== null) {
27223
+ throw RequestError.invalidRequest({ sessionId: session.id }, "The model provider cannot change while a turn is running; cancel it or wait for idle");
27224
+ }
27225
+ try {
27226
+ await this.codex.threadTurnsList({ threadId: session.id, limit: 1, itemsView: "notLoaded" });
27227
+ } catch {
27228
+ await this.withCodex(() => this.codex.threadInjectItems({ threadId: session.id, items: [providerChangeNote()] }));
27229
+ }
27230
+ const config2 = this.providers.routeConfig(runtime.config, gatewayId);
27231
+ const modelProvider = gatewayId ?? await this.withCodex(() => this.resolveModelProvider());
27232
+ await this.codex.threadUnsubscribe({ threadId: session.id });
27233
+ try {
27234
+ await this.withCodex(() => this.codex.threadResume({ threadId: session.id, cwd: session.cwd, config: config2, modelProvider, model: modelId, excludeTurns: true }));
27235
+ } catch (error40) {
27236
+ try {
27237
+ await this.codex.threadResume({ threadId: session.id, cwd: session.cwd, config: runtime.config, modelProvider: runtime.modelProvider, model: session.model.model, excludeTurns: true });
27238
+ } catch (rollbackError) {
27239
+ runtime.stale = true;
27240
+ logger.error("Provider rollback failed", rollbackError, { sessionId: session.id });
27241
+ }
27242
+ throw RequestError.internalError({ sessionId: session.id, stale: runtime.stale }, `Could not move the session to ${gatewayId !== null ? `gateway "${gatewayId}"` : "Codex's provider"}: ${errorMessage(error40)}`);
27243
+ }
27244
+ runtime.config = config2;
27245
+ runtime.modelProvider = modelProvider;
27246
+ runtime.stale = false;
27247
+ }
26960
27248
  runtime(sessionId, method) {
26961
27249
  this.requireInitialized(method);
26962
27250
  const runtime = this.sessions.get(sessionId);
@@ -26972,6 +27260,7 @@ class CodexAgent {
26972
27260
  if (!allowReentry && this.sessionMutations.has(params.sessionId))
26973
27261
  throw RequestError.invalidRequest({ sessionId: params.sessionId }, "Session lifecycle or configuration work is in progress; retry when it finishes");
26974
27262
  const runtime = this.runtime(params.sessionId, "session/prompt");
27263
+ assertWritable(runtime);
26975
27264
  if (runtime.stale)
26976
27265
  throw RequestError.invalidRequest({ sessionId: params.sessionId, _meta: { codex: { routing: { stale: true } } } }, "Session routing is stale; resume the session or successfully change providers before prompting");
26977
27266
  if (!Array.isArray(params.prompt) || params.prompt.length === 0) {
@@ -27270,7 +27559,7 @@ ${error40.additionalDetails}` : error40.message;
27270
27559
  }
27271
27560
  withSkillsContext(cwd, additionalDirectories, operation) {
27272
27561
  const run = async () => {
27273
- const roots = additionalDirectories.map((root) => path6.join(root, ".agents", "skills"));
27562
+ const roots = additionalDirectories.map((root) => path7.join(root, ".agents", "skills"));
27274
27563
  if (roots.length !== this.skillRoots.length || roots.some((root, index) => root !== this.skillRoots[index])) {
27275
27564
  this.changingSkillRoots = true;
27276
27565
  try {
@@ -27304,7 +27593,7 @@ ${error40.additionalDetails}` : error40.message;
27304
27593
  }
27305
27594
  async refreshAvailableCommands(runtime) {
27306
27595
  const run = async () => {
27307
- const roots = runtime.session.additionalDirectories.map((root) => path6.join(root, ".agents", "skills"));
27596
+ const roots = runtime.session.additionalDirectories.map((root) => path7.join(root, ".agents", "skills"));
27308
27597
  if (runtime.session.closed || roots.length !== this.skillRoots.length || roots.some((root, i) => root !== this.skillRoots[i]))
27309
27598
  return;
27310
27599
  return await this.loadSkills(runtime.session.cwd, runtime.session.additionalDirectories);
@@ -27407,6 +27696,28 @@ function interruptedTurn(threadId) {
27407
27696
  turn: { id: "", items: [], itemsView: "notLoaded", status: "interrupted", error: null, startedAt: null, completedAt: null, durationMs: null }
27408
27697
  };
27409
27698
  }
27699
+ function parseSessionRenameParams(raw) {
27700
+ const params = objectParams()(raw);
27701
+ if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
27702
+ throw RequestError.invalidParams({ sessionId: params.sessionId }, "sessionId must be a non-empty string");
27703
+ }
27704
+ if (typeof params.name !== "string")
27705
+ throw RequestError.invalidParams({ name: params.name }, "name must be a string");
27706
+ return { sessionId: params.sessionId, name: params.name };
27707
+ }
27708
+ function parseFuzzyFileSearchParams(raw) {
27709
+ const params = objectParams()(raw);
27710
+ if (typeof params.query !== "string")
27711
+ throw RequestError.invalidParams({ query: params.query }, "query must be a string");
27712
+ if (!Array.isArray(params.roots) || !params.roots.every((root) => typeof root === "string")) {
27713
+ throw RequestError.invalidParams({ roots: params.roots }, "roots must be an array of directory paths");
27714
+ }
27715
+ const token = params.cancellationToken;
27716
+ if (token !== undefined && token !== null && typeof token !== "string") {
27717
+ throw RequestError.invalidParams({ cancellationToken: token }, "cancellationToken must be a string when given");
27718
+ }
27719
+ return { query: params.query, roots: params.roots, cancellationToken: typeof token === "string" ? token : null };
27720
+ }
27410
27721
  function objectParams() {
27411
27722
  return (raw) => {
27412
27723
  if (raw === undefined || raw === null)
@@ -27428,6 +27739,34 @@ function archivedFilter(meta) {
27428
27739
  const codex = meta?.codex;
27429
27740
  return codex?.archived === true;
27430
27741
  }
27742
+ function providerChangeNote() {
27743
+ return { type: "message", role: "developer", content: [{ type: "input_text", text: "The user changed this conversation's model before sending a message." }] };
27744
+ }
27745
+ function assertWritable(runtime) {
27746
+ if (!runtime.readOnly)
27747
+ return;
27748
+ throw RequestError.invalidRequest({ codex: { readOnly: true } }, "Thread is open in another Codex client");
27749
+ }
27750
+ function readOnlyThread(thread, cwd) {
27751
+ return {
27752
+ thread,
27753
+ model: thread.model ?? "",
27754
+ modelProvider: thread.modelProvider,
27755
+ serviceTier: null,
27756
+ cwd,
27757
+ instructionSources: [],
27758
+ approvalPolicy: "on-request",
27759
+ approvalsReviewer: "user",
27760
+ sandbox: { type: "readOnly", networkAccess: false },
27761
+ reasoningEffort: thread.reasoningEffort,
27762
+ turnsBackwardsCursor: null,
27763
+ itemsBackwardsCursor: null
27764
+ };
27765
+ }
27766
+ function requestedModelOf(meta) {
27767
+ const raw = meta?.alwith?.model;
27768
+ return typeof raw === "string" && raw.length > 0 ? raw : null;
27769
+ }
27431
27770
  function seedHistoryOf(meta) {
27432
27771
  const raw = meta?.codex?.seedHistory;
27433
27772
  if (raw === undefined || raw === null)
@@ -27474,7 +27813,7 @@ function createAgentApp(options) {
27474
27813
  };
27475
27814
  return agent({ name: options.info.name }).onConnect((connection) => {
27476
27815
  agent2 = new CodexAgent(connection.client, options);
27477
- }).onRequest(methods.agent.initialize, (ctx) => current().initialize(ctx.params)).onRequest(methods.agent.auth.login, (ctx) => current().login(ctx.params, ctx.requestId)).onRequest(methods.agent.auth.logout, (ctx) => current().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => current().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => current().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => current().disableProvider(ctx.params)).onRequest(methods.agent.session.new, (ctx) => current().newSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.resume, (ctx) => current().resumeSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.fork, (ctx) => current().forkSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.list, (ctx) => current().listSessions(ctx.params)).onRequest(methods.agent.session.close, (ctx) => current().closeSession(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => current().deleteSession(ctx.params)).onRequest("_codex/session_archive", { parse: parseSessionIdParams }, (ctx) => current().archiveSession(ctx.params)).onRequest("_codex/session_unarchive", { parse: parseSessionIdParams }, (ctx) => current().unarchiveSession(ctx.params)).onRequest("_codex/skills_list", { parse: objectParams() }, (ctx) => current().skillsList(ctx.params)).onRequest("_codex/skills_config_write", { parse: objectParams() }, (ctx) => current().skillsConfigWrite(ctx.params)).onRequest("_codex/plugin_list", { parse: objectParams() }, (ctx) => current().pluginList(ctx.params)).onRequest("_codex/plugin_installed", { parse: objectParams() }, (ctx) => current().pluginInstalled(ctx.params)).onRequest("_codex/plugin_install", { parse: objectParams() }, (ctx) => current().pluginInstall(ctx.params)).onRequest("_codex/plugin_uninstall", { parse: objectParams() }, (ctx) => current().pluginUninstall(ctx.params)).onRequest("_codex/plugin_read", { parse: objectParams() }, (ctx) => current().pluginRead(ctx.params)).onRequest("_codex/marketplace_add", { parse: objectParams() }, (ctx) => current().marketplaceAdd(ctx.params)).onRequest("_codex/marketplace_remove", { parse: objectParams() }, (ctx) => current().marketplaceRemove(ctx.params)).onRequest("_codex/marketplace_upgrade", { parse: objectParams() }, (ctx) => current().marketplaceUpgrade(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => current().setSessionConfigOption(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => current().prompt(ctx.params)).onNotification(methods.agent.session.cancel, (ctx) => current().cancel(ctx.params));
27816
+ }).onRequest(methods.agent.initialize, (ctx) => current().initialize(ctx.params)).onRequest(methods.agent.auth.login, (ctx) => current().login(ctx.params, ctx.requestId)).onRequest(methods.agent.auth.logout, (ctx) => current().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => current().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => current().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => current().disableProvider(ctx.params)).onRequest(methods.agent.session.new, (ctx) => current().newSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.resume, (ctx) => current().resumeSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.fork, (ctx) => current().forkSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.list, (ctx) => current().listSessions(ctx.params)).onRequest(methods.agent.session.close, (ctx) => current().closeSession(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => current().deleteSession(ctx.params)).onRequest("_codex/session_archive", { parse: parseSessionIdParams }, (ctx) => current().archiveSession(ctx.params)).onRequest("_codex/session_unarchive", { parse: parseSessionIdParams }, (ctx) => current().unarchiveSession(ctx.params)).onRequest("_codex/skills_list", { parse: objectParams() }, (ctx) => current().skillsList(ctx.params)).onRequest("_codex/skills_config_write", { parse: objectParams() }, (ctx) => current().skillsConfigWrite(ctx.params)).onRequest("_codex/plugin_list", { parse: objectParams() }, (ctx) => current().pluginList(ctx.params)).onRequest("_codex/plugin_installed", { parse: objectParams() }, (ctx) => current().pluginInstalled(ctx.params)).onRequest("_codex/plugin_install", { parse: objectParams() }, (ctx) => current().pluginInstall(ctx.params)).onRequest("_codex/plugin_uninstall", { parse: objectParams() }, (ctx) => current().pluginUninstall(ctx.params)).onRequest("_codex/plugin_read", { parse: objectParams() }, (ctx) => current().pluginRead(ctx.params)).onRequest("_codex/marketplace_add", { parse: objectParams() }, (ctx) => current().marketplaceAdd(ctx.params)).onRequest("_codex/marketplace_remove", { parse: objectParams() }, (ctx) => current().marketplaceRemove(ctx.params)).onRequest("_codex/marketplace_upgrade", { parse: objectParams() }, (ctx) => current().marketplaceUpgrade(ctx.params)).onRequest("_codex/session_rename", { parse: parseSessionRenameParams }, (ctx) => current().sessionRename(ctx.params)).onRequest("_codex/account_read", { parse: objectParams() }, () => current().accountRead()).onRequest("_codex/rate_limits", { parse: objectParams() }, () => current().rateLimits()).onRequest("_codex/fuzzy_file_search", { parse: parseFuzzyFileSearchParams }, (ctx) => current().fuzzyFileSearch(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => current().setSessionConfigOption(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => current().prompt(ctx.params)).onNotification(methods.agent.session.cancel, (ctx) => current().cancel(ctx.params));
27478
27817
  }
27479
27818
 
27480
27819
  // src/codex/AppServerClient.ts
@@ -27887,6 +28226,15 @@ class AppServerClient {
27887
28226
  accountRead(params) {
27888
28227
  return this.send({ method: "account/read", params });
27889
28228
  }
28229
+ accountRateLimitsRead() {
28230
+ return this.send({ method: "account/rateLimits/read", params: undefined });
28231
+ }
28232
+ threadSetName(params) {
28233
+ return this.send({ method: "thread/name/set", params });
28234
+ }
28235
+ fuzzyFileSearch(params) {
28236
+ return this.send({ method: "fuzzyFileSearch", params });
28237
+ }
27890
28238
  accountLogin(params) {
27891
28239
  return this.send({ method: "account/login/start", params });
27892
28240
  }
@@ -27922,7 +28270,7 @@ function threadIdOf(notification) {
27922
28270
 
27923
28271
  // src/codex/process.ts
27924
28272
  import { existsSync } from "fs";
27925
- import path7 from "path";
28273
+ import path8 from "path";
27926
28274
  import { spawn, spawnSync } from "child_process";
27927
28275
  import { createRequire } from "module";
27928
28276
 
@@ -28215,11 +28563,11 @@ function resolveCodexLauncher(codexPath) {
28215
28563
  const target = targets[platform2];
28216
28564
  if (!target)
28217
28565
  throw new Error(`Unsupported Codex platform: ${platform2}`);
28218
- let vendor = path7.resolve(path7.dirname(launcher), "..", "vendor");
28566
+ let vendor = path8.resolve(path8.dirname(launcher), "..", "vendor");
28219
28567
  try {
28220
- vendor = path7.join(path7.dirname(platformRequire.resolve(`@openai/codex-${platform2}/package.json`)), "vendor");
28568
+ vendor = path8.join(path8.dirname(platformRequire.resolve(`@openai/codex-${platform2}/package.json`)), "vendor");
28221
28569
  } catch {}
28222
- const command = path7.join(vendor, target, "bin", process.platform === "win32" ? "codex.exe" : "codex");
28570
+ const command = path8.join(vendor, target, "bin", process.platform === "win32" ? "codex.exe" : "codex");
28223
28571
  if (!existsSync(command))
28224
28572
  throw new Error(`Missing Codex platform binary: ${command}`);
28225
28573
  return { command, prefixArgs: [], shell: false };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.0",
6
+ "version": "0.6.0",
7
7
  "description": "Native ACP v2 agent for the OpenAI Codex app-server",
8
8
  "packageManager": "bun@1.4.0",
9
9
  "engines": {