dsh-oauth-login 0.2.8 → 0.2.9

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
@@ -7,7 +7,7 @@
7
7
  Open **Settings → Plugins → Add plugin** and enter this in “Package name or address”:
8
8
 
9
9
  ```text
10
- github:aa2246740/dsh-oauth-login#v0.2.7
10
+ dsh-oauth-login@0.2.9
11
11
  ```
12
12
 
13
13
  The in-app plugin manager owns the Desktop profile and bundled package manager. This release includes built `lib/`; normal use needs no clone, build, or DSHX installation. Follow the app if it asks you to reload or reopen.
@@ -17,20 +17,21 @@ The in-app plugin manager owns the Desktop profile and bundled package manager.
17
17
  Stock DeepSeek Harness **0.2.0-rc.2** users install to the Web profile with the official CLI:
18
18
 
19
19
  ```sh
20
- dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
20
+ dsh plugin --profile web add dsh-oauth-login@0.2.9
21
21
  ```
22
22
 
23
23
  If `dsh` is not on PATH:
24
24
 
25
25
  ```sh
26
- npx @deepseek-ai/dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
26
+ npx @deepseek-ai/dsh plugin --profile web add dsh-oauth-login@0.2.9
27
27
  ```
28
28
 
29
- That `github:` spec works because the package declares `dsh.bundle.patch` and
30
- commits built `lib/`. Official add runs pnpm in `$DSH_HOME/profiles/web` and
29
+ The package declares `dsh.bundle.patch` and includes built `lib/`.
30
+ Official add runs pnpm in `$DSH_HOME/profiles/web` and
31
31
  appends the package to `dsh.profile.bundles`. This command writes only `web`,
32
- not the Desktop App profile. Reopen an already-running Web Host once and reload
33
- the page. You do not need Creator Mode or a second toolchain.
32
+ not the Desktop App profile. Follow the installer's activation result; reopen
33
+ the original launcher only when it reports that a restart is required.
34
+ You do not need Creator Mode or a second toolchain.
34
35
 
35
36
  Use `file:` only when editing a local clone. Keep the prefix. A bare
36
37
  `./dsh-oauth-login` becomes a symlink, while this plugin intentionally resolves
@@ -54,7 +55,7 @@ dsh plugin --profile web exec dsh-oauth-login logout openai-codex
54
55
  For Zhipu GLM Coding Plan, the command opens the official Plan page and asks
55
56
  for its API key locally. The plugin never reads your browser session.
56
57
 
57
- After upgrading the server plugin and restarting DSH once, open Settings →
58
+ After upgrading and following the installer's activation result, open Settings →
58
59
  Subscription Login → Network proxy. Configure HTTP and WebSocket independently.
59
60
  The address defaults to `http://127.0.0.1`, with the port left empty. An enabled
60
61
  channel with an explicit address and port overrides automatic discovery; an
package/INSTALL.zh.md CHANGED
@@ -7,7 +7,7 @@
7
7
  打开 **设置 → 插件 → 添加插件**,在“包名或地址”中输入:
8
8
 
9
9
  ```text
10
- github:aa2246740/dsh-oauth-login#v0.2.7
10
+ dsh-oauth-login@0.2.9
11
11
  ```
12
12
 
13
13
  应用内插件管理器负责 Desktop profile 和内置包管理器。本发布已包含编译好的 `lib/`;普通使用不需要 clone、构建或安装 DSHX。若应用提示刷新或重新打开,请按提示完成。
@@ -17,16 +17,16 @@ github:aa2246740/dsh-oauth-login#v0.2.7
17
17
  官方 DeepSeek Harness **0.2.0-rc.2** 用户用官方 CLI 安装到 Web profile:
18
18
 
19
19
  ```sh
20
- dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
20
+ dsh plugin --profile web add dsh-oauth-login@0.2.9
21
21
  ```
22
22
 
23
23
  没有 `dsh` 时:
24
24
 
25
25
  ```sh
26
- npx @deepseek-ai/dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
26
+ npx @deepseek-ai/dsh plugin --profile web add dsh-oauth-login@0.2.9
27
27
  ```
28
28
 
29
- 这条 `github:` 命令能装上,是因为包装了 `dsh.bundle.patch`,并且仓库提交了编好的 `lib/`。官方 add 在 `$DSH_HOME/profiles/web` 里跑 pnpm,再把这个包装进 `dsh.profile.bundles`。这条命令只写 `web` profile,不能修改 Desktop App 的 profile;已运行的 Web Host 需要重新打开一次,再刷新网页。不需要 Creator Mode,也不需要另装一套工具。
29
+ 包中声明了 `dsh.bundle.patch`,并包含编好的 `lib/`。官方 add 在 `$DSH_HOME/profiles/web` 里跑 pnpm,再把这个包装进 `dsh.profile.bundles`。这条命令只写 `web` profile,不能修改 Desktop App 的 profile;按安装器的生效结果继续,只有提示需要重启时才用原启动方式重新打开。不需要 Creator Mode,也不需要另装一套工具。
30
30
 
31
31
  本机改源码时再用 `file:`,并且必须保留前缀。直接写 `./dsh-oauth-login` 会变成符号链接;本插件有意从 profile 复用 DSH 运行时的 peer dependency,因此必须使用 `file:` 副本,才能正确解析依赖。
32
32
 
@@ -47,7 +47,7 @@ dsh plugin --profile web exec dsh-oauth-login logout openai-codex
47
47
  智谱 GLM Coding Plan 会打开官方套餐页,再在本机提示你输入 API Key;
48
48
  插件不会读取你的浏览器登录状态。
49
49
 
50
- 升级服务端插件后先重启一次 DSH,再进入「设置 → 订阅登录 → 网络代理」。HTTP 与 WebSocket 可以独立配置,地址默认填入 `http://127.0.0.1`,端口留空。开启并填写端口时优先使用该代理;默认地址且端口留空时沿用自动发现,不会默认使用 80 端口;关闭时直接连接。请使用 HTTP(S) CONNECT / 混合代理端口,不能填纯 SOCKS 端口。页面保存后用于新请求,不需要再次重启;改变启动时继承的环境变量则需要重启。详见[网络代理与恢复机制](docs/network-proxy.md)。
50
+ 升级服务端插件并按安装器提示完成生效后,进入「设置 → 订阅登录 → 网络代理」。HTTP 与 WebSocket 可以独立配置,地址默认填入 `http://127.0.0.1`,端口留空。开启并填写端口时优先使用该代理;默认地址且端口留空时沿用自动发现,不会默认使用 80 端口;关闭时直接连接。请使用 HTTP(S) CONNECT / 混合代理端口,不能填纯 SOCKS 端口。页面保存后用于新请求,不需要再次重启;改变启动时继承的环境变量则需要重启。详见[网络代理与恢复机制](docs/network-proxy.md)。
51
51
 
52
52
  卸载:
53
53
 
package/README.en.md CHANGED
@@ -9,7 +9,7 @@
9
9
  Open **Settings → Plugins → Add plugin** and enter this in “Package name or address”:
10
10
 
11
11
  ```text
12
- github:aa2246740/dsh-oauth-login#v0.2.7
12
+ dsh-oauth-login@0.2.9
13
13
  ```
14
14
 
15
15
  The desktop plugin manager owns the Desktop profile and bundled package manager. This release includes built `lib/`; normal use needs no clone, build, or DSHX installation. Follow the app if it asks you to reload or reopen after installation.
@@ -17,10 +17,10 @@ The desktop plugin manager owns the Desktop profile and bundled package manager.
17
17
  ### Web CLI
18
18
 
19
19
  ```sh
20
- dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
20
+ dsh plugin --profile web add dsh-oauth-login@0.2.9
21
21
  ```
22
22
 
23
- This official CLI command writes only the `web` profile; it cannot modify the Desktop App profile. For an already-running Web Host, reopen that Host once and reload the page.
23
+ This official CLI command writes only the `web` profile; it cannot modify the Desktop App profile. Follow the installer's activation result and reopen the original launcher only when a restart is required.
24
24
 
25
25
  OAuth login for ChatGPT, Claude, Grok, Copilot, OpenRouter, and Kimi on [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), plus Zhipu GLM Coding Plan via its official plan API key. Credentials go only to `$DSH_HOME/.dsh-oauth-auth.json`.
26
26
 
@@ -47,7 +47,7 @@ That `github:` command works because this package declares `dsh.bundle.patch` an
47
47
  If `dsh` is not on PATH:
48
48
 
49
49
  ```sh
50
- npx @deepseek-ai/dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
50
+ npx @deepseek-ai/dsh plugin --profile web add dsh-oauth-login@0.2.9
51
51
  ```
52
52
 
53
53
  Use `file:` only when you are editing a local clone (development/local testing; do not drop the prefix):
@@ -97,7 +97,7 @@ On these routes the plugin removes DSH `web_search` / `web_fetch` and attaches t
97
97
  | Route | Hosted tools |
98
98
  |---|---|
99
99
  | `pi-xai` | `web_search`, `x_search`, `image_generation` |
100
- | `pi-openai-codex` | `web_search`, `image_generation` |
100
+ | `pi-openai-codex` | `web_search` (cached by default) |
101
101
  | `pi-anthropic` | `web_search_20250305` |
102
102
 
103
103
  Copilot, OpenRouter, Kimi, and Zhipu do not get hosted tools. To keep DSH search:
@@ -111,6 +111,8 @@ Copilot, OpenRouter, Kimi, and Zhipu do not get hosted tools. To keep DSH search
111
111
 
112
112
  `nativeImage: false` keeps search and drops image generation.
113
113
 
114
+ Codex uses its own tool declaration. `codexSearch: cached` sends `external_web_access: false`; `live` explicitly enables external access; `off` leaves the DSH client web tools available. Cached results may be stale. Account or backend rejections remain visible, and the plugin never silently changes model or search mode. Codex does not inherit generic Responses image-generation tools. Grok and Anthropic retain their own protocols.
115
+
114
116
  Chat retries use official `dsh-llm-retry`. HTTP 429 is not the same as quota exhaustion. **Settings → 订阅登录 → 网络代理** affects only this plugin.
115
117
 
116
118
  ## License
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  打开 **设置 → 插件 → 添加插件**,在“包名或地址”中输入:
10
10
 
11
11
  ```text
12
- github:aa2246740/dsh-oauth-login#v0.2.7
12
+ dsh-oauth-login@0.2.9
13
13
  ```
14
14
 
15
15
  桌面端插件管理器负责 Desktop profile 和内置包管理器。本发布已包含编译好的 `lib/`;普通使用不需要 clone、构建或安装 DSHX。若应用提示刷新或重新打开,请按提示完成。
@@ -17,10 +17,10 @@ github:aa2246740/dsh-oauth-login#v0.2.7
17
17
  ### Web CLI
18
18
 
19
19
  ```sh
20
- dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
20
+ dsh plugin --profile web add dsh-oauth-login@0.2.9
21
21
  ```
22
22
 
23
- 这条官方 CLI 命令只写入 `web` profile,不能修改 Desktop App 的 profile。对于已经运行的 Web Host,请重新打开该 Host 一次,再刷新网页。
23
+ 这条官方 CLI 命令只写入 `web` profile,不能修改 Desktop App 的 profile。按安装器的生效结果继续;只有提示需要重启时才用原启动方式重新打开 Host。
24
24
 
25
25
  把 ChatGPT、Claude、Grok、Copilot、OpenRouter、Kimi 的 OAuth 登录和智谱 GLM Coding Plan 接到 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)。凭据只写 `$DSH_HOME/.dsh-oauth-auth.json`。
26
26
 
@@ -47,7 +47,7 @@ dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
47
47
  没有 `dsh` 时:
48
48
 
49
49
  ```sh
50
- npx @deepseek-ai/dsh plugin --profile web add github:aa2246740/dsh-oauth-login#v0.2.7
50
+ npx @deepseek-ai/dsh plugin --profile web add dsh-oauth-login@0.2.9
51
51
  ```
52
52
 
53
53
  本机改源码时再用 `file:`(开发/本地测试;不要写成裸的 `./`):
@@ -109,7 +109,9 @@ Copilot、OpenRouter、Kimi、智谱不挂 hosted tool。要继续用 DSH 自己
109
109
  nativeTools: false
110
110
  ```
111
111
 
112
- `nativeImage: false` 只留搜索,去掉出图。
112
+ `nativeImage: false` 关闭受支持路由的托管出图。
113
+
114
+ Codex 订阅路由使用独立的工具声明:`codexSearch: cached` 默认发送 `external_web_access: false`;`live` 显式请求实时外部访问,`off` 不注入托管搜索并保留 DSH 的客户端搜索工具。缓存搜索不保证结果实时性;账户和后端仍可能拒绝工具请求,插件不会自动换模型或在失败后静默重试另一种搜索模式。Codex 不再继承通用 Responses 的托管图片生成声明。Grok、Anthropic 保持各自协议。
113
115
 
114
116
  Chat 失败重试走官方 `dsh-llm-retry`。429 不等于额度耗尽。设置 → 订阅登录 → 网络代理只影响本插件。
115
117
 
package/lib/bin.js CHANGED
@@ -577,10 +577,12 @@ const HOSTED_CLIENT_LEAK_NAMES = [
577
577
  new Set(HOSTED_CLIENT_LEAK_NAMES);
578
578
  const DEFAULT_NATIVE_TOOL_POLICY = {
579
579
  enabled: true,
580
- image: true
580
+ image: true,
581
+ codexSearch: "cached"
581
582
  };
582
583
  const SEARCH_GUIDANCE = "This turn already includes this account's native hosted search. Do not call web_search or web_fetch — those DSH tools are not available on this route.";
583
584
  const SEARCH_AND_IMAGE_GUIDANCE = "This turn already includes this account's native hosted search and image generation. Do not call web_search or web_fetch — those DSH tools are not available on this route.";
585
+ const CACHED_SEARCH_GUIDANCE = "This turn includes native cached web search. Cached results may not reflect current pages. Do not call web_search or web_fetch as client functions — those DSH tools are not available on this route.";
584
586
  function responsesSearch() {
585
587
  return { type: "web_search" };
586
588
  }
@@ -609,12 +611,17 @@ function nativePlan(providerId, api, policy = DEFAULT_NATIVE_TOOL_POLICY) {
609
611
  ],
610
612
  guidance: policy.image ? SEARCH_AND_IMAGE_GUIDANCE : SEARCH_GUIDANCE
611
613
  };
612
- case "openai-codex:openai-codex-responses": return {
613
- providerId,
614
- api: "openai-codex-responses",
615
- hosted: [responsesSearch(), ...policy.image ? [responsesImage()] : []],
616
- guidance: policy.image ? SEARCH_AND_IMAGE_GUIDANCE : SEARCH_GUIDANCE
617
- };
614
+ case "openai-codex:openai-codex-responses":
615
+ if (policy.codexSearch === "off") return void 0;
616
+ return {
617
+ providerId,
618
+ api: "openai-codex-responses",
619
+ hosted: [{
620
+ type: "web_search",
621
+ external_web_access: policy.codexSearch === "live"
622
+ }],
623
+ guidance: policy.codexSearch === "live" ? SEARCH_GUIDANCE : CACHED_SEARCH_GUIDANCE
624
+ };
618
625
  case "anthropic:anthropic-messages": return {
619
626
  providerId,
620
627
  api: "anthropic-messages",
@@ -644,13 +651,15 @@ function hostedToolKey(tool) {
644
651
  function applyNativeToolsToPayload(payload, providerId, api, policy = DEFAULT_NATIVE_TOOL_POLICY) {
645
652
  const plan = nativePlan(providerId, api, policy);
646
653
  if (plan === void 0 || !isRecord(payload) || !Array.isArray(payload.tools)) return payload;
647
- const kept = payload.tools.filter((tool) => !isDshWebFunctionTool(tool));
648
- const seen = new Set(kept.filter(isRecord).map((tool) => hostedToolKey(tool)));
649
- const hosted = plan.hosted.filter((tool) => {
650
- const key = hostedToolKey(tool);
651
- if (seen.has(key)) return false;
652
- seen.add(key);
653
- return true;
654
+ const current = payload.tools;
655
+ const managed = new Set(plan.hosted.map(hostedToolKey));
656
+ const kept = current.filter((tool) => !isDshWebFunctionTool(tool) && (!isRecord(tool) || !managed.has(hostedToolKey(tool))) && !(providerId === "openai-codex" && isRecord(tool) && tool.type === "image_generation"));
657
+ const hosted = plan.hosted.map((tool) => {
658
+ const previous = current.find((candidate) => isRecord(candidate) && hostedToolKey(candidate) === hostedToolKey(tool));
659
+ return {
660
+ ...isRecord(previous) ? previous : {},
661
+ ...tool
662
+ };
654
663
  });
655
664
  return {
656
665
  ...payload,
@@ -664,8 +673,8 @@ function wrapOnPayload(existing, providerId, api, policy = DEFAULT_NATIVE_TOOL_P
664
673
  ...payload,
665
674
  tools: []
666
675
  } : payload, providerId, api, policy);
667
- if (existing === void 0) return injected;
668
- return await existing(injected, model);
676
+ const customized = existing === void 0 ? void 0 : await existing(injected, model);
677
+ return applyNativeToolsToPayload(customized === void 0 ? injected : customized, providerId, api, policy);
669
678
  };
670
679
  }
671
680
  function filterPiContext(context, providerId, api, policy = DEFAULT_NATIVE_TOOL_POLICY) {
@@ -1981,7 +1990,12 @@ z.object({
1981
1990
  streamIdleTimeoutMs: z.number().min(1).default(PI_LOGIN_STREAM_IDLE_TIMEOUT_MS),
1982
1991
  retryPolicy: RetryPolicySchema,
1983
1992
  nativeTools: z.boolean().default(true),
1984
- nativeImage: z.boolean().default(true)
1993
+ nativeImage: z.boolean().default(true),
1994
+ codexSearch: z.union([
1995
+ "cached",
1996
+ "live",
1997
+ "off"
1998
+ ]).default("cached")
1985
1999
  });
1986
2000
  //#endregion
1987
2001
  //#region src/bin.ts
package/lib/index.d.ts CHANGED
@@ -20,8 +20,10 @@ interface Config {
20
20
  * search, and attach those hosted tools to the provider request.
21
21
  */
22
22
  nativeTools?: boolean;
23
- /** Also attach hosted image generation on Grok and Codex. */
23
+ /** Also attach hosted image generation on transports that support it (Grok). */
24
24
  nativeImage?: boolean;
25
+ /** Codex defaults to cached search; live external access is explicit. */
26
+ codexSearch?: 'cached' | 'live' | 'off';
25
27
  }
26
28
  declare const Config: z<Config>;
27
29
  //#endregion
@@ -117,6 +119,8 @@ interface NativeToolPolicy {
117
119
  enabled: boolean;
118
120
  /** Also attach hosted image generation where the provider documents it. Default true. */
119
121
  image: boolean;
122
+ /** Codex search distinguishes cached content from live external access. */
123
+ codexSearch?: 'cached' | 'live' | 'off';
120
124
  }
121
125
  declare const DEFAULT_NATIVE_TOOL_POLICY: NativeToolPolicy;
122
126
  interface NativeToolPlan {
package/lib/index.js CHANGED
@@ -796,10 +796,12 @@ const HOSTED_CLIENT_LEAK_NAMES = [
796
796
  const HOSTED_CLIENT_LEAK_NAME_SET = new Set(HOSTED_CLIENT_LEAK_NAMES);
797
797
  const DEFAULT_NATIVE_TOOL_POLICY = {
798
798
  enabled: true,
799
- image: true
799
+ image: true,
800
+ codexSearch: "cached"
800
801
  };
801
802
  const SEARCH_GUIDANCE = "This turn already includes this account's native hosted search. Do not call web_search or web_fetch — those DSH tools are not available on this route.";
802
803
  const SEARCH_AND_IMAGE_GUIDANCE = "This turn already includes this account's native hosted search and image generation. Do not call web_search or web_fetch — those DSH tools are not available on this route.";
804
+ const CACHED_SEARCH_GUIDANCE = "This turn includes native cached web search. Cached results may not reflect current pages. Do not call web_search or web_fetch as client functions — those DSH tools are not available on this route.";
803
805
  function responsesSearch() {
804
806
  return { type: "web_search" };
805
807
  }
@@ -828,12 +830,17 @@ function nativePlan(providerId, api, policy = DEFAULT_NATIVE_TOOL_POLICY) {
828
830
  ],
829
831
  guidance: policy.image ? SEARCH_AND_IMAGE_GUIDANCE : SEARCH_GUIDANCE
830
832
  };
831
- case "openai-codex:openai-codex-responses": return {
832
- providerId,
833
- api: "openai-codex-responses",
834
- hosted: [responsesSearch(), ...policy.image ? [responsesImage()] : []],
835
- guidance: policy.image ? SEARCH_AND_IMAGE_GUIDANCE : SEARCH_GUIDANCE
836
- };
833
+ case "openai-codex:openai-codex-responses":
834
+ if (policy.codexSearch === "off") return void 0;
835
+ return {
836
+ providerId,
837
+ api: "openai-codex-responses",
838
+ hosted: [{
839
+ type: "web_search",
840
+ external_web_access: policy.codexSearch === "live"
841
+ }],
842
+ guidance: policy.codexSearch === "live" ? SEARCH_GUIDANCE : CACHED_SEARCH_GUIDANCE
843
+ };
837
844
  case "anthropic:anthropic-messages": return {
838
845
  providerId,
839
846
  api: "anthropic-messages",
@@ -1020,13 +1027,15 @@ function hostedToolKey(tool) {
1020
1027
  function applyNativeToolsToPayload(payload, providerId, api, policy = DEFAULT_NATIVE_TOOL_POLICY) {
1021
1028
  const plan = nativePlan(providerId, api, policy);
1022
1029
  if (plan === void 0 || !isRecord(payload) || !Array.isArray(payload.tools)) return payload;
1023
- const kept = payload.tools.filter((tool) => !isDshWebFunctionTool(tool));
1024
- const seen = new Set(kept.filter(isRecord).map((tool) => hostedToolKey(tool)));
1025
- const hosted = plan.hosted.filter((tool) => {
1026
- const key = hostedToolKey(tool);
1027
- if (seen.has(key)) return false;
1028
- seen.add(key);
1029
- return true;
1030
+ const current = payload.tools;
1031
+ const managed = new Set(plan.hosted.map(hostedToolKey));
1032
+ const kept = current.filter((tool) => !isDshWebFunctionTool(tool) && (!isRecord(tool) || !managed.has(hostedToolKey(tool))) && !(providerId === "openai-codex" && isRecord(tool) && tool.type === "image_generation"));
1033
+ const hosted = plan.hosted.map((tool) => {
1034
+ const previous = current.find((candidate) => isRecord(candidate) && hostedToolKey(candidate) === hostedToolKey(tool));
1035
+ return {
1036
+ ...isRecord(previous) ? previous : {},
1037
+ ...tool
1038
+ };
1030
1039
  });
1031
1040
  return {
1032
1041
  ...payload,
@@ -1040,8 +1049,8 @@ function wrapOnPayload(existing, providerId, api, policy = DEFAULT_NATIVE_TOOL_P
1040
1049
  ...payload,
1041
1050
  tools: []
1042
1051
  } : payload, providerId, api, policy);
1043
- if (existing === void 0) return injected;
1044
- return await existing(injected, model);
1052
+ const customized = existing === void 0 ? void 0 : await existing(injected, model);
1053
+ return applyNativeToolsToPayload(customized === void 0 ? injected : customized, providerId, api, policy);
1045
1054
  };
1046
1055
  }
1047
1056
  function filterPiContext(context, providerId, api, policy = DEFAULT_NATIVE_TOOL_POLICY) {
@@ -3050,7 +3059,12 @@ const Config = z.object({
3050
3059
  streamIdleTimeoutMs: z.number().min(1).default(PI_LOGIN_STREAM_IDLE_TIMEOUT_MS),
3051
3060
  retryPolicy: RetryPolicySchema,
3052
3061
  nativeTools: z.boolean().default(true),
3053
- nativeImage: z.boolean().default(true)
3062
+ nativeImage: z.boolean().default(true),
3063
+ codexSearch: z.union([
3064
+ "cached",
3065
+ "live",
3066
+ "off"
3067
+ ]).default("cached")
3054
3068
  });
3055
3069
  //#endregion
3056
3070
  //#region src/index.ts
@@ -3067,6 +3081,7 @@ function apply(ctx, config) {
3067
3081
  console.log("[my-plugins/dsh-oauth-login] loaded");
3068
3082
  const native = {
3069
3083
  enabled: config.nativeTools !== false,
3084
+ codexSearch: config.codexSearch ?? "cached",
3070
3085
  image: config.nativeImage !== false
3071
3086
  };
3072
3087
  const session = new PiLoginSession(new PiLoginCredentialStore(), native);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-oauth-login",
3
3
  "description": "DSH-owned subscription login powered by Pi AI adapters: OAuth plus official Plan API keys, with an independent credential store.",
4
- "version": "0.2.8",
4
+ "version": "0.2.9",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "registry": "https://registry.npmjs.org/"
package/src/index.ts CHANGED
@@ -131,6 +131,7 @@ export function apply(ctx: Context, config: Config): void {
131
131
  console.log('[my-plugins/dsh-oauth-login] loaded')
132
132
  const native = {
133
133
  enabled: config.nativeTools !== false,
134
+ codexSearch: config.codexSearch ?? 'cached',
134
135
  image: config.nativeImage !== false,
135
136
  }
136
137
  const session = new PiLoginSession(new PiLoginCredentialStore(), native)
@@ -48,11 +48,14 @@ export interface NativeToolPolicy {
48
48
  enabled: boolean
49
49
  /** Also attach hosted image generation where the provider documents it. Default true. */
50
50
  image: boolean
51
+ /** Codex search distinguishes cached content from live external access. */
52
+ codexSearch?: 'cached' | 'live' | 'off'
51
53
  }
52
54
 
53
55
  export const DEFAULT_NATIVE_TOOL_POLICY: NativeToolPolicy = {
54
56
  enabled: true,
55
57
  image: true,
58
+ codexSearch: 'cached',
56
59
  }
57
60
 
58
61
  export interface NativeToolPlan {
@@ -68,6 +71,9 @@ const SEARCH_GUIDANCE =
68
71
  const SEARCH_AND_IMAGE_GUIDANCE =
69
72
  'This turn already includes this account\'s native hosted search and image generation. Do not call web_search or web_fetch — those DSH tools are not available on this route.'
70
73
 
74
+ const CACHED_SEARCH_GUIDANCE =
75
+ 'This turn includes native cached web search. Cached results may not reflect current pages. Do not call web_search or web_fetch as client functions — those DSH tools are not available on this route.'
76
+
71
77
  function responsesSearch(): Record<string, unknown> {
72
78
  return { type: 'web_search' }
73
79
  }
@@ -103,14 +109,15 @@ export function nativePlan(
103
109
  guidance: policy.image ? SEARCH_AND_IMAGE_GUIDANCE : SEARCH_GUIDANCE,
104
110
  }
105
111
  case 'openai-codex:openai-codex-responses':
112
+ if (policy.codexSearch === 'off') return undefined
106
113
  return {
107
114
  providerId,
108
115
  api: 'openai-codex-responses',
109
- hosted: [
110
- responsesSearch(),
111
- ...policy.image ? [responsesImage()] : [],
112
- ],
113
- guidance: policy.image ? SEARCH_AND_IMAGE_GUIDANCE : SEARCH_GUIDANCE,
116
+ // Codex is a distinct transport. Its official tool specification makes
117
+ // external access explicit; the generic Responses default is not a
118
+ // substitute. Hosted image generation is not part of this contract.
119
+ hosted: [{ type: 'web_search', external_web_access: policy.codexSearch === 'live' }],
120
+ guidance: policy.codexSearch === 'live' ? SEARCH_GUIDANCE : CACHED_SEARCH_GUIDANCE,
114
121
  }
115
122
  case 'anthropic:anthropic-messages':
116
123
  return {
@@ -352,15 +359,14 @@ export function applyNativeToolsToPayload(
352
359
  const plan = nativePlan(providerId, api, policy)
353
360
  if (plan === undefined || !isRecord(payload) || !Array.isArray(payload.tools)) return payload
354
361
  const current = payload.tools
355
- const kept = current.filter(tool => !isDshWebFunctionTool(tool))
356
- const seen = new Set(
357
- kept.filter(isRecord).map(tool => hostedToolKey(tool)),
358
- )
359
- const hosted = plan.hosted.filter(tool => {
360
- const key = hostedToolKey(tool)
361
- if (seen.has(key)) return false
362
- seen.add(key)
363
- return true
362
+ const managed = new Set(plan.hosted.map(hostedToolKey))
363
+ const kept = current.filter(tool => !isDshWebFunctionTool(tool)
364
+ && (!isRecord(tool) || !managed.has(hostedToolKey(tool)))
365
+ && !(providerId === 'openai-codex' && isRecord(tool) && tool.type === 'image_generation'))
366
+ const hosted = plan.hosted.map(tool => {
367
+ const previous = current.find(candidate => isRecord(candidate) && hostedToolKey(candidate) === hostedToolKey(tool))
368
+ // Preserve caller filters, but explicit transport capability fields win.
369
+ return { ...(isRecord(previous) ? previous : {}), ...tool }
364
370
  })
365
371
  return { ...payload, tools: [...hosted, ...kept] }
366
372
  }
@@ -381,8 +387,10 @@ export function wrapOnPayload(
381
387
  ? { ...payload, tools: [] }
382
388
  : payload
383
389
  const injected = applyNativeToolsToPayload(requestPayload, providerId, api, policy)
384
- if (existing === undefined) return injected
385
- return await existing(injected, model)
390
+ // Preserve the existing callback's observation contract, then revalidate
391
+ // the actual returned payload so it cannot restore an unsupported tool.
392
+ const customized = existing === undefined ? undefined : await existing(injected, model)
393
+ return applyNativeToolsToPayload(customized === undefined ? injected : customized, providerId, api, policy)
386
394
  }
387
395
  }
388
396
 
@@ -18,8 +18,10 @@ export interface Config {
18
18
  * search, and attach those hosted tools to the provider request.
19
19
  */
20
20
  nativeTools?: boolean
21
- /** Also attach hosted image generation on Grok and Codex. */
21
+ /** Also attach hosted image generation on transports that support it (Grok). */
22
22
  nativeImage?: boolean
23
+ /** Codex defaults to cached search; live external access is explicit. */
24
+ codexSearch?: 'cached' | 'live' | 'off'
23
25
  }
24
26
 
25
27
  export const Config: z<Config> = z.object({
@@ -27,4 +29,5 @@ export const Config: z<Config> = z.object({
27
29
  retryPolicy: RetryPolicySchema,
28
30
  nativeTools: z.boolean().default(true),
29
31
  nativeImage: z.boolean().default(true),
32
+ codexSearch: z.union(['cached', 'live', 'off']).default('cached'),
30
33
  })