dsh-codex-share 0.5.1 → 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/README.md CHANGED
@@ -4,7 +4,7 @@ A DeepSeek Harness LLM adapter that uses the same ChatGPT subscription login as
4
4
 
5
5
  ## Requirements
6
6
 
7
- - DeepSeek Harness `0.2.1-alpha.2` (earlier releases are no longer supported), with Node.js `^22.19.0 || >=24`.
7
+ - DeepSeek Harness `0.2.1-alpha.2` or `0.2.1-rc.2` and later 0.2 releases (earlier releases are no longer supported), with Node.js `^22.19.0 || >=24`. The Harness packages and pi-ai are peer dependencies: the plugin uses the Harness's own copies rather than installing its own.
8
8
  - Codex CLI signed in with a ChatGPT account and configured for **file credential storage**. Run `codex login` and check `codex login status`. Codex must be able to read and update `$CODEX_HOME/auth.json` (default `~/.codex/auth.json`).
9
9
  - The harness process must run as the same OS user, or have read access to that same credential file. A keyring-only Codex login is not supported.
10
10
 
@@ -16,7 +16,7 @@ Install the bundle into a harness profile:
16
16
  dsh plugin --profile web add dsh-codex-share
17
17
  ```
18
18
 
19
- On a fresh profile, pnpm may stop with `ERR_PNPM_IGNORED_BUILDS` for the pi-ai dependency tree. In `$DSH_HOME/profiles/web/pnpm-workspace.yaml` (default `~/.dsh/profiles/web/pnpm-workspace.yaml`), set the generated `allowBuilds` entries for `@google/genai` and `protobufjs` to `false`, as the harness source workspace does. Then run `dsh plugin --profile web remove dsh-codex-share` followed by the add command again; a failed add can leave the package installed without activating its bundle layer.
19
+ Versions up to 0.5.1 installed their own pinned copies of the Harness packages and pi-ai into the profile, which shadow the running Harness. After updating from those versions, `$DSH_HOME/profiles/web/node_modules/@deepseek-ai/` (default `~/.dsh/profiles/web/node_modules/@deepseek-ai/`) should no longer contain `dsh-llm` or `dsh-llm-pi-ai`; if it does, remove and re-add the plugin.
20
20
 
21
21
  For development from this checkout, from its directory:
22
22
 
@@ -24,7 +24,7 @@ For development from this checkout, from its directory:
24
24
  dsh plugin --profile web add .
25
25
  ```
26
26
 
27
- Restart the profile after installation or update. The bundle mounts one provider route, `openai-codex-local`, with the model catalog supplied by pi-ai 1.0, including GPT-6 Astra, Sol, and Luna, and GPT-6.1 Sol (`gpt-6.1-sol`). GPT-6.1 Luna is not listed in the current OpenAI or Codex catalogs; use `gpt-6-luna` for Luna. This route replaces the plugin's earlier `openai-codex` route, which collides with pi-ai's configurable provider directory; update any saved model selection to `openai-codex-local`. The plugin appears in **Settings → Plugins → Plugin list**. Open **Settings → Models** and use **Edit model IDs** in the OpenAI Codex row to add subscription model IDs (one per line). The IDs are saved as `additionalModelIds` on the `llm-codex-share` entry in the profile's `cordis.patch.yml` and take effect without a restart. The installed catalog remains available; clearing the additional IDs restores it. For example, enter a model ID that your account can use but the catalog does not list yet. An unrecognized ID can be listed, but requests to it will fail if the Codex endpoint rejects it. Select a listed model in the model picker or agent preset. For a local Codex home outside the default location, set the absolute `CODEX_HOME` path in the environment that launches the harness. Direct Cordis composition can configure `codexHome` on the `dsh-codex-share` plugin instead.
27
+ Restart the profile after installation or update. The bundle discovers home directories whose names start with `.codex` and contain an `auth.json` file (including symlinked directories). Each gets a separate provider group labeled with its directory name, such as **OpenAI Codex (.codex)** and **OpenAI Codex (.codex-p)**, in the model picker. The primary account retains `openai-codex-local`; other accounts use stable routes such as `openai-codex-local-.codex-p`. Each route offers the same catalog and additional model IDs, and reads only that account’s current credentials for each request. Invalid logins never fall back to another account. Discovery runs when the plugin starts; restart the profile after adding or removing account directories. Directory labels distinguish homes without exposing account email addresses. The primary route comes with the model catalog supplied by the Harness's pi-ai 1.x, including GPT-6 Astra, Sol, and Luna, and GPT-6.1 Sol (`gpt-6.1-sol`). GPT-6.1 Luna is not listed in the current OpenAI or Codex catalogs; use `gpt-6-luna` for Luna. This route replaces the plugin's earlier `openai-codex` route, which collides with pi-ai's configurable provider directory; update any saved model selection to `openai-codex-local`. The plugin appears in **Settings → Plugins → Plugin list**. Open **Settings → Models** and use **Edit model IDs** in the OpenAI Codex row to add subscription model IDs (one per line). The IDs are saved as `additionalModelIds` on the `llm-codex-share` entry in the profile's `cordis.patch.yml` and take effect without a restart. The installed catalog remains available; clearing the additional IDs restores it. For example, enter a model ID that your account can use but the catalog does not list yet. An unrecognized ID can be listed, but requests to it will fail if the Codex endpoint rejects it. Select a listed model in the model picker or agent preset. To select the primary Codex home (while still discovering other home directories), set the absolute `CODEX_HOME` path in the environment that launches the harness. Direct Cordis composition can configure `codexHome` on the `dsh-codex-share` plugin instead.
28
28
 
29
29
  The plugin intentionally does not refresh a stale token. When Codex writes a replacement, the next harness request reads it. When Codex's current access token has expired, harness calls fail until Codex refreshes its own login. An API-key-only Codex login is rejected; this route never falls back to `OPENAI_API_KEY`.
30
30
 
package/dist/index.d.mts CHANGED
@@ -19,7 +19,7 @@ declare const name = "llm-codex-share";
19
19
  declare const inject: string[];
20
20
  /** Settings namespace: the Loader entry id this bundle's patch inserts. */
21
21
  declare const SETTINGS_NS = "llm-codex-share";
22
- /** Plugin configuration. Omit codexHome to follow CODEX_HOME or ~/.codex. */
22
+ /** codexHome/CODEX_HOME selects the primary route; other ~/.codex* homes are discovered. */
23
23
  interface Config {
24
24
  codexHome?: string;
25
25
  /** Extra subscription model IDs alongside the installed pi-ai catalog; edits apply without a restart. */
package/dist/index.mjs CHANGED
@@ -5,9 +5,10 @@ import { envApiKeyAuth } from "@earendil-works/pi-ai";
5
5
  import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
6
6
  import { zaiProvider } from "@earendil-works/pi-ai/providers/zai";
7
7
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
8
+ import { readdirSync, statSync } from "node:fs";
8
9
  import { readFile } from "node:fs/promises";
9
10
  import { homedir } from "node:os";
10
- import { isAbsolute, join } from "node:path";
11
+ import { basename, dirname, isAbsolute, join } from "node:path";
11
12
  //#region src/auth.ts
12
13
  /** Read the exact ChatGPT access token currently stored by Codex CLI. */
13
14
  /** Resolve Codex's current auth.json path without retaining a token. */
@@ -38,6 +39,24 @@ async function readCodexAccessToken(codexHome) {
38
39
  if (auth === null || auth.auth_mode !== void 0 && auth.auth_mode !== "chatgpt" || typeof auth.OPENAI_API_KEY === "string" && auth.OPENAI_API_KEY.length > 0 || typeof auth.tokens?.access_token !== "string" || auth.tokens.access_token.length === 0) throw new LlmError(`Codex login at ${path} has no ChatGPT access token; run codex login with ChatGPT`, "MISSING_CREDENTIAL");
39
40
  return auth.tokens.access_token;
40
41
  }
42
+ /** Discover account homes at plugin startup; retain only paths, never credentials. */
43
+ function discoverCodexHomes(codexHome) {
44
+ const primary = dirname(codexAuthPath(codexHome));
45
+ const homes = /* @__PURE__ */ new Set([primary]);
46
+ try {
47
+ for (const entry of readdirSync(homedir()).sort()) {
48
+ if (!entry.startsWith(".codex")) continue;
49
+ const home = join(homedir(), entry);
50
+ try {
51
+ if (statSync(home).isDirectory() && statSync(join(home, "auth.json")).isFile()) homes.add(home);
52
+ } catch {}
53
+ }
54
+ } catch {}
55
+ return [...homes].map((home) => ({
56
+ home,
57
+ label: basename(home)
58
+ }));
59
+ }
41
60
  //#endregion
42
61
  //#region src/index.ts
43
62
  /** Harness route for subscription-backed Codex models. */
@@ -84,6 +103,12 @@ function apply(ctx, config = Config({})) {
84
103
  if (this === ctx.fiber) validateModelIds(Config(raw).additionalModelIds.get());
85
104
  return raw;
86
105
  });
106
+ const accounts = discoverCodexHomes(config.codexHome).map((account, index) => ({
107
+ ...account,
108
+ provider: index === 0 ? PROVIDER : `${PROVIDER}-${encodeURIComponent(account.label)}`,
109
+ displayName: `OpenAI Codex (${account.label})`
110
+ }));
111
+ const accountHomes = new Map(accounts.map((account) => [account.provider, account.home]));
87
112
  let lastIds;
88
113
  let profiles;
89
114
  const upstream = openaiCodexProvider();
@@ -158,17 +183,18 @@ function apply(ctx, config = Config({})) {
158
183
  name: id
159
184
  };
160
185
  })];
161
- const provider = routeProvider(upstream, PROVIDER, upstream.name, "Codex CLI login", models);
162
- profiles = new Map([[PROVIDER, {
186
+ profiles = new Map([...accounts.map((account) => [account.provider, {
163
187
  ...profileBase,
164
- piProvider: provider
165
- }], ...glmProfile === void 0 ? [] : [[GLM_PROVIDER, glmProfile]]]);
188
+ provider: account.provider,
189
+ displayName: account.displayName,
190
+ piProvider: routeProvider(upstream, account.provider, account.displayName, "Codex CLI login", models)
191
+ }]), ...glmProfile === void 0 ? [] : [[GLM_PROVIDER, glmProfile]]]);
166
192
  lastIds = configured;
167
193
  return profiles;
168
194
  };
169
195
  const adapter = new PiAiAdapter({
170
196
  profiles: resolveProfiles,
171
- resolveApiKey: async (provider) => provider === "glm-coding-global" ? glmKey : readCodexAccessToken(config.codexHome),
197
+ resolveApiKey: async (provider) => provider === "glm-coding-global" ? glmKey : accountHomes.has(provider) ? readCodexAccessToken(accountHomes.get(provider)) : Promise.reject(new LlmError("Unknown Codex account route", "NO_ADAPTER")),
172
198
  resolveAttachments: () => ctx.get("attachments"),
173
199
  resolveImageAccess: (attachments, ref) => resolveImageAttachmentAccess(attachments, (hostPath) => ctx.get("fs")?.processPathFromHostPath(hostPath), ref),
174
200
  auth: {
@@ -188,13 +214,13 @@ function apply(ctx, config = Config({})) {
188
214
  }
189
215
  }
190
216
  });
191
- ctx.llm.registerConfigurableProviders([{
192
- provider: PROVIDER,
193
- displayName: "OpenAI Codex (local login)",
217
+ ctx.llm.registerConfigurableProviders([...accounts.map(({ provider, displayName }) => ({
218
+ provider,
219
+ displayName,
194
220
  settingsNs: SETTINGS_NS,
195
221
  settingsPath: []
196
- }]);
197
- ctx.llm.registerAdapter(glmProfile === void 0 ? [PROVIDER] : [PROVIDER, GLM_PROVIDER], adapter);
222
+ }))]);
223
+ ctx.llm.registerAdapter([...accountHomes.keys(), ...glmProfile === void 0 ? [] : [GLM_PROVIDER]], adapter);
198
224
  }
199
225
  //#endregion
200
226
  export { Config, GLM_PROVIDER, PROVIDER, SETTINGS_NS, apply, codexAuthPath, inject, name, readCodexAccessToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-share",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Use the local Codex CLI ChatGPT subscription login as a DeepSeek Harness LLM provider",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,23 +27,17 @@
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@deepseek-ai/cordis": "~4.0.5-alpha.1",
30
- "@deepseek-ai/dsh-attachment": "0.2.1-alpha.2",
31
- "@deepseek-ai/dsh-fs": "0.2.1-alpha.2",
32
- "@deepseek-ai/dsh-llm": "0.2.1-alpha.2",
33
- "@deepseek-ai/dsh-llm-pi-ai": "0.2.1-alpha.2",
34
- "@deepseek-ai/schemastery": "~3.18.5-alpha.1"
35
- },
36
- "dependencies": {
37
- "@deepseek-ai/cordis": "~4.0.5-alpha.1",
38
- "@deepseek-ai/dsh-attachment": "0.2.1-alpha.2",
39
- "@deepseek-ai/dsh-credentials": "0.2.1-alpha.2",
40
- "@deepseek-ai/dsh-launch-environment": "0.2.1-alpha.2",
41
- "@deepseek-ai/dsh-llm": "0.2.1-alpha.2",
42
- "@deepseek-ai/dsh-llm-pi-ai": "0.2.1-alpha.2",
30
+ "@deepseek-ai/dsh-attachment": "0.2.1-alpha.2 || ^0.2.1-rc.2",
31
+ "@deepseek-ai/dsh-fs": "0.2.1-alpha.2 || ^0.2.1-rc.2",
32
+ "@deepseek-ai/dsh-launch-environment": "0.2.1-alpha.2 || ^0.2.1-rc.2",
33
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.2 || ^0.2.1-rc.2",
34
+ "@deepseek-ai/dsh-llm-pi-ai": "0.2.1-alpha.2 || ^0.2.1-rc.2",
43
35
  "@deepseek-ai/schemastery": "~3.18.5-alpha.1",
44
36
  "@earendil-works/pi-ai": "^1.0.2"
45
37
  },
46
38
  "devDependencies": {
39
+ "@earendil-works/pi-ai": "1.1.0",
40
+ "@deepseek-ai/dsh-launch-environment": "link:../../deepseek-harness/packages/util/launch-environment",
47
41
  "@deepseek-ai/cordis": "link:../../deepseek-harness/vendor/cordis",
48
42
  "@deepseek-ai/dsh-api-remotes": "link:../../deepseek-harness/packages/api/remotes",
49
43
  "@deepseek-ai/dsh-app-boot": "link:../../deepseek-harness/packages/boot/app-boot",