@nyssance/codex-acp-v2 0.5.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 +23 -0
  2. package/dist/index.js +206 -71
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
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
+
3
26
  ## 0.5.0 — 2026-09-11
4
27
 
5
28
  ### Added
package/dist/index.js CHANGED
@@ -21721,7 +21721,7 @@ var package_default = {
21721
21721
  publishConfig: {
21722
21722
  access: "public"
21723
21723
  },
21724
- version: "0.5.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: {
@@ -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, gatewayGroup = null) {
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,
@@ -24000,16 +24000,18 @@ function modelConfigOption(catalog, current, gatewayGroup = null) {
24000
24000
  type: "select",
24001
24001
  currentValue: current
24002
24002
  };
24003
- if (gatewayGroup === null)
24003
+ if (gatewayGroups.length === 0)
24004
24004
  return { ...base, options };
24005
- const gatewayIds = new Set(gatewayGroup.modelIds);
24006
- const native = options.filter((option) => !gatewayIds.has(option.value));
24007
- const gateway = options.filter((option) => gatewayIds.has(option.value));
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));
24008
24010
  return {
24009
24011
  ...base,
24010
24012
  options: [
24011
24013
  { groupId: "codex", name: "Codex", options: native },
24012
- { groupId: "custom-gateway", name: gatewayGroup.name, options: gateway }
24014
+ ...gatewayGroups.map((group) => ({ groupId: group.id, name: group.name, options: options.filter((option) => owner.get(option.value) === group.id) }))
24013
24015
  ]
24014
24016
  };
24015
24017
  }
@@ -25966,7 +25968,7 @@ function sessionConfigOptions(session) {
25966
25968
  const model = findModel(session.catalog, session.model.model);
25967
25969
  const options = [
25968
25970
  modeConfigOption(session.mode),
25969
- modelConfigOption(session.catalog, session.model.model, session.gatewayGroup),
25971
+ modelConfigOption(session.catalog, session.model.model, session.gatewayGroups),
25970
25972
  effortConfigOption(supportedEfforts(model), session.model.effort),
25971
25973
  collaborationModeConfigOption(session.collaborationMode)
25972
25974
  ];
@@ -26074,31 +26076,41 @@ import path6 from "path";
26074
26076
  var OPENAI_PROVIDER_ID = "openai";
26075
26077
  var GATEWAY_MODEL_PROVIDER = "custom-gateway";
26076
26078
  var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
26079
+ var GATEWAY_ID_PATTERN = /^[a-z0-9-]+$/;
26080
+
26077
26081
  class ProviderRouting {
26078
26082
  baseConfig;
26079
26083
  configuredProvider;
26080
- gateway = null;
26084
+ gateways = new Map;
26081
26085
  constructor(baseConfig, configuredProvider) {
26082
26086
  this.baseConfig = baseConfig;
26083
26087
  this.configuredProvider = configuredProvider;
26084
26088
  }
26085
26089
  copy() {
26086
26090
  const copy = new ProviderRouting(this.baseConfig, this.configuredProvider);
26087
- copy.gateway = this.gateway;
26091
+ copy.gateways = new Map(this.gateways);
26088
26092
  return copy;
26089
26093
  }
26090
26094
  get active() {
26091
- 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);
26092
26102
  }
26093
26103
  list() {
26094
- const routed = this.gateway !== null && this.gateway.mode === "route";
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) });
26095
26107
  return {
26096
26108
  providers: [{
26097
26109
  providerId: OPENAI_PROVIDER_ID,
26098
26110
  supported: ["openai"],
26099
26111
  required: false,
26100
- current: routed && this.gateway ? { apiType: "openai", baseUrl: this.gateway.baseUrl } : { apiType: "openai", baseUrl: this.nativeBaseUrl() },
26101
- ...this.gateway && !routed ? { _meta: { codex: { mode: "catalog", gateway: { name: this.gateway.name, baseUrl: this.gateway.baseUrl, models: this.gateway.models.map((model) => model.id) } } } } : {}
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) } } } : {}
26102
26114
  }]
26103
26115
  };
26104
26116
  }
@@ -26114,7 +26126,21 @@ class ProviderRouting {
26114
26126
  throw RequestError.invalidParams({ baseUrl: request.baseUrl }, "baseUrl must be an http(s) URL");
26115
26127
  }
26116
26128
  const hints = readHints(request._meta);
26117
- 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,
26118
26144
  baseUrl,
26119
26145
  headers: { ...request.headers ?? {} },
26120
26146
  name: hints.name ?? "Client-configured gateway",
@@ -26124,41 +26150,65 @@ class ProviderRouting {
26124
26150
  config: hints.config,
26125
26151
  mode: hints.mode,
26126
26152
  catalogEntries: hints.catalogEntries
26127
- };
26153
+ });
26128
26154
  }
26129
26155
  get mode() {
26130
- return this.gateway?.mode ?? "route";
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;
26131
26167
  }
26132
26168
  isGatewayModel(modelId) {
26133
- return this.gateway !== null && this.gateway.models.some((model) => model.id === modelId);
26169
+ return this.gatewayFor(modelId) !== null;
26134
26170
  }
26135
- gatewayGroup() {
26136
- if (!this.gateway || this.gateway.mode !== "catalog" || this.gateway.models.length === 0)
26137
- return null;
26138
- return { name: this.gateway.name, modelIds: this.gateway.models.map((model) => model.id) };
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) }));
26139
26175
  }
26140
26176
  disable(request) {
26141
- if (request.providerId === OPENAI_PROVIDER_ID)
26142
- 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);
26143
26184
  }
26144
26185
  modelProvider() {
26145
- return this.gateway && this.gateway.mode === "route" ? GATEWAY_MODEL_PROVIDER : this.configuredProvider;
26186
+ return this.defaultGatewayId() ?? this.configuredProvider;
26146
26187
  }
26147
26188
  routesByDefault() {
26148
- return this.gateway !== null && this.gateway.mode === "route";
26189
+ return this.defaultGatewayId() !== null;
26190
+ }
26191
+ defaultGatewayId() {
26192
+ const first = this.active;
26193
+ return first !== null && first.mode === "route" ? first.id : null;
26149
26194
  }
26150
- threadConfig(onGateway = this.routesByDefault()) {
26151
- if (!this.gateway || !onGateway)
26195
+ threadConfig(gatewayId = this.defaultGatewayId()) {
26196
+ const gateway = this.gateway(gatewayId);
26197
+ if (!gateway)
26152
26198
  return this.baseConfig;
26153
- return this.applyGateway(this.baseConfig, this.gateway);
26199
+ return this.applyGateway(this.baseConfig, gateway);
26154
26200
  }
26155
- routeConfig(config2, onGateway) {
26156
- return this.rebind(config2, this, onGateway);
26201
+ routeConfig(config2, gatewayId) {
26202
+ return this.rebind(config2, this, gatewayId);
26157
26203
  }
26158
26204
  overrideKeys() {
26159
- return this.gateway ? Object.keys(this.gateway.config) : [];
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];
26160
26210
  }
26161
- rebind(config2, previous, onGateway = this.routesByDefault()) {
26211
+ rebind(config2, previous, gatewayId = this.defaultGatewayId()) {
26162
26212
  const next = { ...config2 };
26163
26213
  for (const key of previous.overrideKeys()) {
26164
26214
  if (key in this.baseConfig)
@@ -26166,13 +26216,11 @@ class ProviderRouting {
26166
26216
  else
26167
26217
  delete next[key];
26168
26218
  }
26169
- if (!this.gateway || !onGateway) {
26170
- delete next["model_providers"];
26171
- if (isRecord2(this.baseConfig["model_providers"]))
26172
- next["model_providers"] = this.baseConfig["model_providers"];
26173
- return next;
26174
- }
26175
- return this.applyGateway(next, this.gateway);
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;
26176
26224
  }
26177
26225
  applyGateway(config2, gateway) {
26178
26226
  const existing = isRecord2(config2["model_providers"]) ? config2["model_providers"] : {};
@@ -26187,15 +26235,24 @@ class ProviderRouting {
26187
26235
  return {
26188
26236
  ...config2,
26189
26237
  ...gateway.config,
26190
- model_providers: { ...existing, [GATEWAY_MODEL_PROVIDER]: entry }
26238
+ model_providers: { ...existing, [gateway.id]: entry }
26191
26239
  };
26192
26240
  }
26193
26241
  catalog(codexCatalog) {
26194
- if (!this.gateway || this.gateway.models.length === 0)
26242
+ const first = this.active;
26243
+ if (first === null)
26195
26244
  return codexCatalog;
26196
- const gateway = this.gateway;
26197
- const own = gateway.models.map((model, index) => gatewayModel(model, gateway.mode === "route" && index === 0, codexCatalog[0], gateway.catalogEntries.get(model.id)));
26198
- return gateway.mode === "catalog" ? [...codexCatalog, ...own] : own;
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];
26199
26256
  }
26200
26257
  nativeBaseUrl() {
26201
26258
  const providers = this.baseConfig["model_providers"];
@@ -26212,6 +26269,7 @@ function readHints(meta) {
26212
26269
  const root = isRecord2(meta) ? meta : {};
26213
26270
  const alwith = isRecord2(root["alwith"]) ? root["alwith"] : {};
26214
26271
  const codex = isRecord2(root["codex"]) ? root["codex"] : {};
26272
+ const id = readGatewayId(meta) ?? GATEWAY_MODEL_PROVIDER;
26215
26273
  const models = [];
26216
26274
  if (Array.isArray(alwith["models"])) {
26217
26275
  for (const entry of alwith["models"]) {
@@ -26249,7 +26307,18 @@ function readHints(meta) {
26249
26307
  }
26250
26308
  const catalogPath = config2["model_catalog_json"];
26251
26309
  const catalogEntries = typeof catalogPath === "string" ? readCatalogFile(catalogPath) : new Map;
26252
- return { models, model, name, bearerToken, config: config2, mode, catalogEntries };
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;
26253
26322
  }
26254
26323
  function readCatalogFile(catalogPath) {
26255
26324
  const resolved = catalogPath.startsWith("~/") ? path6.join(os2.homedir(), catalogPath.slice(2)) : catalogPath;
@@ -26406,6 +26475,12 @@ class CodexAgent {
26406
26475
  }
26407
26476
  if (notification.method === "account/updated")
26408
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);
26409
26484
  });
26410
26485
  this.codex.disconnectSignal.addEventListener("abort", stopObserving, { once: true });
26411
26486
  }
@@ -26442,7 +26517,7 @@ class CodexAgent {
26442
26517
  additionalDirectories: {}
26443
26518
  },
26444
26519
  providers: {},
26445
- _meta: { codex: { archive: true, seedHistory: true, skills: true, plugins: true, providerCatalog: true } }
26520
+ _meta: { codex: { archive: true, seedHistory: true, skills: true, plugins: true, providerCatalog: true, rename: true, account: true, fuzzyFileSearch: true } }
26446
26521
  },
26447
26522
  authMethods: authMethods(this.capabilities, this.env)
26448
26523
  };
@@ -26529,8 +26604,14 @@ class CodexAgent {
26529
26604
  const catalog = await this.withCodex(() => this.codex.allModels(true));
26530
26605
  const nativeProvider = await this.resolveModelProvider(candidate);
26531
26606
  const wholesale = this.providers.active !== null && this.providers.mode === "route" || candidate.active !== null && candidate.mode === "route";
26532
- const moving = [...this.sessions.values()].filter((runtime) => wholesale || runtime.modelProvider === GATEWAY_MODEL_PROVIDER);
26533
- const targets = new Map(moving.map((runtime) => [runtime, candidate.active !== null && (candidate.mode === "route" || runtime.modelProvider === GATEWAY_MODEL_PROVIDER)]));
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
+ }));
26534
26615
  for (const runtime of moving) {
26535
26616
  try {
26536
26617
  await this.codex.threadTurnsList({ threadId: runtime.session.id, limit: 1, itemsView: "notLoaded" });
@@ -26543,10 +26624,10 @@ class CodexAgent {
26543
26624
  }
26544
26625
  const nextCatalog = candidate.catalog(catalog);
26545
26626
  for (const runtime of moving) {
26546
- const onGateway = targets.get(runtime) === true;
26547
- const config2 = candidate.rebind(runtime.config, this.providers, onGateway);
26548
- const modelProvider = onGateway ? GATEWAY_MODEL_PROVIDER : nativeProvider;
26549
- const keep = findModel(nextCatalog, runtime.session.model.model) && candidate.isGatewayModel(runtime.session.model.model) === onGateway;
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;
26550
26631
  const requestedModel = candidate.mode === "route" ? candidate.active?.model ?? (keep ? runtime.session.model.model : null) : keep ? runtime.session.model.model : null;
26551
26632
  const selection = resolveModelSelection(nextCatalog, requestedModel, requestedModel === runtime.session.model.model ? runtime.session.model.effort : null);
26552
26633
  attempted.push(runtime);
@@ -26566,12 +26647,12 @@ class CodexAgent {
26566
26647
  const next = staged.get(runtime);
26567
26648
  if (next) {
26568
26649
  runtime.config = next.config;
26569
- runtime.modelProvider = targets.get(runtime) === true ? GATEWAY_MODEL_PROVIDER : nativeProvider;
26650
+ runtime.modelProvider = targets.get(runtime) ?? nativeProvider;
26570
26651
  runtime.session.model = next.model;
26571
26652
  runtime.stale = false;
26572
26653
  }
26573
26654
  runtime.session.catalog = nextCatalog;
26574
- runtime.session.gatewayGroup = candidate.gatewayGroup();
26655
+ runtime.session.gatewayGroups = candidate.gatewayGroups();
26575
26656
  if (!next)
26576
26657
  runtime.session.model = resolveModelSelection(nextCatalog, findModel(nextCatalog, runtime.session.model.model) ? runtime.session.model.model : null, runtime.session.model.effort);
26577
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));
@@ -26663,25 +26744,26 @@ class CodexAgent {
26663
26744
  }
26664
26745
  const mcpServers = request.mcpServers ?? [];
26665
26746
  const requestedModel = requestedModelOf(request._meta);
26666
- let onGateway = this.providers.routesByDefault();
26747
+ let gatewayId = this.providers.defaultGatewayId();
26667
26748
  if (this.providers.active !== null && this.providers.mode === "catalog") {
26668
26749
  if (requestedModel !== null)
26669
- onGateway = this.providers.isGatewayModel(requestedModel);
26750
+ gatewayId = this.providers.gatewayIdFor(requestedModel);
26670
26751
  else if (open2.kind === "resume") {
26671
26752
  const known = await this.withCodex(() => this.codex.threadRead({ threadId: open2.request.sessionId, includeTurns: false }));
26672
- onGateway = known.thread.modelProvider === GATEWAY_MODEL_PROVIDER;
26753
+ gatewayId = this.providers.hasGateway(known.thread.modelProvider) ? known.thread.modelProvider : null;
26673
26754
  }
26674
26755
  }
26756
+ const onGateway = gatewayId !== null;
26675
26757
  let accountVersion = this.accountGeneration;
26676
26758
  const account = await this.withCodex(() => this.codex.accountRead({ refreshToken: false }));
26677
26759
  if (!onGateway && account.requiresOpenaiAuth && account.account === null) {
26678
26760
  throw RequestError.authRequired(undefined, "Log in to Codex first (auth/login)");
26679
26761
  }
26680
26762
  const existingMcp = mcpServers.length > 0 ? await this.withCodex(() => this.configuredMcpServerNames(request.cwd)) : new Set;
26681
- const config2 = buildThreadConfig(this.providers.threadConfig(onGateway), request.cwd, additionalDirectories, mcpServers, existingMcp);
26763
+ const config2 = buildThreadConfig(this.providers.threadConfig(gatewayId), request.cwd, additionalDirectories, mcpServers, existingMcp);
26682
26764
  const mcpStartupGeneration = this.codex.mcpStartupGeneration;
26683
- const modelProvider = onGateway ? GATEWAY_MODEL_PROVIDER : await this.withCodex(() => this.resolveModelProvider());
26684
- const startModel = onGateway ? requestedModel ?? this.providers.active?.model ?? null : requestedModel;
26765
+ const modelProvider = gatewayId ?? await this.withCodex(() => this.resolveModelProvider());
26766
+ const startModel = onGateway ? requestedModel ?? this.providers.gateway(gatewayId)?.model ?? null : requestedModel;
26685
26767
  let readOnly = false;
26686
26768
  const { thread, skills } = await this.withSkillsContext(request.cwd, additionalDirectories, async (skills2) => ({ skills: skills2, thread: await this.withCodex(async () => {
26687
26769
  if (signal?.aborted)
@@ -26735,7 +26817,7 @@ class CodexAgent {
26735
26817
  additionalDirectories,
26736
26818
  mcpServerNames: mcpServerNames(mcpServers).filter((name) => !existingMcp.has(name)),
26737
26819
  catalog,
26738
- gatewayGroup: this.providers.gatewayGroup(),
26820
+ gatewayGroups: this.providers.gatewayGroups(),
26739
26821
  model,
26740
26822
  mode: initialAgentMode(this.env),
26741
26823
  collaborationMode: DEFAULT_COLLABORATION_MODE,
@@ -26748,7 +26830,7 @@ class CodexAgent {
26748
26830
  contextWindow: null,
26749
26831
  closed: false
26750
26832
  };
26751
- const runtime = this.installRuntime(session, capabilities, config2, onGateway ? GATEWAY_MODEL_PROVIDER : thread.modelProvider, readOnly);
26833
+ const runtime = this.installRuntime(session, capabilities, config2, gatewayId ?? thread.modelProvider, readOnly);
26752
26834
  const cancelOpening = () => {
26753
26835
  runtime.session.closed = true;
26754
26836
  runtime.lifetime.abort();
@@ -27046,6 +27128,27 @@ class CodexAgent {
27046
27128
  notifySkillsChanged() {
27047
27129
  this.link.notify("_codex/skills_changed", {}).catch((error40) => logger.error("skills change notification failed", error40));
27048
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
+ }
27049
27152
  async closeRuntime(sessionId) {
27050
27153
  const deadline = performance.now() + this.closeGraceMs;
27051
27154
  const runtime = this.sessions.get(sessionId);
@@ -27103,16 +27206,17 @@ class CodexAgent {
27103
27206
  const runtime = this.runtime(params.sessionId, "session/set_config_option");
27104
27207
  assertWritable(runtime);
27105
27208
  if (params.configId === MODEL_CONFIG_ID && this.providers.mode === "catalog" && typeof params.value === "string") {
27106
- const toGateway = this.providers.isGatewayModel(params.value);
27107
- if (toGateway !== (runtime.modelProvider === GATEWAY_MODEL_PROVIDER))
27108
- await this.rerouteSession(runtime, toGateway, params.value);
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);
27109
27213
  }
27110
27214
  await this.withCodex(() => applyConfigOption(runtime.session, this.codex, params));
27111
27215
  const configOptions = sessionConfigOptions(runtime.session);
27112
27216
  await runtime.client.update({ sessionUpdate: "config_option_update", configOptions });
27113
27217
  return { configOptions };
27114
27218
  }
27115
- async rerouteSession(runtime, toGateway, modelId) {
27219
+ async rerouteSession(runtime, gatewayId, modelId) {
27116
27220
  const { session } = runtime;
27117
27221
  this.assertRoutingAvailable();
27118
27222
  if (session.activeTurn !== null) {
@@ -27123,8 +27227,8 @@ class CodexAgent {
27123
27227
  } catch {
27124
27228
  await this.withCodex(() => this.codex.threadInjectItems({ threadId: session.id, items: [providerChangeNote()] }));
27125
27229
  }
27126
- const config2 = this.providers.routeConfig(runtime.config, toGateway);
27127
- const modelProvider = toGateway ? GATEWAY_MODEL_PROVIDER : await this.withCodex(() => this.resolveModelProvider());
27230
+ const config2 = this.providers.routeConfig(runtime.config, gatewayId);
27231
+ const modelProvider = gatewayId ?? await this.withCodex(() => this.resolveModelProvider());
27128
27232
  await this.codex.threadUnsubscribe({ threadId: session.id });
27129
27233
  try {
27130
27234
  await this.withCodex(() => this.codex.threadResume({ threadId: session.id, cwd: session.cwd, config: config2, modelProvider, model: modelId, excludeTurns: true }));
@@ -27135,7 +27239,7 @@ class CodexAgent {
27135
27239
  runtime.stale = true;
27136
27240
  logger.error("Provider rollback failed", rollbackError, { sessionId: session.id });
27137
27241
  }
27138
- throw RequestError.internalError({ sessionId: session.id, stale: runtime.stale }, `Could not move the session to ${toGateway ? "the gateway" : "Codex's provider"}: ${errorMessage(error40)}`);
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)}`);
27139
27243
  }
27140
27244
  runtime.config = config2;
27141
27245
  runtime.modelProvider = modelProvider;
@@ -27592,6 +27696,28 @@ function interruptedTurn(threadId) {
27592
27696
  turn: { id: "", items: [], itemsView: "notLoaded", status: "interrupted", error: null, startedAt: null, completedAt: null, durationMs: null }
27593
27697
  };
27594
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
+ }
27595
27721
  function objectParams() {
27596
27722
  return (raw) => {
27597
27723
  if (raw === undefined || raw === null)
@@ -27687,7 +27813,7 @@ function createAgentApp(options) {
27687
27813
  };
27688
27814
  return agent({ name: options.info.name }).onConnect((connection) => {
27689
27815
  agent2 = new CodexAgent(connection.client, options);
27690
- }).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));
27691
27817
  }
27692
27818
 
27693
27819
  // src/codex/AppServerClient.ts
@@ -28100,6 +28226,15 @@ class AppServerClient {
28100
28226
  accountRead(params) {
28101
28227
  return this.send({ method: "account/read", params });
28102
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
+ }
28103
28238
  accountLogin(params) {
28104
28239
  return this.send({ method: "account/login/start", params });
28105
28240
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.5.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": {