dsh-openai-subscription 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -22,12 +22,6 @@ Use a ChatGPT subscription with Codex access in [DeepSeek Harness](https://githu
22
22
  - A [ChatGPT plan with Codex access](https://help.openai.com/en/articles/11369540-use-codex-with-a-chatgpt-plan) and device-code authorization enabled
23
23
  - Network access to `auth.openai.com` and `chatgpt.com`
24
24
 
25
- Install or update DSH when needed:
26
-
27
- ```sh
28
- npm install -g pnpm @deepseek-ai/dsh@next
29
- ```
30
-
31
25
  ## Installation
32
26
 
33
27
  Install the plugin into the Web profile:
@@ -80,7 +74,8 @@ Restart the Web profile and refresh the page afterward.
80
74
  ## Troubleshooting
81
75
 
82
76
  - **The settings entry is missing:** confirm the plugin is installed in the `web` profile with `dsh plugin --profile web why dsh-openai-subscription`, then restart DSH and refresh the page.
83
- - **Authorization cannot start:** update DSH, confirm `node --version` meets the requirement, and check access to the OpenAI authentication service.
77
+ - **Authorization cannot start:** confirm `node --version` meets the requirement and check access to the OpenAI authentication service.
78
+ - **Sign-in component unavailable:** the sign-in module is taken first from the `llm-pi-ai` adapter dependency bundled with the running DSH (no separate `pi` CLI install is needed); npm/nvm/bun-managed global `pi` installs are only a fallback. If the message persists, restart DSH and try again.
84
79
  - **The device code is rejected:** enable device-code authorization in ChatGPT security settings and start a new connection.
85
80
  - **Models need syncing:** select **Sync models**. If an explicit list already exists, review the confirmation and continue; local edits are preserved.
86
81
  - **Model sync fails:** existing settings remain unchanged. Check access to `chatgpt.com` and authorization status, then retry; refresh authorization first if needed.
@@ -88,7 +83,7 @@ Restart the Web profile and refresh the page afterward.
88
83
 
89
84
  ## Compatibility notes
90
85
 
91
- Dynamic discovery uses the backend endpoint used by the official ChatGPT Codex client, but that endpoint is not a public, stability-guaranteed OpenAI API and may change. It does not provide complete output-limit, cost, or wire-compatibility metadata. `llm-pi-ai` fills metadata for known models; unknown models use adapter defaults. Catalog presence also does not guarantee that an older adapter supports every new model capability, so update DSH first when calls reveal compatibility problems.
86
+ Dynamic discovery uses the backend endpoint used by the official ChatGPT Codex client, but that endpoint is not a public, stability-guaranteed OpenAI API and may change. It does not provide complete output-limit, cost, or wire-compatibility metadata. `llm-pi-ai` fills metadata for known models; unknown models use adapter defaults. Catalog presence also does not guarantee that the installed adapter supports every new model capability.
92
87
 
93
88
  ## Security and privacy
94
89
 
package/README.zh.md CHANGED
@@ -22,12 +22,6 @@
22
22
  - [包含 Codex 权限的 ChatGPT 套餐](https://help.openai.com/en/articles/11369540-use-codex-with-a-chatgpt-plan),并已启用设备码授权
23
23
  - 能够访问 `auth.openai.com` 和 `chatgpt.com`
24
24
 
25
- 如需安装或更新 DSH:
26
-
27
- ```sh
28
- npm install -g pnpm @deepseek-ai/dsh@next
29
- ```
30
-
31
25
  ## 安装
32
26
 
33
27
  将插件安装到 Web profile:
@@ -80,7 +74,8 @@ dsh plugin --profile web remove dsh-openai-subscription
80
74
  ## 故障排查
81
75
 
82
76
  - **设置中没有入口:** 使用 `dsh plugin --profile web why dsh-openai-subscription` 确认插件已安装到 `web` profile,然后重启 DSH 并刷新页面。
83
- - **无法开始授权:** 更新 DSH,确认 `node --version` 满足要求,并检查 OpenAI 登录服务是否可访问。
77
+ - **无法开始授权:** 确认 `node --version` 满足要求,并检查 OpenAI 登录服务是否可访问。
78
+ - **提示登录组件不可用:** 登录组件优先取自当前 DSH 自带的 `llm-pi-ai` 适配器依赖(无需单独安装 `pi` CLI),找不到时才会回退到 npm/nvm/bun 全局安装的 `pi` 所携带的副本。若仍提示不可用,请重启 DSH 后重试。
84
79
  - **设备码被拒绝:** 在 ChatGPT 安全设置中启用设备码授权,然后重新连接。
85
80
  - **模型需要同步:** 点击 **同步模型**。若已有显式模型列表,请阅读确认提示后继续;插件会保留本地编辑。
86
81
  - **模型同步失败:** 现有设置会保持不变。检查 `chatgpt.com` 的网络访问和授权状态,稍后重试;必要时先刷新授权。
@@ -88,7 +83,7 @@ dsh plugin --profile web remove dsh-openai-subscription
88
83
 
89
84
  ## 兼容性说明
90
85
 
91
- 动态目录使用 ChatGPT 官方 Codex 客户端所使用的后端接口,但它不是公开、稳定承诺的 OpenAI API,未来可能变化。接口不提供完整的输出上限、费用或 wire compatibility 元数据;已知模型由 `llm-pi-ai` 补全,未知模型使用适配器默认值。目录中出现模型也不保证旧版适配器支持其所有新能力,遇到调用兼容问题时请先更新 DSH。
86
+ 动态目录使用 ChatGPT 官方 Codex 客户端所使用的后端接口,但它不是公开、稳定承诺的 OpenAI API,未来可能变化。接口不提供完整的输出上限、费用或 wire compatibility 元数据;已知模型由 `llm-pi-ai` 补全,未知模型使用适配器默认值。目录中出现模型也不保证当前安装的适配器支持其所有新能力。
92
87
 
93
88
  ## 安全与隐私
94
89
 
package/dist/host.d.ts CHANGED
@@ -54,6 +54,17 @@ type PollResult = {
54
54
  outcome: FlowState['outcome'];
55
55
  error: string | null;
56
56
  };
57
+ /**
58
+ * Filesystem candidates for the OpenAI auth module, derived from the running
59
+ * DSH process. The first candidate that exists is used, so the module always
60
+ * matches the pi-ai version bundled with the installed DSH regardless of how
61
+ * `pi` (or DSH itself) was installed; the shell locator below only runs when
62
+ * none of these probes hit.
63
+ */
64
+ export declare function authModuleCandidates(entryScript: string | undefined, nodeBinary: string | undefined, env: {
65
+ NODE_PATH?: string | undefined;
66
+ HOME?: string | undefined;
67
+ } | undefined): string[];
57
68
  declare class OpenAISubscriptionController extends TypertRemoteService {
58
69
  private registeredAuthorization;
59
70
  private cachedModule;
@@ -69,6 +80,11 @@ declare class OpenAISubscriptionController extends TypertRemoteService {
69
80
  private authorization;
70
81
  private llm;
71
82
  private ensureAuthorizationFlow;
83
+ /**
84
+ * Locate the OpenAI auth module without spawning a shell. Probes the pi-ai
85
+ * copy shipped inside the running DSH first; returns '' when nothing hits.
86
+ */
87
+ private probeInProcessModule;
72
88
  private locateAuthModule;
73
89
  private runDevice;
74
90
  private runRefresh;
package/dist/host.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // Host service for ChatGPT subscription authorization in DSH.
2
2
  // OAuth is delegated to the OpenAI Codex integration bundled with DSH.
3
+ import { existsSync } from 'node:fs';
4
+ import { delimiter as pathDelimiter, dirname, join, resolve } from 'node:path';
3
5
  import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
4
6
  import { normalizeOAuthCredential } from './oauth.js';
5
7
  import { discoverOpenAIModels, mergeModelCatalog, removeManagedModels, } from './models.js';
@@ -27,6 +29,12 @@ const LOCATE_SCRIPT = [
27
29
  'fi',
28
30
  'printf "%s" "$CAND"',
29
31
  ].join('\n');
32
+ /**
33
+ * The OpenAI Codex OAuth implementation, relative to a `node_modules` root.
34
+ * DSH's `llm-pi-ai` adapter already depends on this package, so the copy that
35
+ * matches the running DSH is the preferred source for device authorization.
36
+ */
37
+ const AUTH_MODULE_PACKAGE_RELATIVE = '@earendil-works/pi-ai/dist/auth/oauth/openai-codex.js';
30
38
  const DRIVER_DEVICE = [
31
39
  "import { pathToFileURL } from 'node:url'",
32
40
  "const { openaiCodexOAuth } = await import(pathToFileURL(process.argv[1]).href)",
@@ -114,6 +122,68 @@ function unionModelCatalog(remote, builtin) {
114
122
  function settingsConflict(error) {
115
123
  return recordOf(error)?.code === 'SETTINGS_CONFLICT';
116
124
  }
125
+ /**
126
+ * Filesystem candidates for the OpenAI auth module, derived from the running
127
+ * DSH process. The first candidate that exists is used, so the module always
128
+ * matches the pi-ai version bundled with the installed DSH regardless of how
129
+ * `pi` (or DSH itself) was installed; the shell locator below only runs when
130
+ * none of these probes hit.
131
+ */
132
+ export function authModuleCandidates(entryScript, nodeBinary, env) {
133
+ const candidates = [];
134
+ const seen = new Set();
135
+ const push = (value) => {
136
+ if (!value)
137
+ return;
138
+ const normalized = resolve(value);
139
+ if (seen.has(normalized))
140
+ return;
141
+ seen.add(normalized);
142
+ candidates.push(normalized);
143
+ };
144
+ // Walk up from the running DSH entry script. This finds the pi-ai copy
145
+ // bundled inside DSH's own installation for npm-, nvm-, and yarn-style
146
+ // layouts (global or workspace), even when `pi` is a standalone binary
147
+ // or missing entirely.
148
+ if (entryScript) {
149
+ let dir = dirname(entryScript);
150
+ for (let depth = 0; depth < 40 && dir; depth++) {
151
+ push(join(dir, 'node_modules', AUTH_MODULE_PACKAGE_RELATIVE));
152
+ const parent = dirname(dir);
153
+ if (parent === dir)
154
+ break;
155
+ dir = parent;
156
+ }
157
+ }
158
+ // Node's global module roots, mirroring how `module.globalPaths` is built:
159
+ // NODE_PATH entries plus the lib/node_modules directory beside the Node
160
+ // binary (also covers nvm-managed Node on macOS and Linux).
161
+ const globalRoots = [];
162
+ if (nodeBinary) {
163
+ globalRoots.push(resolve(dirname(nodeBinary), '../lib/node_modules'));
164
+ globalRoots.push(join(dirname(nodeBinary), 'node_modules'));
165
+ }
166
+ const nodePath = env?.NODE_PATH;
167
+ if (nodePath) {
168
+ for (const entry of nodePath.split(pathDelimiter)) {
169
+ if (entry)
170
+ globalRoots.push(entry);
171
+ }
172
+ }
173
+ for (const root of globalRoots) {
174
+ // A directly installed pi-ai, or the hoisted copy under a globally
175
+ // installed DSH (npm hoists transitive dependencies into DSH's own
176
+ // node_modules; the nested path covers version-conflicted installs).
177
+ push(join(root, 'node_modules', AUTH_MODULE_PACKAGE_RELATIVE));
178
+ push(join(root, AUTH_MODULE_PACKAGE_RELATIVE));
179
+ push(join(root, '@deepseek-ai/dsh/node_modules', AUTH_MODULE_PACKAGE_RELATIVE));
180
+ push(join(root, '@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-llm-pi-ai/node_modules', AUTH_MODULE_PACKAGE_RELATIVE));
181
+ }
182
+ // bun's global install root, matching the shell locator fallback.
183
+ if (env?.HOME)
184
+ push(join(env.HOME, '.bun/install/global/node_modules', AUTH_MODULE_PACKAGE_RELATIVE));
185
+ return candidates;
186
+ }
117
187
  //#endregion
118
188
  /** Register source-mode remote methods without decorator syntax. */
119
189
  const REMOTE_METHODS = ['status', 'authorize', 'poll', 'cancel', 'syncModels', 'logout'];
@@ -172,12 +242,34 @@ class OpenAISubscriptionController extends TypertRemoteService {
172
242
  return;
173
243
  this.registerAuthorizationFlow(authorization);
174
244
  }
245
+ /**
246
+ * Locate the OpenAI auth module without spawning a shell. Probes the pi-ai
247
+ * copy shipped inside the running DSH first; returns '' when nothing hits.
248
+ */
249
+ probeInProcessModule() {
250
+ if (typeof process === 'undefined' || !Array.isArray(process.argv))
251
+ return '';
252
+ const candidates = authModuleCandidates(process.argv[1], process.execPath, process.env);
253
+ for (const candidate of candidates) {
254
+ try {
255
+ if (existsSync(candidate))
256
+ return candidate;
257
+ }
258
+ catch {
259
+ // Unreadable or unavailable path: keep probing the remaining roots.
260
+ }
261
+ }
262
+ return '';
263
+ }
175
264
  async locateAuthModule() {
176
265
  if (this.cachedModule !== null)
177
266
  return this.cachedModule;
178
267
  if (this.locatingModule !== null)
179
268
  return this.locatingModule;
180
269
  const pending = (async () => {
270
+ const probed = this.probeInProcessModule();
271
+ if (probed)
272
+ return probed;
181
273
  const shell = this.shell();
182
274
  if (shell === undefined)
183
275
  return '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-openai-subscription",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "ChatGPT subscription sign-in for DeepSeek Harness via OpenAI device authorization.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.6.3",