@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.
- package/CHANGELOG.md +45 -0
- package/dist/index.js +432 -84
- 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
|
|
3410
|
-
var
|
|
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 ??
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
26091
|
+
copy.gateways = new Map(this.gateways);
|
|
26075
26092
|
return copy;
|
|
26076
26093
|
}
|
|
26077
26094
|
get active() {
|
|
26078
|
-
return this.
|
|
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:
|
|
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.
|
|
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
|
|
26112
|
-
|
|
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.
|
|
26186
|
+
return this.defaultGatewayId() ?? this.configuredProvider;
|
|
26187
|
+
}
|
|
26188
|
+
routesByDefault() {
|
|
26189
|
+
return this.defaultGatewayId() !== null;
|
|
26116
26190
|
}
|
|
26117
|
-
|
|
26118
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
26123
|
-
|
|
26124
|
-
|
|
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
|
-
|
|
26242
|
+
const first = this.active;
|
|
26243
|
+
if (first === null)
|
|
26136
26244
|
return codexCatalog;
|
|
26137
|
-
|
|
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
|
-
|
|
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:
|
|
26182
|
-
defaultReasoningEffort:
|
|
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
|
|
26412
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26420
|
-
|
|
26421
|
-
const
|
|
26422
|
-
|
|
26423
|
-
|
|
26424
|
-
|
|
26425
|
-
|
|
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,
|
|
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
|
|
26443
|
-
|
|
26444
|
-
|
|
26445
|
-
|
|
26446
|
-
|
|
26447
|
-
|
|
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
|
-
|
|
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" || !
|
|
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 (
|
|
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
|
-
|
|
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,
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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 =
|
|
28566
|
+
let vendor = path8.resolve(path8.dirname(launcher), "..", "vendor");
|
|
28219
28567
|
try {
|
|
28220
|
-
vendor =
|
|
28568
|
+
vendor = path8.join(path8.dirname(platformRequire.resolve(`@openai/codex-${platform2}/package.json`)), "vendor");
|
|
28221
28569
|
} catch {}
|
|
28222
|
-
const command =
|
|
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 };
|