dsh-codex-connect 0.1.0-alpha.4.18 → 0.1.0-alpha.4.19

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/INSTALL.md CHANGED
@@ -12,7 +12,7 @@ Install `dsh-codex-connect` into one requested DeepSeek Harness profile without
12
12
 
13
13
  ## Install and validate
14
14
 
15
- The only verified combination is DSH plugin API packages `0.1.1-rc.2`, `@earendil-works/pi-ai` `0.82.1`, and Node.js `^22.19.0 || >=24.0.0`. Alpha 4.18 uses the rc.2 keyed Plugin configuration slot; users of older DSH API packages should upgrade the DSH API packages and pi-ai together, then rerun `dsh-codex-connect doctor --json` and `pnpm --silent run check:compatibility`; the contract does not make claims about future versions.
15
+ The only verified combination is DSH plugin API packages `0.1.1-rc.2`, `@earendil-works/pi-ai` `0.82.1`, and Node.js `^22.19.0 || >=24.0.0`. Alpha 4.19 uses the rc.2 keyed Plugin configuration slot; users of older DSH API packages should upgrade the DSH API packages and pi-ai together, then rerun `dsh-codex-connect doctor --json` and `pnpm --silent run check:compatibility`; the contract does not make claims about future versions.
16
16
 
17
17
  1. Check `dsh --version` or `dsh --help`. From a Harness checkout use `pnpm dsh`.
18
18
  2. Install the package:
@@ -21,7 +21,7 @@ The only verified combination is DSH plugin API packages `0.1.1-rc.2`, `@earendi
21
21
  dsh plugin --profile web add dsh-codex-connect@alpha
22
22
  ```
23
23
 
24
- After `0.1.0-alpha.4.18` is published, pin it exactly with `dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.18`. If npm is unavailable after its matching GitHub prerelease is created, use `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.18'`.
24
+ After `0.1.0-alpha.4.19` is published, pin it exactly with `dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.19`. If npm is unavailable after its matching GitHub prerelease is created, use `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.19'`.
25
25
 
26
26
  3. Run `dsh --profile web --dump-config` and require exactly one `llm-openai-codex` row loading `dsh-codex-connect`.
27
27
  4. Confirm the effective `agent-default-model` and `web.searchProvider` values are unchanged from before installation.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # git hash-object README.md docs/README.zh.md
5
- README.md: a08627cda39b43cbc75d48f50370d8dcb7511365
6
- docs/README.zh.md: 3b018afca725285aa933eccc8b1c65599f1bf242
5
+ README.md: ecad858eb993cfc54b97eb8d8d0518feb0b23f17
6
+ docs/README.zh.md: 2d61465cfd3fde855d912ed39363563b46730a23
package/README.md CHANGED
@@ -28,7 +28,7 @@ dsh plugin --profile web add dsh-codex-connect@alpha
28
28
 
29
29
  Expected result: the package is added to that profile. This does not change the profile's default model or global search route.
30
30
 
31
- To reproduce this release exactly, use `dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.18`. If npm is unavailable after the matching GitHub prerelease exists, use `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.18'`. A local checkout can be installed as `link:/absolute/path/to/dsh-codex-connect`.
31
+ To reproduce this release exactly, use `dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.19`. If npm is unavailable after the matching GitHub prerelease exists, use `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.19'`. A local checkout can be installed as `link:/absolute/path/to/dsh-codex-connect`.
32
32
 
33
33
  ### Version updates
34
34
 
@@ -84,6 +84,21 @@ Open Harness's normal model picker and select an `openai-codex` model for the ag
84
84
 
85
85
  The picker groups the available entries under **OpenAI Codex**. Model identifiers such as `GPT-5.6 Luna` are canonical names, so they intentionally remain un-translated.
86
86
 
87
+ To shorten that list, open **Settings → Plugins → Plugin configuration → Codex Connect**, uncheck the models you do not want to see, and select **Save changes**. This controls discovery only: a hidden model already stored in an existing conversation or supplied by its exact id remains usable. A fresh installation shows the complete catalog.
88
+
89
+ Profiles may also seed the visible subset with `models`; provider order is preserved regardless of the order written here:
90
+
91
+ ```yaml
92
+ - id: llm-openai-codex
93
+ config:
94
+ models:
95
+ - gpt-5.6-luna
96
+ - gpt-5.6-sol
97
+ - gpt-5.6-terra
98
+ ```
99
+
100
+ Omit `models` to show the full catalog. An empty list hides every Codex model from selectors without disabling exact-id routing.
101
+
87
102
  <p align="center">
88
103
  <img src="https://raw.githubusercontent.com/franksong2702/dsh-codex-connect/main/docs/assets/en/model-selector.jpg" alt="OpenAI Codex model group in the English-localized DeepSeek Harness model picker" width="360">
89
104
  </p>
@@ -194,6 +209,7 @@ Selecting Codex as the profile's global search route is another explicit change:
194
209
 
195
210
  | Field | Default | Values |
196
211
  |---|---:|---|
212
+ | `models` | full catalog | Codex model id array; empty hides all entries |
197
213
  | `enableSearch` | `false` | boolean |
198
214
  | `enableImageTool` | `false` | boolean |
199
215
  | `enableImageGeneration` | `false` | boolean |
@@ -223,8 +239,9 @@ Selecting Codex as the profile's global search route is another explicit change:
223
239
 
224
240
  ## Compatibility and security boundary
225
241
 
226
- - The only verified compatibility combination is DSH plugin API packages `0.1.1-rc.2`, `@earendil-works/pi-ai` `0.82.1`, and Node.js `^22.19.0 || >=24.0.0`; see [compatibility.json](compatibility.json). Alpha 4.18 uses the rc.2 keyed Plugin configuration slot; users of older DSH API packages should upgrade to the rc.2 API packages.
242
+ - The only verified compatibility combination is DSH plugin API packages `0.1.1-rc.2`, `@earendil-works/pi-ai` `0.82.1`, and Node.js `^22.19.0 || >=24.0.0`; see [compatibility.json](compatibility.json). Alpha 4.19 uses the rc.2 keyed Plugin configuration slot; users of older DSH API packages should upgrade to the rc.2 API packages.
227
243
  - Upgrade the DSH plugin API packages and `@earendil-works/pi-ai` as one group, then run `dsh-codex-connect doctor --json` and the compatibility check again. This contract does not make claims about future versions.
244
+ - When the daily upstream check finds a new `latest` or `next` DSH candidate, it installs Codex Connect into an isolated profile, boots the installed model runtime without OAuth credentials, verifies model and reasoning-effort discovery, and confirms provider disposal. Live sign-in, quota, and model requests still require manual validation in the test profile.
228
245
  - ChatGPT plan eligibility, model access, quotas, and backend behavior are controlled by OpenAI and may change.
229
246
  - The Codex endpoint does not enforce the ordinary Responses `max_output_tokens` field. Harness compaction still works, but that summary cap cannot be imposed server-side on this route.
230
247
  - Shell, filesystem, skills, MCP, subagents, approvals, permissions, attachments, session persistence, compaction, and recovery continue to come from the active Harness profile.
package/docs/README.zh.md CHANGED
@@ -28,7 +28,7 @@ dsh plugin --profile web add dsh-codex-connect@alpha
28
28
 
29
29
  预期结果:包被加入该 profile。这个动作不会更改 profile 的默认模型或全局搜索路由。
30
30
 
31
- 如需精确复现这个版本,使用 `dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.18`。对应 GitHub prerelease 已创建但 npm 不可用时,可使用 `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.18'`。本地 checkout 可安装为 `link:/absolute/path/to/dsh-codex-connect`。
31
+ 如需精确复现这个版本,使用 `dsh plugin --profile web add dsh-codex-connect@0.1.0-alpha.4.19`。对应 GitHub prerelease 已创建但 npm 不可用时,可使用 `dsh plugin --profile web add 'github:franksong2702/dsh-codex-connect#v0.1.0-alpha.4.19'`。本地 checkout 可安装为 `link:/absolute/path/to/dsh-codex-connect`。
32
32
 
33
33
  ### 版本更新提醒
34
34
 
@@ -84,6 +84,21 @@ dsh web
84
84
 
85
85
  选择器会把可用项归在 **OpenAI Codex** 下。`GPT-5.6 Luna` 一类模型标识是规范名称,因此会保留原样,不翻译。
86
86
 
87
+ 如需缩短这个列表,打开 **设置 → 插件 → 插件配置 → Codex Connect**,取消勾选不想显示的模型,然后点击 **保存更改**。这个设置只控制模型发现:已有会话中保存的隐藏模型,或通过精确模型 ID 指定的隐藏模型,仍可继续使用。全新安装默认显示完整目录。
88
+
89
+ 也可以在 profile 中通过 `models` 设置初始显示子集;无论这里如何排列,选择器仍保持提供方的原始顺序:
90
+
91
+ ```yaml
92
+ - id: llm-openai-codex
93
+ config:
94
+ models:
95
+ - gpt-5.6-luna
96
+ - gpt-5.6-sol
97
+ - gpt-5.6-terra
98
+ ```
99
+
100
+ 省略 `models` 时显示完整目录;空列表会在选择器中隐藏全部 Codex 模型,但不会禁用精确模型 ID 路由。
101
+
87
102
  <p align="center">
88
103
  <img src="https://raw.githubusercontent.com/franksong2702/dsh-codex-connect/main/docs/assets/zh/model-selector.jpg" alt="中文 DeepSeek Harness 模型选择器中的 OpenAI Codex 模型分组" width="360">
89
104
  </p>
@@ -194,6 +209,7 @@ dsh plugin --profile web exec dsh-codex-connect doctor --json
194
209
 
195
210
  | 字段 | 默认值 | 可选值 |
196
211
  |---|---:|---|
212
+ | `models` | 完整目录 | Codex model id 数组;空数组隐藏全部条目 |
197
213
  | `enableSearch` | `false` | boolean |
198
214
  | `enableImageTool` | `false` | boolean |
199
215
  | `enableImageGeneration` | `false` | boolean |
@@ -223,8 +239,9 @@ dsh plugin --profile web exec dsh-codex-connect doctor --json
223
239
 
224
240
  ## 兼容性与安全边界
225
241
 
226
- - 当前唯一已验证的兼容组合是 DSH 插件 API packages `0.1.1-rc.2`、`@earendil-works/pi-ai` `0.82.1` 和 Node.js `^22.19.0 || >=24.0.0`;详见 [compatibility.json](../compatibility.json)。Alpha 4.18 使用 rc.2 的 keyed 插件配置 slot;旧版 DSH API packages 用户应升级到 rc.2 API packages。
242
+ - 当前唯一已验证的兼容组合是 DSH 插件 API packages `0.1.1-rc.2`、`@earendil-works/pi-ai` `0.82.1` 和 Node.js `^22.19.0 || >=24.0.0`;详见 [compatibility.json](../compatibility.json)。Alpha 4.19 使用 rc.2 的 keyed 插件配置 slot;旧版 DSH API packages 用户应升级到 rc.2 API packages。
227
243
  - 升级时请将 DSH 插件 API packages 与 `@earendil-works/pi-ai` 作为一组升级,再运行 `dsh-codex-connect doctor --json` 和兼容性检查。本契约不对未来版本作判断。
244
+ - 每日上游检查发现新的 DSH `latest` 或 `next` 候选版本时,会把 Codex Connect 安装到隔离 Profile 中,在没有 OAuth 凭据的情况下启动已安装的模型运行时,验证模型与推理强度发现,并确认提供方可被正确卸载。真实登录、额度和模型请求仍需在测试 Profile 中人工验证。
228
245
  - ChatGPT 套餐资格、模型权限、额度和后端行为由 OpenAI 控制,可能变化。
229
246
  - Codex 端点不会强制普通 Responses 的 `max_output_tokens` 字段。Harness 压缩仍可工作,但这个摘要上限不能由服务端在该路由上强制。
230
247
  - shell、文件系统、skills、MCP、subagents、审批、权限、附件、会话持久化、压缩与恢复继续由当前 Harness profile 提供。
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { L as diagnoseOpenAICodex, c as migrateOpenAICodexSearchHistory, ct as normalizeTrustedOrigin, ft as loginOpenAICodex, mt as openAICodexAuthStatus, pt as logoutOpenAICodex, st as OpenAICodexTrustedOriginsStore, vt as openAICodexAuthPath, z as CODEX_CONNECT_VERSION } from "./src-DY3uYj_g.js";
2
+ import { L as diagnoseOpenAICodex, c as migrateOpenAICodexSearchHistory, ct as normalizeTrustedOrigin, ft as loginOpenAICodex, mt as openAICodexAuthStatus, pt as logoutOpenAICodex, st as OpenAICodexTrustedOriginsStore, vt as openAICodexAuthPath, z as CODEX_CONNECT_VERSION } from "./src-CiOi4nFr.js";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { realpathSync } from "node:fs";
5
5
  import { spawn } from "node:child_process";
package/lib/client.js CHANGED
@@ -12,6 +12,7 @@ window.__ModuleLoader__.load({
12
12
  /** Stable Harness settings namespace owned by this plugin. */
13
13
  const OPENAI_CODEX_SETTINGS_NAMESPACE = "llm-openai-codex";
14
14
  Object.freeze({
15
+ models: void 0,
15
16
  enableSearch: false,
16
17
  enableImageTool: false,
17
18
  enableImageGeneration: false,
@@ -26,6 +27,7 @@ window.__ModuleLoader__.load({
26
27
  /** Narrow the redacted settings wire payload before it enters React state. */
27
28
  function decodeOpenAICodexSettings(value) {
28
29
  if (!isRecord$2(value)) return void 0;
30
+ const models = value["models"];
29
31
  const enableSearch = value["enableSearch"];
30
32
  const enableImageTool = value["enableImageTool"];
31
33
  const enableImageGeneration = value["enableImageGeneration"];
@@ -33,6 +35,7 @@ window.__ModuleLoader__.load({
33
35
  const searchMode = value["searchMode"];
34
36
  const searchContextSize = value["searchContextSize"];
35
37
  const searchMaxOutputTokens = value["searchMaxOutputTokens"];
38
+ if (models !== void 0 && (!Array.isArray(models) || models.some((model) => typeof model !== "string"))) return void 0;
36
39
  if (typeof enableSearch !== "boolean" || typeof enableImageTool !== "boolean") return void 0;
37
40
  if (enableImageGeneration !== void 0 && typeof enableImageGeneration !== "boolean") return void 0;
38
41
  if (typeof searchModel !== "string" || searchModel.trim().length === 0) return void 0;
@@ -40,6 +43,7 @@ window.__ModuleLoader__.load({
40
43
  if (searchContextSize !== "low" && searchContextSize !== "medium" && searchContextSize !== "high") return void 0;
41
44
  if (typeof searchMaxOutputTokens !== "number" || !Number.isInteger(searchMaxOutputTokens) || searchMaxOutputTokens < 1) return void 0;
42
45
  return {
46
+ models: models === void 0 ? void 0 : [...new Set(models)],
43
47
  enableSearch,
44
48
  enableImageTool,
45
49
  enableImageGeneration: enableImageGeneration ?? false,
@@ -59,6 +63,30 @@ window.__ModuleLoader__.load({
59
63
  /** Plugin-owned logout endpoint consumed by its browser half. */
60
64
  const OPENAI_CODEX_AUTH_LOGOUT_PATH = "/plugins/dsh-openai-codex/auth/logout";
61
65
  //#endregion
66
+ //#region src/model-contract.ts
67
+ /** Node-free model catalog contract shared by the Host route and browser card. */
68
+ /** Same-origin endpoint exposing the complete Codex model catalog. */
69
+ const OPENAI_CODEX_MODEL_CATALOG_PATH = "/plugins/dsh-codex-connect/models";
70
+ /** Validate the model catalog before it enters React state. */
71
+ function decodeOpenAICodexModelCatalog(value) {
72
+ if (!Array.isArray(value)) return void 0;
73
+ const catalog = [];
74
+ const ids = /* @__PURE__ */ new Set();
75
+ for (const entry of value) {
76
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return void 0;
77
+ const record = entry;
78
+ const id = record["id"];
79
+ const name = record["name"];
80
+ if (typeof id !== "string" || id.length === 0 || typeof name !== "string" || name.length === 0 || ids.has(id)) return void 0;
81
+ ids.add(id);
82
+ catalog.push({
83
+ id,
84
+ name
85
+ });
86
+ }
87
+ return catalog;
88
+ }
89
+ //#endregion
62
90
  //#region src/client/OpenAICodexConfiguration.tsx
63
91
  /** Staged optional-capability editor inside the OpenAI Codex plugin card. */
64
92
  const sectionStyle$1 = {
@@ -89,6 +117,25 @@ window.__ModuleLoader__.load({
89
117
  padding: 0,
90
118
  border: 0
91
119
  };
120
+ const modelListStyle = {
121
+ display: "grid",
122
+ gridTemplateColumns: "repeat(auto-fit, minmax(210px, 1fr))",
123
+ gap: 10
124
+ };
125
+ const modelRowStyle = {
126
+ display: "flex",
127
+ alignItems: "center",
128
+ gap: 9,
129
+ minHeight: 30,
130
+ fontSize: 13,
131
+ color: "var(--dsw-alias-label-primary)",
132
+ cursor: "pointer"
133
+ };
134
+ const modelIdStyle = {
135
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
136
+ fontSize: 12,
137
+ color: "var(--dsw-alias-label-secondary)"
138
+ };
92
139
  const toggleRowStyle = {
93
140
  display: "flex",
94
141
  alignItems: "flex-start",
@@ -175,6 +222,7 @@ window.__ModuleLoader__.load({
175
222
  mode: "memory"
176
223
  };
177
224
  const CONFIG_FIELDS = [
225
+ "models",
178
226
  "enableSearch",
179
227
  "enableImageTool",
180
228
  "enableImageGeneration",
@@ -183,8 +231,13 @@ window.__ModuleLoader__.load({
183
231
  "searchContextSize",
184
232
  "searchMaxOutputTokens"
185
233
  ];
234
+ function sameField(field, left, right) {
235
+ if (field !== "models") return left === right;
236
+ if (left === void 0 || right === void 0) return left === right;
237
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((model, index) => model === right[index]);
238
+ }
186
239
  function sameConfig(left, right) {
187
- return left !== void 0 && right !== void 0 && CONFIG_FIELDS.every((field) => left[field] === right[field]);
240
+ return left !== void 0 && right !== void 0 && CONFIG_FIELDS.every((field) => sameField(field, left[field], right[field]));
188
241
  }
189
242
  /** Edit the Host-owned llm-openai-codex settings section with Save/Discard staging. */
190
243
  function OpenAICodexConfiguration({ scope, t }) {
@@ -195,6 +248,29 @@ window.__ModuleLoader__.load({
195
248
  const [dirty, setDirty] = (0, react.useState)(false);
196
249
  const [busy, setBusy] = (0, react.useState)(false);
197
250
  const [feedback, setFeedback] = (0, react.useState)("idle");
251
+ const [modelCatalog, setModelCatalog] = (0, react.useState)();
252
+ const [modelCatalogError, setModelCatalogError] = (0, react.useState)(false);
253
+ (0, react.useEffect)(() => {
254
+ if (scope === void 0) return;
255
+ const controller = new AbortController();
256
+ fetch(OPENAI_CODEX_MODEL_CATALOG_PATH, {
257
+ method: "GET",
258
+ credentials: "same-origin",
259
+ headers: { accept: "application/json" },
260
+ signal: controller.signal
261
+ }).then(async (response) => {
262
+ if (!response.ok) throw new Error(`model catalog request failed: ${String(response.status)}`);
263
+ const catalog = decodeOpenAICodexModelCatalog(await response.json());
264
+ if (catalog === void 0) throw new Error("model catalog response was invalid");
265
+ setModelCatalog(catalog);
266
+ setModelCatalogError(false);
267
+ }).catch(() => {
268
+ if (!controller.signal.aborted) setModelCatalogError(true);
269
+ });
270
+ return () => {
271
+ controller.abort();
272
+ };
273
+ }, [scope]);
198
274
  (0, react.useEffect)(() => {
199
275
  if (!dirty && !busy) setDraft(snapshot.value);
200
276
  }, [
@@ -229,9 +305,11 @@ window.__ModuleLoader__.load({
229
305
  setFeedback("idle");
230
306
  try {
231
307
  for (const field of CONFIG_FIELDS) {
232
- if (scope.getSnapshot().value?.[field] === desired[field]) continue;
308
+ const accepted = scope.getSnapshot().value;
309
+ if (accepted !== void 0 && sameField(field, accepted[field], desired[field])) continue;
233
310
  await scope.set(field, desired[field]);
234
- if (scope.getSnapshot().value?.[field] !== desired[field]) throw new Error(`Host refused ${field}`);
311
+ const committed = scope.getSnapshot().value;
312
+ if (committed === void 0 || !sameField(field, committed[field], desired[field])) throw new Error(`Host refused ${field}`);
235
313
  }
236
314
  const accepted = scope.getSnapshot().value;
237
315
  if (!sameConfig(accepted, desired)) throw new Error("Host returned a different configuration");
@@ -251,19 +329,8 @@ window.__ModuleLoader__.load({
251
329
  const searchDisabled = !editable || draft?.enableSearch !== true;
252
330
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
253
331
  style: sectionStyle$1,
254
- "aria-labelledby": "openai-codex-capabilities-title",
332
+ "aria-label": t("configurationHeading"),
255
333
  children: [
256
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
257
- id: "openai-codex-capabilities-title",
258
- style: headingStyle,
259
- children: t("capabilitiesHeading")
260
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
261
- style: {
262
- ...bodyStyle$2,
263
- marginTop: 4
264
- },
265
- children: t("capabilitiesIntro")
266
- })] }),
267
334
  loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
268
335
  style: bodyStyle$2,
269
336
  role: "status",
@@ -283,6 +350,67 @@ window.__ModuleLoader__.load({
283
350
  style: fieldsetStyle,
284
351
  disabled: !editable,
285
352
  children: [
353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
354
+ style: headingStyle,
355
+ children: t("modelCatalog")
356
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
357
+ style: {
358
+ ...bodyStyle$2,
359
+ marginTop: 4
360
+ },
361
+ children: t("modelCatalogIntro")
362
+ })] }),
363
+ modelCatalog === void 0 && !modelCatalogError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
364
+ style: bodyStyle$2,
365
+ role: "status",
366
+ children: t("modelCatalogLoading")
367
+ }) : null,
368
+ modelCatalogError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
369
+ style: errorStyle$2,
370
+ role: "alert",
371
+ children: t("modelCatalogFailed")
372
+ }) : null,
373
+ modelCatalog === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
374
+ style: modelListStyle,
375
+ role: "group",
376
+ "aria-label": t("modelCatalog"),
377
+ children: modelCatalog.map((model) => {
378
+ const selected = draft.models === void 0 || draft.models.includes(model.id);
379
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
380
+ style: modelRowStyle,
381
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
382
+ type: "checkbox",
383
+ checked: selected,
384
+ onChange: (event) => {
385
+ const visible = new Set(draft.models ?? modelCatalog.map((entry) => entry.id));
386
+ if (event.currentTarget.checked) visible.add(model.id);
387
+ else visible.delete(model.id);
388
+ update("models", modelCatalog.filter((entry) => visible.has(entry.id)).map((entry) => entry.id));
389
+ }
390
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.name === model.id ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
391
+ style: modelIdStyle,
392
+ children: [
393
+ " (",
394
+ model.id,
395
+ ")"
396
+ ]
397
+ })] })]
398
+ }, model.id);
399
+ })
400
+ }),
401
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
402
+ style: { paddingTop: 4 },
403
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
404
+ style: headingStyle,
405
+ children: t("capabilitiesHeading")
406
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
407
+ style: {
408
+ ...bodyStyle$2,
409
+ marginTop: 4
410
+ },
411
+ children: t("capabilitiesIntro")
412
+ })]
413
+ }),
286
414
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
287
415
  style: toggleRowStyle,
288
416
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -492,7 +620,8 @@ window.__ModuleLoader__.load({
492
620
  "dsh-rc7",
493
621
  "search-stability",
494
622
  "image-generation",
495
- "oauth-history"
623
+ "oauth-history",
624
+ "model-visibility"
496
625
  ];
497
626
  function isHighlightKind(value) {
498
627
  return typeof value === "string" && HIGHLIGHT_KINDS.includes(value);
@@ -995,7 +1124,8 @@ window.__ModuleLoader__.load({
995
1124
  "dsh-rc7": "updateHighlightDshRc7",
996
1125
  "search-stability": "updateHighlightSearchStability",
997
1126
  "image-generation": "updateHighlightImageGeneration",
998
- "oauth-history": "updateHighlightOauthHistory"
1127
+ "oauth-history": "updateHighlightOauthHistory",
1128
+ "model-visibility": "updateHighlightModelVisibility"
999
1129
  };
1000
1130
  const compatibilityTitleKeys = {
1001
1131
  compatible: "compatibilityCompatibleTitle",
@@ -2430,6 +2560,11 @@ window.__ModuleLoader__.load({
2430
2560
  remoteOriginCopy: "Copy trust command",
2431
2561
  remoteOriginCopied: "Copied",
2432
2562
  remoteOriginCopyFailed: "Copy was unavailable. Copy the command manually.",
2563
+ configurationHeading: "Codex Connect configuration",
2564
+ modelCatalog: "Models shown in the selector",
2565
+ modelCatalogIntro: "Choose which Codex models appear in model selectors. Existing conversations can continue using a hidden model.",
2566
+ modelCatalogLoading: "Loading available models…",
2567
+ modelCatalogFailed: "Available models could not be loaded.",
2433
2568
  capabilitiesHeading: "Optional capabilities",
2434
2569
  capabilitiesIntro: "Choose which extra Codex capabilities this dsh profile may register.",
2435
2570
  enableSearch: "Enable Codex search provider",
@@ -2539,6 +2674,7 @@ window.__ModuleLoader__.load({
2539
2674
  updateHighlightSearchStability: "Improved search-session stability so new search records remain readable after restarts.",
2540
2675
  updateHighlightImageGeneration: "Added optional GPT Image generation in the Codex Connect settings and a preview card in the conversation.",
2541
2676
  updateHighlightOauthHistory: "Added safer session-history migration and a sign-in fallback when the account needs to reconnect.",
2677
+ updateHighlightModelVisibility: "You can now choose which Codex models appear in model selectors.",
2542
2678
  viewTechnicalDetails: "View technical details",
2543
2679
  hideTechnicalDetails: "Hide technical details",
2544
2680
  technicalDetailsHeading: "Technical changes",
@@ -2608,6 +2744,11 @@ window.__ModuleLoader__.load({
2608
2744
  remoteOriginCopy: "复制授权命令",
2609
2745
  remoteOriginCopied: "已复制",
2610
2746
  remoteOriginCopyFailed: "当前无法访问剪贴板,请手动复制命令。",
2747
+ configurationHeading: "Codex Connect 配置",
2748
+ modelCatalog: "模型选择器中显示的模型",
2749
+ modelCatalogIntro: "选择要在模型选择器中显示的 Codex 模型;隐藏模型后,已有会话仍可继续使用。",
2750
+ modelCatalogLoading: "正在加载可用模型…",
2751
+ modelCatalogFailed: "无法加载可用模型。",
2611
2752
  capabilitiesHeading: "可选能力",
2612
2753
  capabilitiesIntro: "选择允许此 dsh profile 注册哪些额外的 Codex 能力。",
2613
2754
  enableSearch: "启用 Codex 搜索提供方",
@@ -2717,6 +2858,7 @@ window.__ModuleLoader__.load({
2717
2858
  updateHighlightSearchStability: "改进搜索会话稳定性,重启后新写入的搜索记录仍能正常读取。",
2718
2859
  updateHighlightImageGeneration: "在 Codex Connect 设置中加入可选的 GPT Image 生图,并在对话里展示生成结果卡片。",
2719
2860
  updateHighlightOauthHistory: "增加更安全的会话历史迁移,并在账号需要重新登录时提供备用登录路径。",
2861
+ updateHighlightModelVisibility: "现在可以选择哪些 Codex 模型显示在模型选择菜单中。",
2720
2862
  viewTechnicalDetails: "查看完整技术说明",
2721
2863
  hideTechnicalDetails: "收起技术说明",
2722
2864
  technicalDetailsHeading: "技术变更",
@@ -3805,7 +3947,7 @@ window.__ModuleLoader__.load({
3805
3947
  }
3806
3948
  //#endregion
3807
3949
  //#region src/version.ts
3808
- const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.18";
3950
+ const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.19";
3809
3951
  //#endregion
3810
3952
  //#region src/client/index.tsx
3811
3953
  /** Stable browser-plugin name. */
package/lib/index.d.ts CHANGED
@@ -311,6 +311,8 @@ declare const DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE: OpenAICodexSearchContext
311
311
  declare const DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS = 10000;
312
312
  /** Fully resolved user-editable section presented by Plugin configuration. */
313
313
  interface OpenAICodexSettingsConfig {
314
+ /** Model ids advertised in selectors; undefined advertises the full catalog. */
315
+ models: string[] | undefined;
314
316
  enableSearch: boolean;
315
317
  enableImageTool: boolean;
316
318
  enableImageGeneration: boolean;
@@ -476,7 +478,7 @@ declare const OPENAI_CODEX_FAST_MODE_PATH = "/plugins/dsh-openai-codex/fast-mode
476
478
  declare const OPENAI_CODEX_UPDATE_PATH = "/openai-codex/update";
477
479
  //#endregion
478
480
  //#region src/update.d.ts
479
- type OpenAICodexUpdateHighlightKind = 'trusted-origins' | 'runtime-compatibility' | 'quota-fast-mode' | 'dsh-rc7' | 'search-stability' | 'image-generation' | 'oauth-history';
481
+ type OpenAICodexUpdateHighlightKind = 'trusted-origins' | 'runtime-compatibility' | 'quota-fast-mode' | 'dsh-rc7' | 'search-stability' | 'image-generation' | 'oauth-history' | 'model-visibility';
480
482
  interface OpenAICodexUpdateHighlight {
481
483
  version: string;
482
484
  kind: OpenAICodexUpdateHighlightKind;
@@ -583,6 +585,8 @@ declare const inject: string[];
583
585
  declare const OPENAI_CODEX_SETTINGS_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
584
586
  /** Composite model and standalone-search configuration. */
585
587
  interface Config {
588
+ /** Model ids advertised in selectors; omitted to advertise the full catalog. */
589
+ models?: string[] | undefined;
586
590
  /** Register the optional standalone Codex search provider. */
587
591
  enableSearch?: boolean;
588
592
  /** Register the optional image-loading tool. */
package/lib/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as assessCompatibility, A as OPENAI_CODEX_TRANSPORT_SERVICE, B as OPENAI_CODEX_UPDATE_PATH, C as OPENAI_CODEX_IMAGE_MAX_COUNT, D as OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS, E as OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH, F as IMAGE_GENERATE_TOOL_NAME, G as COMPATIBILITY_CONTRACT, H as compareOpenAICodexVersions, I as assertNoOpenAICodexProviderConflict, J as DSH_PLUGIN_API_PACKAGES, K as COMPATIBILITY_PACKAGES, L as diagnoseOpenAICodex, M as OpenAICodexTransportError, N as isOpenAICodexTransportError, O as OPENAI_CODEX_TRANSPORT_API_VERSION, P as VIEW_IMAGE_TOOL_NAME, Q as SUPPORTED_PI_AI_VERSION, R as openAICodexConflictMessage, S as OPENAI_CODEX_IMAGE_GENERATION_URL, T as OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES, U as parseOpenAICodexUpdateResult, V as checkForOpenAICodexUpdate, W as parseOpenAICodexVersion, X as SUPPORTED_DSH_PLUGIN_API_VERSION, Y as PI_AI_PACKAGE, Z as SUPPORTED_NODE_RANGE, _ as DEFAULT_OPENAI_CODEX_SEARCH_MODEL, _t as OpenAICodexCredentialStore, a as name, at as OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH, b as decodeOpenAICodexSettings, c as migrateOpenAICodexSearchHistory, d as OPENAI_CODEX_SEARCH_URL, dt as readOpenAICodexRateLimits, et as detectCompatibility, f as OpenAICodexSearchProvider, ft as loginOpenAICodex, g as DEFAULT_OPENAI_CODEX_SEARCH_MODE, gt as OPENAI_CODEX_PROVIDER, h as DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS, ht as OPENAI_CODEX_AUTH_FILENAME, i as inject, it as OPENAI_CODEX_FAST_MODE_MAX_SESSIONS, j as OpenAICodexTransport, k as OPENAI_CODEX_TRANSPORT_ERROR_CODES, l as OPENAI_CODEX_BASE_URL, lt as OPENAI_CODEX_USAGE_URL, m as DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE, mt as openAICodexAuthStatus, n as OPENAI_CODEX_SETTINGS_NS, nt as OPENAI_CODEX_FAST_MODE_PATH, o as OPENAI_CODEX_HISTORY_BACKUP_SUFFIX, ot as isFastModeSessionId, p as mapOpenAICodexSearchResponse, pt as logoutOpenAICodex, q as COMPATIBILITY_SCHEMA_VERSION, r as apply, rt as FastModeRegistry, s as OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT, t as Config, tt as evaluateCompatibility, u as OPENAI_CODEX_SEARCH_PROVIDER, ut as parseOpenAICodexUsage, v as DEFAULT_OPENAI_CODEX_SETTINGS, vt as openAICodexAuthPath, w as OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES, x as resolveOpenAICodexSettings, y as OPENAI_CODEX_SETTINGS_NAMESPACE } from "./src-DY3uYj_g.js";
1
+ import { $ as assessCompatibility, A as OPENAI_CODEX_TRANSPORT_SERVICE, B as OPENAI_CODEX_UPDATE_PATH, C as OPENAI_CODEX_IMAGE_MAX_COUNT, D as OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS, E as OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH, F as IMAGE_GENERATE_TOOL_NAME, G as COMPATIBILITY_CONTRACT, H as compareOpenAICodexVersions, I as assertNoOpenAICodexProviderConflict, J as DSH_PLUGIN_API_PACKAGES, K as COMPATIBILITY_PACKAGES, L as diagnoseOpenAICodex, M as OpenAICodexTransportError, N as isOpenAICodexTransportError, O as OPENAI_CODEX_TRANSPORT_API_VERSION, P as VIEW_IMAGE_TOOL_NAME, Q as SUPPORTED_PI_AI_VERSION, R as openAICodexConflictMessage, S as OPENAI_CODEX_IMAGE_GENERATION_URL, T as OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES, U as parseOpenAICodexUpdateResult, V as checkForOpenAICodexUpdate, W as parseOpenAICodexVersion, X as SUPPORTED_DSH_PLUGIN_API_VERSION, Y as PI_AI_PACKAGE, Z as SUPPORTED_NODE_RANGE, _ as DEFAULT_OPENAI_CODEX_SEARCH_MODEL, _t as OpenAICodexCredentialStore, a as name, at as OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH, b as decodeOpenAICodexSettings, c as migrateOpenAICodexSearchHistory, d as OPENAI_CODEX_SEARCH_URL, dt as readOpenAICodexRateLimits, et as detectCompatibility, f as OpenAICodexSearchProvider, ft as loginOpenAICodex, g as DEFAULT_OPENAI_CODEX_SEARCH_MODE, gt as OPENAI_CODEX_PROVIDER, h as DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS, ht as OPENAI_CODEX_AUTH_FILENAME, i as inject, it as OPENAI_CODEX_FAST_MODE_MAX_SESSIONS, j as OpenAICodexTransport, k as OPENAI_CODEX_TRANSPORT_ERROR_CODES, l as OPENAI_CODEX_BASE_URL, lt as OPENAI_CODEX_USAGE_URL, m as DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE, mt as openAICodexAuthStatus, n as OPENAI_CODEX_SETTINGS_NS, nt as OPENAI_CODEX_FAST_MODE_PATH, o as OPENAI_CODEX_HISTORY_BACKUP_SUFFIX, ot as isFastModeSessionId, p as mapOpenAICodexSearchResponse, pt as logoutOpenAICodex, q as COMPATIBILITY_SCHEMA_VERSION, r as apply, rt as FastModeRegistry, s as OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT, t as Config, tt as evaluateCompatibility, u as OPENAI_CODEX_SEARCH_PROVIDER, ut as parseOpenAICodexUsage, v as DEFAULT_OPENAI_CODEX_SETTINGS, vt as openAICodexAuthPath, w as OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES, x as resolveOpenAICodexSettings, y as OPENAI_CODEX_SETTINGS_NAMESPACE } from "./src-CiOi4nFr.js";
2
2
  export { COMPATIBILITY_CONTRACT, COMPATIBILITY_PACKAGES, COMPATIBILITY_SCHEMA_VERSION, Config, DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE, DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS, DEFAULT_OPENAI_CODEX_SEARCH_MODE, DEFAULT_OPENAI_CODEX_SEARCH_MODEL, DEFAULT_OPENAI_CODEX_SETTINGS, DSH_PLUGIN_API_PACKAGES, FastModeRegistry, FastModeRegistry as OpenAICodexFastModeRegistry, IMAGE_GENERATE_TOOL_NAME, OPENAI_CODEX_AUTH_FILENAME, OPENAI_CODEX_BASE_URL, OPENAI_CODEX_FAST_MODE_MAX_SESSIONS, OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH, OPENAI_CODEX_FAST_MODE_PATH, OPENAI_CODEX_HISTORY_BACKUP_SUFFIX, OPENAI_CODEX_IMAGE_GENERATION_URL, OPENAI_CODEX_IMAGE_MAX_COUNT, OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES, OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES, OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH, OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS, OPENAI_CODEX_PROVIDER, OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT, OPENAI_CODEX_SEARCH_PROVIDER, OPENAI_CODEX_SEARCH_URL, OPENAI_CODEX_SETTINGS_NAMESPACE, OPENAI_CODEX_SETTINGS_NS, OPENAI_CODEX_TRANSPORT_API_VERSION, OPENAI_CODEX_TRANSPORT_ERROR_CODES, OPENAI_CODEX_TRANSPORT_SERVICE, OPENAI_CODEX_UPDATE_PATH, OPENAI_CODEX_USAGE_URL, OpenAICodexCredentialStore, OpenAICodexSearchProvider, OpenAICodexTransport, OpenAICodexTransportError, PI_AI_PACKAGE, SUPPORTED_DSH_PLUGIN_API_VERSION, SUPPORTED_NODE_RANGE, SUPPORTED_PI_AI_VERSION, VIEW_IMAGE_TOOL_NAME, apply, assertNoOpenAICodexProviderConflict, assessCompatibility, checkForOpenAICodexUpdate, compareOpenAICodexVersions, decodeOpenAICodexSettings, detectCompatibility, diagnoseOpenAICodex, evaluateCompatibility, inject, isFastModeSessionId, isOpenAICodexTransportError, loginOpenAICodex, logoutOpenAICodex, mapOpenAICodexSearchResponse, migrateOpenAICodexSearchHistory, name, openAICodexAuthPath, openAICodexAuthStatus, openAICodexConflictMessage, parseOpenAICodexUpdateResult, parseOpenAICodexUsage, parseOpenAICodexVersion, readOpenAICodexRateLimits, resolveOpenAICodexSettings };
@@ -164,6 +164,13 @@ var OpenAICodexCredentialStore = class {
164
164
  //#endregion
165
165
  //#region src/adapter.ts
166
166
  /** OpenAI Codex adapter assembled from public dsh-llm-pi-ai extension points. */
167
+ /** Return a detached copy of the complete pi-ai Codex model catalog. */
168
+ function openAICodexModelCatalog() {
169
+ return openaiCodexProvider().getModels().map((model) => ({
170
+ id: model.id,
171
+ name: model.name
172
+ }));
173
+ }
167
174
  /** Provider idle ceiling used by the composite route. */
168
175
  const OPENAI_CODEX_STREAM_IDLE_TIMEOUT_MS = 3e5;
169
176
  /** rc.2 default maximum base64 image payload retained in one request. */
@@ -243,12 +250,21 @@ function createOpenAICodexProfile(provider, fastMode) {
243
250
  * resolution, streaming, reasoning metadata, and compaction behavior; this
244
251
  * plugin supplies its provider-native OAuth token for each request.
245
252
  */
246
- function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode) {
253
+ function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode, visibleModelIds) {
247
254
  const provider = openaiCodexProvider();
248
255
  const profiles = /* @__PURE__ */ new Map([[OPENAI_CODEX_PROVIDER, createOpenAICodexProfile(provider, fastMode)]]);
249
256
  const models = createModels({ credentials });
250
257
  models.setProvider(provider);
251
- return new PiAiAdapter({
258
+ class OpenAICodexAdapter extends PiAiAdapter {
259
+ async listModels(providerId) {
260
+ const catalog = await super.listModels(providerId);
261
+ const configured = visibleModelIds?.();
262
+ if (configured === void 0) return catalog;
263
+ const visible = new Set(configured);
264
+ return catalog.filter((model) => visible.has(model.id));
265
+ }
266
+ }
267
+ return new OpenAICodexAdapter({
252
268
  profiles: () => profiles,
253
269
  resolveApiKey: async () => (await models.getAuth(OPENAI_CODEX_PROVIDER))?.auth.apiKey,
254
270
  auth: {
@@ -928,7 +944,7 @@ async function trustedRequestDecision(req, trustedOrigins = new OpenAICodexTrust
928
944
  error: REMOTE_WEB_ORIGIN_NOT_TRUSTED
929
945
  };
930
946
  }
931
- function json$1(res, status, value) {
947
+ function json$2(res, status, value) {
932
948
  res.writeHead(status, {
933
949
  "content-type": "application/json; charset=utf-8",
934
950
  "cache-control": "no-store",
@@ -1018,7 +1034,7 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
1018
1034
  const authorize = async (req, res) => {
1019
1035
  const decision = await trustedRequestDecision(req, trustedOrigins);
1020
1036
  if (decision.trusted) return true;
1021
- json$1(res, 403, { error: decision.error });
1037
+ json$2(res, 403, { error: decision.error });
1022
1038
  return false;
1023
1039
  };
1024
1040
  const routes = [
@@ -1026,21 +1042,21 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
1026
1042
  kind: "exact",
1027
1043
  path: OPENAI_CODEX_AUTH_STATUS_PATH,
1028
1044
  handler: async (req, res) => {
1029
- if (req.method !== "GET") return json$1(res, 405, { error: "method not allowed" });
1045
+ if (req.method !== "GET") return json$2(res, 405, { error: "method not allowed" });
1030
1046
  if (!await authorize(req, res)) return;
1031
- json$1(res, 200, await auth.status());
1047
+ json$2(res, 200, await auth.status());
1032
1048
  }
1033
1049
  }),
1034
1050
  ctx.webServer.register({
1035
1051
  kind: "exact",
1036
1052
  path: OPENAI_CODEX_AUTH_LOGIN_PATH,
1037
1053
  handler: async (req, res) => {
1038
- if (req.method !== "POST") return json$1(res, 405, { error: "method not allowed" });
1054
+ if (req.method !== "POST") return json$2(res, 405, { error: "method not allowed" });
1039
1055
  if (!await authorize(req, res)) return;
1040
1056
  try {
1041
- json$1(res, 200, await auth.signIn());
1057
+ json$2(res, 200, await auth.signIn());
1042
1058
  } catch (error) {
1043
- json$1(res, 500, { error: safeMessage(error) });
1059
+ json$2(res, 500, { error: safeMessage(error) });
1044
1060
  }
1045
1061
  }
1046
1062
  }),
@@ -1048,13 +1064,13 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
1048
1064
  kind: "exact",
1049
1065
  path: OPENAI_CODEX_AUTH_LOGOUT_PATH,
1050
1066
  handler: async (req, res) => {
1051
- if (req.method !== "POST") return json$1(res, 405, { error: "method not allowed" });
1067
+ if (req.method !== "POST") return json$2(res, 405, { error: "method not allowed" });
1052
1068
  if (!await authorize(req, res)) return;
1053
1069
  try {
1054
1070
  await auth.signOut();
1055
- json$1(res, 200, { ok: true });
1071
+ json$2(res, 200, { ok: true });
1056
1072
  } catch (error) {
1057
- json$1(res, 500, { error: safeMessage(error) });
1073
+ json$2(res, 500, { error: safeMessage(error) });
1058
1074
  }
1059
1075
  }
1060
1076
  }),
@@ -1062,22 +1078,22 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
1062
1078
  kind: "exact",
1063
1079
  path: OPENAI_CODEX_FAST_MODE_PATH,
1064
1080
  handler: async (req, res) => {
1065
- if (req.method !== "GET" && req.method !== "POST") return json$1(res, 405, { error: "method not allowed" });
1081
+ if (req.method !== "GET" && req.method !== "POST") return json$2(res, 405, { error: "method not allowed" });
1066
1082
  if (!await authorize(req, res)) return;
1067
1083
  if (req.method === "GET") {
1068
1084
  const sessionId = fastModeSessionIdFromQuery(req);
1069
- if (sessionId === void 0) return json$1(res, 400, { error: "invalid input" });
1070
- return json$1(res, 200, { enabled: fastMode.isEnabled(sessionId) });
1085
+ if (sessionId === void 0) return json$2(res, 400, { error: "invalid input" });
1086
+ return json$2(res, 200, { enabled: fastMode.isEnabled(sessionId) });
1071
1087
  }
1072
1088
  const type = header(req, "content-type");
1073
- if (type === void 0 || !/^application\/json(?:\s*;|$)/iu.test(type.trim())) return json$1(res, 415, { error: "unsupported content type" });
1089
+ if (type === void 0 || !/^application\/json(?:\s*;|$)/iu.test(type.trim())) return json$2(res, 415, { error: "unsupported content type" });
1074
1090
  try {
1075
1091
  const body = fastModeBody(await readFastModeBody(req));
1076
- if (body === void 0) return json$1(res, 400, { error: "invalid input" });
1092
+ if (body === void 0) return json$2(res, 400, { error: "invalid input" });
1077
1093
  fastMode.set(body.sessionId, body.enabled);
1078
- return json$1(res, 200, { enabled: fastMode.isEnabled(body.sessionId) });
1094
+ return json$2(res, 200, { enabled: fastMode.isEnabled(body.sessionId) });
1079
1095
  } catch (error) {
1080
- return json$1(res, error instanceof RangeError ? 413 : 400, { error: error instanceof RangeError ? "request body too large" : "invalid input" });
1096
+ return json$2(res, error instanceof RangeError ? 413 : 400, { error: error instanceof RangeError ? "request body too large" : "invalid input" });
1081
1097
  }
1082
1098
  }
1083
1099
  })
@@ -1242,7 +1258,8 @@ const HIGHLIGHT_KINDS = [
1242
1258
  "dsh-rc7",
1243
1259
  "search-stability",
1244
1260
  "image-generation",
1245
- "oauth-history"
1261
+ "oauth-history",
1262
+ "model-visibility"
1246
1263
  ];
1247
1264
  function isHighlightKind(value) {
1248
1265
  return typeof value === "string" && HIGHLIGHT_KINDS.includes(value);
@@ -1671,7 +1688,7 @@ async function detectCurrentDshVersion() {
1671
1688
  const adapter = report.packages["@deepseek-ai/dsh-llm-pi-ai"].installed;
1672
1689
  return llm !== null && llm === adapter ? llm : void 0;
1673
1690
  }
1674
- function json(res, status, value) {
1691
+ function json$1(res, status, value) {
1675
1692
  res.writeHead(status, {
1676
1693
  "content-type": "application/json; charset=utf-8",
1677
1694
  "cache-control": "no-store",
@@ -1686,14 +1703,14 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
1686
1703
  kind: "exact",
1687
1704
  path: OPENAI_CODEX_RUNTIME_PATH,
1688
1705
  handler: async (req, res) => {
1689
- if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
1706
+ if (req.method !== "GET") return json$1(res, 405, { error: "method not allowed" });
1690
1707
  const decision = await trustedRequestDecision(req, trustedOrigins);
1691
- if (!decision.trusted) return json(res, 403, { error: decision.error });
1708
+ if (!decision.trusted) return json$1(res, 403, { error: decision.error });
1692
1709
  try {
1693
1710
  const currentDshVersion = await (options.resolveCurrentDshVersion ?? detectCurrentDshVersion)();
1694
- return json(res, 200, currentDshVersion === void 0 ? {} : { currentDshVersion });
1711
+ return json$1(res, 200, currentDshVersion === void 0 ? {} : { currentDshVersion });
1695
1712
  } catch {
1696
- return json(res, 200, {});
1713
+ return json$1(res, 200, {});
1697
1714
  }
1698
1715
  }
1699
1716
  });
@@ -1701,9 +1718,9 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
1701
1718
  kind: "exact",
1702
1719
  path: OPENAI_CODEX_UPDATE_PATH,
1703
1720
  handler: async (req, res) => {
1704
- if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
1721
+ if (req.method !== "GET") return json$1(res, 405, { error: "method not allowed" });
1705
1722
  const decision = await trustedRequestDecision(req, trustedOrigins);
1706
- if (!decision.trusted) return json(res, 403, { error: decision.error });
1723
+ if (!decision.trusted) return json$1(res, 403, { error: decision.error });
1707
1724
  let result;
1708
1725
  let currentDshVersion;
1709
1726
  try {
@@ -1726,7 +1743,7 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
1726
1743
  reason: "registry-unavailable"
1727
1744
  };
1728
1745
  }
1729
- return json(res, 200, result);
1746
+ return json$1(res, 200, result);
1730
1747
  }
1731
1748
  });
1732
1749
  return () => {
@@ -1736,8 +1753,36 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
1736
1753
  }, "dsh-codex-connect: update route");
1737
1754
  }
1738
1755
  //#endregion
1756
+ //#region src/model-contract.ts
1757
+ /** Node-free model catalog contract shared by the Host route and browser card. */
1758
+ /** Same-origin endpoint exposing the complete Codex model catalog. */
1759
+ const OPENAI_CODEX_MODEL_CATALOG_PATH = "/plugins/dsh-codex-connect/models";
1760
+ //#endregion
1761
+ //#region src/model-routes.ts
1762
+ function json(res, status, value) {
1763
+ res.writeHead(status, {
1764
+ "content-type": "application/json; charset=utf-8",
1765
+ "cache-control": "no-store",
1766
+ "x-content-type-options": "nosniff"
1767
+ });
1768
+ res.end(JSON.stringify(value));
1769
+ }
1770
+ /** Register the read-only catalog route consumed by Plugin configuration. */
1771
+ function registerOpenAICodexModelCatalogRoute(ctx, resolveCatalog, trustedOrigins) {
1772
+ ctx.effect(() => ctx.webServer.register({
1773
+ kind: "exact",
1774
+ path: OPENAI_CODEX_MODEL_CATALOG_PATH,
1775
+ handler: async (req, res) => {
1776
+ if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
1777
+ const decision = await trustedRequestDecision(req, trustedOrigins);
1778
+ if (!decision.trusted) return json(res, 403, { error: decision.error });
1779
+ return json(res, 200, resolveCatalog());
1780
+ }
1781
+ }), "dsh-codex-connect: model catalog route");
1782
+ }
1783
+ //#endregion
1739
1784
  //#region src/version.ts
1740
- const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.18";
1785
+ const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.19";
1741
1786
  //#endregion
1742
1787
  //#region src/doctor.ts
1743
1788
  /** Secret-free diagnostics and duplicate-provider guidance. */
@@ -2739,6 +2784,7 @@ const DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE = "medium";
2739
2784
  /** Default output budget for the standalone search response. */
2740
2785
  const DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS = 1e4;
2741
2786
  const DEFAULT_OPENAI_CODEX_SETTINGS = Object.freeze({
2787
+ models: void 0,
2742
2788
  enableSearch: false,
2743
2789
  enableImageTool: false,
2744
2790
  enableImageGeneration: false,
@@ -2760,6 +2806,7 @@ function isRecord$1(value) {
2760
2806
  /** Narrow the redacted settings wire payload before it enters React state. */
2761
2807
  function decodeOpenAICodexSettings(value) {
2762
2808
  if (!isRecord$1(value)) return void 0;
2809
+ const models = value["models"];
2763
2810
  const enableSearch = value["enableSearch"];
2764
2811
  const enableImageTool = value["enableImageTool"];
2765
2812
  const enableImageGeneration = value["enableImageGeneration"];
@@ -2767,6 +2814,7 @@ function decodeOpenAICodexSettings(value) {
2767
2814
  const searchMode = value["searchMode"];
2768
2815
  const searchContextSize = value["searchContextSize"];
2769
2816
  const searchMaxOutputTokens = value["searchMaxOutputTokens"];
2817
+ if (models !== void 0 && (!Array.isArray(models) || models.some((model) => typeof model !== "string"))) return void 0;
2770
2818
  if (typeof enableSearch !== "boolean" || typeof enableImageTool !== "boolean") return void 0;
2771
2819
  if (enableImageGeneration !== void 0 && typeof enableImageGeneration !== "boolean") return void 0;
2772
2820
  if (typeof searchModel !== "string" || searchModel.trim().length === 0) return void 0;
@@ -2774,6 +2822,7 @@ function decodeOpenAICodexSettings(value) {
2774
2822
  if (searchContextSize !== "low" && searchContextSize !== "medium" && searchContextSize !== "high") return void 0;
2775
2823
  if (typeof searchMaxOutputTokens !== "number" || !Number.isInteger(searchMaxOutputTokens) || searchMaxOutputTokens < 1) return void 0;
2776
2824
  return {
2825
+ models: models === void 0 ? void 0 : [...new Set(models)],
2777
2826
  enableSearch,
2778
2827
  enableImageTool,
2779
2828
  enableImageGeneration: enableImageGeneration ?? false,
@@ -3269,6 +3318,7 @@ const inject = ["llm"];
3269
3318
  /** Branded Host settings namespace for Codex Connect capability configuration. */
3270
3319
  const OPENAI_CODEX_SETTINGS_NS = settingsNamespace(OPENAI_CODEX_SETTINGS_NAMESPACE);
3271
3320
  const Config = z.object({
3321
+ models: z.union([z.const(void 0), z.array(z.string())]),
3272
3322
  enableSearch: z.boolean().default(false),
3273
3323
  enableImageTool: z.boolean().default(false),
3274
3324
  enableImageGeneration: z.boolean().default(false),
@@ -3299,10 +3349,11 @@ function apply(ctx, config) {
3299
3349
  const fastMode = new FastModeRegistry();
3300
3350
  assertNoOpenAICodexProviderConflict(ctx.llm.listProviders().map((provider) => provider.id));
3301
3351
  new OpenAICodexTransport(ctx, credentials);
3302
- ctx.llm.registerAdapter([OPENAI_CODEX_PROVIDER], createOpenAICodexAdapter(credentials, () => ctx.get("attachments"), fastMode));
3352
+ ctx.llm.registerAdapter([OPENAI_CODEX_PROVIDER], createOpenAICodexAdapter(credentials, () => ctx.get("attachments"), fastMode, () => resolveOpenAICodexSettings(current()).models));
3303
3353
  ctx.inject(["webServer"], (webCtx) => {
3304
3354
  registerOpenAICodexAuthRoutes(webCtx, credentials, trustedOrigins, fastMode);
3305
3355
  registerOpenAICodexUpdateRoutes(webCtx, { currentVersion: CODEX_CONNECT_VERSION }, trustedOrigins);
3356
+ registerOpenAICodexModelCatalogRoute(webCtx, openAICodexModelCatalog, trustedOrigins);
3306
3357
  });
3307
3358
  let stopped = false;
3308
3359
  let searchFiber;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-codex-connect",
3
3
  "displayName": "Codex Connect",
4
4
  "description": "ChatGPT OAuth and Codex models for DeepSeek Harness.",
5
- "version": "0.1.0-alpha.4.18",
5
+ "version": "0.1.0-alpha.4.19",
6
6
  "author": "Frank Song",
7
7
  "contributors": [
8
8
  "Yan-Zero (original dsh-codex author)"