dsh-coding-subscription-oauth 0.6.2 → 0.6.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-coding-subscription-oauth",
3
3
  "description": "DeepSeek Harness coding-subscription OAuth: SuperGrok/Grok Build, ChatGPT Plus Codex, Kimi Code, Claude Code. Fixes AUTH API key is invalid, INVALID_REPLAY_STATE, grok-4.6 xhigh, Kimi Bearer vs x-api-key.",
4
- "version": "0.6.2",
4
+ "version": "0.6.4",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "registry": "https://registry.npmjs.org/"
@@ -39,18 +39,18 @@
39
39
  "dsh-coding-oauth": "lib/bin.js",
40
40
  "dsh-grok-build": "lib/bin.js"
41
41
  },
42
- "scripts": {
43
- "check:bom": "node build/verify-dsh-bom.mjs",
42
+ "scripts": {
43
+ "check:bom": "node build/verify-dsh-bom.mjs",
44
44
  "clean": "node build/clean.mjs",
45
45
  "typecheck": "tsc -b tsconfig.host.json tsconfig.client.json",
46
46
  "build:server": "tsc -p tsconfig.build.json && tsc -p tsconfig.dts-bin.json && node build/build-server.mjs",
47
47
  "build:client": "node build/build-client.mjs",
48
- "build:artifacts": "pnpm run check:bom && pnpm run clean && pnpm run typecheck && pnpm run build:server && pnpm run build:client",
48
+ "build:artifacts": "pnpm run check:bom && pnpm run clean && pnpm run typecheck && pnpm run build:server && pnpm run build:client",
49
49
  "build": "pnpm run release:build",
50
50
  "test": "vitest run",
51
51
  "test:watch": "vitest",
52
52
  "lint": "biome check",
53
- "check:next": "pnpm run check:bom && pnpm run lint && pnpm run typecheck && pnpm run test",
53
+ "check:next": "pnpm run check:bom && pnpm run lint && pnpm run typecheck && pnpm run test",
54
54
  "check": "pnpm run lint && pnpm run release:build && pnpm run test",
55
55
  "release:promote": "node build/promote-release.mjs",
56
56
  "release:verify": "node build/verify-release.mjs",
@@ -83,9 +83,9 @@
83
83
  "CHANGELOG.md",
84
84
  "CONTRIBUTING.md",
85
85
  "docs/00-project-rules.md",
86
- "docs/02-architecture.md",
87
- "docs/02-architecture.zh-CN.md",
88
- "compatibility/dsh-bom.json",
86
+ "docs/02-architecture.md",
87
+ "docs/02-architecture.zh-CN.md",
88
+ "compatibility/dsh-bom.json",
89
89
  "README.zh-CN.md",
90
90
  "README.ja.md",
91
91
  "README.ko.md",
@@ -102,34 +102,34 @@
102
102
  "NOTICE"
103
103
  ],
104
104
  "license": "Apache-2.0",
105
- "dsh": {
106
- "compatibility": {
107
- "coreAbi": "dsh-coding-oauth-core/v1",
108
- "verifiedBom": "./compatibility/dsh-bom.json",
109
- "participant": "coding-subscription-oauth",
110
- "diagnostics": "standalone",
111
- "bom": {
112
- "@deepseek-ai/cordis": "4.0.1",
113
- "@deepseek-ai/dsh-atomic-write": "0.1.1-rc.2",
114
- "@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
115
- "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
116
- "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
117
- "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
118
- "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
119
- "@deepseek-ai/dsh-client-web": "0.1.1-rc.2",
120
- "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
121
- "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
122
- "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
123
- "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
124
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
125
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
126
- "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
127
- "@deepseek-ai/schemastery": "3.18.1",
128
- "@earendil-works/pi-ai": "0.84.2",
129
- "react": "18.3.1",
130
- "react-dom": "18.3.1"
131
- }
132
- },
105
+ "dsh": {
106
+ "compatibility": {
107
+ "coreAbi": "dsh-coding-oauth-core/v1",
108
+ "verifiedBom": "./compatibility/dsh-bom.json",
109
+ "participant": "coding-subscription-oauth",
110
+ "diagnostics": "standalone",
111
+ "bom": {
112
+ "@deepseek-ai/cordis": "4.0.1",
113
+ "@deepseek-ai/dsh-atomic-write": "0.1.1-rc.2",
114
+ "@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
115
+ "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
116
+ "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
117
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
118
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
119
+ "@deepseek-ai/dsh-client-web": "0.1.1-rc.2",
120
+ "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
121
+ "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
122
+ "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
123
+ "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
124
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
125
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
126
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
127
+ "@deepseek-ai/schemastery": "3.18.1",
128
+ "@earendil-works/pi-ai": "0.84.2",
129
+ "react": "18.3.1",
130
+ "react-dom": "18.3.1"
131
+ }
132
+ },
133
133
  "bundle": {
134
134
  "patch": "./cordis.patch.yml"
135
135
  },
@@ -142,25 +142,25 @@
142
142
  "platform": "web"
143
143
  }
144
144
  },
145
- "dependencies": {
146
- "dsh-coding-oauth-core": "0.1.0",
147
- "undici": "^7.24.8"
148
- },
145
+ "dependencies": {
146
+ "dsh-coding-oauth-core": "0.1.1",
147
+ "undici": "7.29.0"
148
+ },
149
149
  "peerDependencies": {
150
- "@deepseek-ai/cordis": "4.0.1",
151
- "@deepseek-ai/dsh-atomic-write": "0.1.1-rc.2",
152
- "@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
153
- "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
154
- "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
155
- "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
156
- "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
157
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
158
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
159
- "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
150
+ "@deepseek-ai/cordis": "4.0.1",
151
+ "@deepseek-ai/dsh-atomic-write": "0.1.1-rc.2",
152
+ "@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
153
+ "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
154
+ "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
155
+ "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
156
+ "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
157
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
158
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
159
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
160
160
  "@deepseek-ai/schemastery": "3.18.1",
161
161
  "@earendil-works/pi-ai": "0.84.2",
162
- "react": "18.3.1",
163
- "react-dom": "18.3.1"
162
+ "react": "18.3.1",
163
+ "react-dom": "18.3.1"
164
164
  },
165
165
  "peerDependenciesMeta": {
166
166
  "@deepseek-ai/cordis": {
@@ -181,40 +181,40 @@
181
181
  "@deepseek-ai/dsh-tools": {
182
182
  "optional": true
183
183
  },
184
- "react": {
185
- "optional": true
186
- },
187
- "react-dom": {
188
- "optional": true
184
+ "react": {
185
+ "optional": true
186
+ },
187
+ "react-dom": {
188
+ "optional": true
189
189
  }
190
190
  },
191
191
  "devDependencies": {
192
192
  "@biomejs/biome": "^2.5.8",
193
- "@deepseek-ai/cordis": "4.0.1",
194
- "@deepseek-ai/dsh-atomic-write": "0.1.1-rc.2",
195
- "@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
196
- "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
197
- "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
198
- "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
199
- "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
200
- "@deepseek-ai/dsh-client-web": "0.1.1-rc.2",
201
- "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
202
- "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
203
- "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
204
- "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
205
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
206
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
207
- "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
193
+ "@deepseek-ai/cordis": "4.0.1",
194
+ "@deepseek-ai/dsh-atomic-write": "0.1.1-rc.2",
195
+ "@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
196
+ "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
197
+ "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
198
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
199
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
200
+ "@deepseek-ai/dsh-client-web": "0.1.1-rc.2",
201
+ "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
202
+ "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
203
+ "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
204
+ "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
205
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
206
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
207
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
208
208
  "@deepseek-ai/schemastery": "3.18.1",
209
209
  "@earendil-works/pi-ai": "0.84.2",
210
210
  "@types/node": "^22.20.0",
211
- "@types/react": "~18.3.1",
212
- "@types/react-dom": "18.3.7",
211
+ "@types/react": "~18.3.1",
212
+ "@types/react-dom": "18.3.7",
213
213
  "esbuild": "^0.28.2",
214
- "react": "18.3.1",
215
- "react-dom": "18.3.1",
214
+ "react": "18.3.1",
215
+ "react-dom": "18.3.1",
216
216
  "typescript": "^6.0.3",
217
- "undici": "^7.24.8",
217
+ "undici": "7.29.0",
218
218
  "vitest": "^4.1.8"
219
219
  },
220
220
  "overrides": {
package/src/adapter.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import type { AttachmentStore } from "@deepseek-ai/dsh-attachment";
4
4
  import type { RetryPolicyConfig } from "@deepseek-ai/dsh-llm";
5
5
  import { type LlmAdapter, LlmError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
6
- import type { PiAiAdapterOptions, ResolvedPiAiProviderProfile } from "@deepseek-ai/dsh-llm-pi-ai";
6
+ import type { PiAiAdapterOptions, ResolvedPiAiProviderProfile } from "@deepseek-ai/dsh-llm-pi-ai";
7
7
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
8
8
  import type { AliasLlmRoutePolicy } from "./alias-adapter.ts";
9
9
  import { AliasLlmAdapter } from "./alias-adapter.ts";
@@ -25,60 +25,60 @@ import {
25
25
  import type { OAuthProviderSession } from "./oauth-session.ts";
26
26
  import { grokBuildBaselineModels, grokBuildFingerprintHeaders } from "./provider.ts";
27
27
  import { safeMessage } from "./redact.ts";
28
- import type { GrokBuildSession } from "./session.ts";
29
-
30
- type PiAiAuthInjection = PiAiAdapterOptions["auth"];
31
- type PiAiCredentialStore = PiAiAuthInjection["credentials"];
32
-
33
- const REQUEST_IMAGE_POLICY = {
34
- maxRequestImageBytes: 20 * 1024 * 1024,
35
- requestImagePixelBudget: 2048 * 2048,
36
- requestImageMaxBytes: 1024 * 1024,
37
- } as const;
38
-
39
- /**
40
- * Route pi-ai credential operations to the already owner-locked OAuth files.
41
- * The adapter may ask its collection about every profile, so an unknown id is
42
- * never allowed to reach a writable store. Reads are empty and writes fail
43
- * closed, preserving the existing per-provider refresh lock and file policy.
44
- */
45
- function oauthAuthInjection(grok: GrokBuildSession, subscriptions: readonly OAuthProviderSession[]): PiAiAuthInjection {
46
- const stores = new Map<string, PiAiCredentialStore>([
47
- [XAI_PI_PROVIDER, grok.store],
48
- ...subscriptions.map((session) => [session.definition.nativeProviderId, session.store] as const),
49
- ]);
50
- const storeFor = (providerId: string): PiAiCredentialStore | undefined => stores.get(providerId);
51
- return {
52
- credentials: {
53
- async read(providerId, options) {
54
- return storeFor(providerId)?.read(providerId, options);
55
- },
56
- async list(options) {
57
- const entries = await Promise.all([...stores.values()].map((store) => store.list(options)));
58
- return entries.flat();
59
- },
60
- async modify(providerId, fn, options) {
61
- const store = storeFor(providerId);
62
- if (store === undefined)
63
- throw new Error(`refusing credential write for unknown OAuth provider "${providerId}"`);
64
- return store.modify(providerId, fn, options);
65
- },
66
- async delete(providerId, options) {
67
- const store = storeFor(providerId);
68
- if (store === undefined)
69
- throw new Error(`refusing credential deletion for unknown OAuth provider "${providerId}"`);
70
- await store.delete(providerId, options);
71
- },
72
- },
73
- authContext: {
74
- // Subscription adapters obtain request credentials only from their
75
- // owner-scoped stores. Do not let a foreign environment/file credential
76
- // silently change a selected OAuth route.
77
- env: async () => undefined,
78
- fileExists: async () => false,
79
- },
80
- };
81
- }
28
+ import type { GrokBuildSession } from "./session.ts";
29
+
30
+ type PiAiAuthInjection = PiAiAdapterOptions["auth"];
31
+ type PiAiCredentialStore = PiAiAuthInjection["credentials"];
32
+
33
+ const REQUEST_IMAGE_POLICY = {
34
+ maxRequestImageBytes: 20 * 1024 * 1024,
35
+ requestImagePixelBudget: 2048 * 2048,
36
+ requestImageMaxBytes: 1024 * 1024,
37
+ } as const;
38
+
39
+ /**
40
+ * Route pi-ai credential operations to the already owner-locked OAuth files.
41
+ * The adapter may ask its collection about every profile, so an unknown id is
42
+ * never allowed to reach a writable store. Reads are empty and writes fail
43
+ * closed, preserving the existing per-provider refresh lock and file policy.
44
+ */
45
+ function oauthAuthInjection(grok: GrokBuildSession, subscriptions: readonly OAuthProviderSession[]): PiAiAuthInjection {
46
+ const stores = new Map<string, PiAiCredentialStore>([
47
+ [XAI_PI_PROVIDER, grok.store],
48
+ ...subscriptions.map((session) => [session.definition.nativeProviderId, session.store] as const),
49
+ ]);
50
+ const storeFor = (providerId: string): PiAiCredentialStore | undefined => stores.get(providerId);
51
+ return {
52
+ credentials: {
53
+ async read(providerId, options) {
54
+ return storeFor(providerId)?.read(providerId, options);
55
+ },
56
+ async list(options) {
57
+ const entries = await Promise.all([...stores.values()].map((store) => store.list(options)));
58
+ return entries.flat();
59
+ },
60
+ async modify(providerId, fn, options) {
61
+ const store = storeFor(providerId);
62
+ if (store === undefined)
63
+ throw new Error(`refusing credential write for unknown OAuth provider "${providerId}"`);
64
+ return store.modify(providerId, fn, options);
65
+ },
66
+ async delete(providerId, options) {
67
+ const store = storeFor(providerId);
68
+ if (store === undefined)
69
+ throw new Error(`refusing credential deletion for unknown OAuth provider "${providerId}"`);
70
+ await store.delete(providerId, options);
71
+ },
72
+ },
73
+ authContext: {
74
+ // Subscription adapters obtain request credentials only from their
75
+ // owner-scoped stores. Do not let a foreign environment/file credential
76
+ // silently change a selected OAuth route.
77
+ env: async () => undefined,
78
+ fileExists: async () => false,
79
+ },
80
+ };
81
+ }
82
82
 
83
83
  /** Prefer grok-4.6 when the current (live or baseline) list has it. */
84
84
  export function preferredGrokBuildModel(models: readonly { id: string }[] = grokBuildBaselineModels()): string {
@@ -139,8 +139,8 @@ function profile(
139
139
  retryPolicy ?? CODING_OAUTH_RETRY_POLICY,
140
140
  "dsh-coding-subscription-oauth retryPolicy",
141
141
  ),
142
- configuredMaxTokens: new Map(),
143
- ...REQUEST_IMAGE_POLICY,
142
+ configuredMaxTokens: new Map(),
143
+ ...REQUEST_IMAGE_POLICY,
144
144
  ...(headers === undefined ? {} : { headers }),
145
145
  piProvider,
146
146
  };
@@ -151,7 +151,7 @@ export function createGrokBuildAdapter(
151
151
  session: GrokBuildSession,
152
152
  resolveAttachments: () => AttachmentStore | undefined,
153
153
  ): PiAiAdapter {
154
- return new PiAiAdapter({
154
+ return new PiAiAdapter({
155
155
  profiles: () =>
156
156
  new Map<string, ResolvedPiAiProviderProfile>([
157
157
  [
@@ -159,13 +159,13 @@ export function createGrokBuildAdapter(
159
159
  profile(GROK_BUILD_ROUTE, "xAI Grok Build", session.provider(), undefined, grokBuildFingerprintHeaders()),
160
160
  ],
161
161
  ]),
162
- resolveApiKey: async () =>
162
+ resolveApiKey: async () =>
163
163
  resolveOAuthToken("Grok Build", async () => {
164
164
  const auth = await session.models.getAuth(XAI_PI_PROVIDER, { minOAuthValidityMs: MIN_OAUTH_VALIDITY_MS });
165
165
  return auth?.auth.apiKey;
166
- }),
167
- auth: oauthAuthInjection(session, []),
168
- resolveAttachments,
166
+ }),
167
+ auth: oauthAuthInjection(session, []),
168
+ resolveAttachments,
169
169
  });
170
170
  }
171
171
 
@@ -256,7 +256,7 @@ export function createCodingOAuthAdapter(
256
256
  });
257
257
  }
258
258
 
259
- const inner = new PiAiAdapter({
259
+ const inner = new PiAiAdapter({
260
260
  profiles: () => {
261
261
  const profiles = new Map<string, ResolvedPiAiProviderProfile>();
262
262
  profiles.set(
@@ -299,7 +299,7 @@ export function createCodingOAuthAdapter(
299
299
  }
300
300
  return profiles;
301
301
  },
302
- resolveApiKey: async (provider) => {
302
+ resolveApiKey: async (provider) => {
303
303
  if (provider === GROK_BUILD_ROUTE) {
304
304
  return resolveOAuthToken("Grok Build", async () => {
305
305
  const auth = await grok.models.getAuth(XAI_PI_PROVIDER, { minOAuthValidityMs: MIN_OAUTH_VALIDITY_MS });
@@ -309,19 +309,19 @@ export function createCodingOAuthAdapter(
309
309
  const session =
310
310
  provider === CODEX_OAUTH_FAST_ROUTE ? byNativeId.get(CODEX_PI_PROVIDER) : byNativeId.get(provider);
311
311
  if (session === undefined) throw new LlmError(`Unknown OAuth provider "${provider}"`, "NO_ADAPTER");
312
- return resolveOAuthToken(session.definition.displayName, () => session.resolveAccessToken());
313
- },
314
- auth: oauthAuthInjection(grok, subscriptions),
315
- resolveAttachments,
312
+ return resolveOAuthToken(session.definition.displayName, () => session.resolveAccessToken());
313
+ },
314
+ auth: oauthAuthInjection(grok, subscriptions),
315
+ resolveAttachments,
316
316
  });
317
317
 
318
- // The Fast wrapper restores wire/replay model identity to openai-codex.
319
- // Keep the opaque envelope untouched and map public Fast source identity to
320
- // that native replay provider before PiAiAdapter validates it.
321
- const replayProviders = new Map(aliases);
322
- replayProviders.set(CODEX_OAUTH_FAST_ROUTE, CODEX_PI_PROVIDER);
323
- return new AliasLlmAdapter(inner, aliases, policies, replayProviders);
324
- }
318
+ // The Fast wrapper restores wire/replay model identity to openai-codex.
319
+ // Keep the opaque envelope untouched and map public Fast source identity to
320
+ // that native replay provider before PiAiAdapter validates it.
321
+ const replayProviders = new Map(aliases);
322
+ replayProviders.set(CODEX_OAUTH_FAST_ROUTE, CODEX_PI_PROVIDER);
323
+ return new AliasLlmAdapter(inner, aliases, policies, replayProviders);
324
+ }
325
325
 
326
326
  /**
327
327
  * Resolve an OAuth access token for one route, translating a failed refresh
@@ -36,10 +36,10 @@ export interface AliasLlmRoutePolicy {
36
36
  onAuthFailure?: () => Promise<void>;
37
37
  }
38
38
 
39
- export function normalizeReplayForRoute(
40
- message: GenerateOptions["messages"][number],
41
- route: string,
42
- nativeReplayProvider: string,
39
+ export function normalizeReplayForRoute(
40
+ message: GenerateOptions["messages"][number],
41
+ route: string,
42
+ nativeReplayProvider: string,
43
43
  ): GenerateOptions["messages"][number] {
44
44
  if (message.role !== "assistant" || message.source.kind !== "model") return message;
45
45
  if (message.source.provider !== route) {
@@ -47,10 +47,10 @@ export function normalizeReplayForRoute(
47
47
  const { replayState: _foreignReplay, ...source } = message.source;
48
48
  return { ...message, source };
49
49
  }
50
- // The rc.2 envelope stays entirely opaque. PiAiAdapter only keys replay
51
- // validation from source.provider, so restore the native provider identity
52
- // without inspecting or changing replayState.
53
- return { ...message, source: { ...message.source, provider: nativeReplayProvider } };
50
+ // The rc.2 envelope stays entirely opaque. PiAiAdapter only keys replay
51
+ // validation from source.provider, so restore the native provider identity
52
+ // without inspecting or changing replayState.
53
+ return { ...message, source: { ...message.source, provider: nativeReplayProvider } };
54
54
  }
55
55
 
56
56
  /**
@@ -58,11 +58,11 @@ export function normalizeReplayForRoute(
58
58
  * Harness route names. Every public operation translates exactly once.
59
59
  */
60
60
  export class AliasLlmAdapter extends LlmAdapter {
61
- constructor(
62
- private readonly inner: LlmAdapter,
63
- private readonly aliases: ReadonlyMap<string, string>,
64
- private readonly policies: ReadonlyMap<string, AliasLlmRoutePolicy> = new Map(),
65
- private readonly replayProviders: ReadonlyMap<string, string> = aliases,
61
+ constructor(
62
+ private readonly inner: LlmAdapter,
63
+ private readonly aliases: ReadonlyMap<string, string>,
64
+ private readonly policies: ReadonlyMap<string, AliasLlmRoutePolicy> = new Map(),
65
+ private readonly replayProviders: ReadonlyMap<string, string> = aliases,
66
66
  ) {
67
67
  super();
68
68
  }
@@ -109,9 +109,9 @@ export class AliasLlmAdapter extends LlmAdapter {
109
109
  async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
110
110
  const route = options.provider;
111
111
  const native = this.nativeProvider(route);
112
- const messages = options.messages.map((message) =>
113
- normalizeReplayForRoute(message, route, this.replayProviders.get(route) ?? native),
114
- );
112
+ const messages = options.messages.map((message) =>
113
+ normalizeReplayForRoute(message, route, this.replayProviders.get(route) ?? native),
114
+ );
115
115
  let authFailureNotified = false;
116
116
  for await (const raw of this.inner.stream({ ...options, provider: native, messages })) {
117
117
  const chunk =
@@ -141,7 +141,7 @@ export class AliasLlmAdapter extends LlmAdapter {
141
141
  }
142
142
  }
143
143
  }
144
- yield chunk;
144
+ yield chunk;
145
145
  }
146
146
  }
147
147
  }
@@ -302,6 +302,7 @@ function sameSettings(left: CapabilitySettings, right: CapabilitySettings): bool
302
302
  left.codexSearch === right.codexSearch &&
303
303
  left.codexImages === right.codexImages &&
304
304
  left.codexImageEdits === right.codexImageEdits &&
305
+ left.codexImagesAnyModel === right.codexImagesAnyModel &&
305
306
  left.codexUsage === right.codexUsage &&
306
307
  left.codexFast === right.codexFast &&
307
308
  left.grokImagineImage === right.grokImagineImage &&
@@ -18,6 +18,7 @@ export const CAPABILITY_FLAG_KEYS = [
18
18
  "codexSearch",
19
19
  "codexImages",
20
20
  "codexImageEdits",
21
+ "codexImagesAnyModel",
21
22
  "codexUsage",
22
23
  "codexFast",
23
24
  "grokImagineImage",
@@ -39,6 +40,8 @@ export interface CapabilitySettings {
39
40
  readonly codexSearch: boolean;
40
41
  readonly codexImages: boolean;
41
42
  readonly codexImageEdits: boolean;
43
+ /** Allow non-Codex-route models to use the Codex image generate/edit tools. */
44
+ readonly codexImagesAnyModel: boolean;
42
45
  readonly codexUsage: boolean;
43
46
  readonly codexFast: boolean;
44
47
  readonly grokImagineImage: boolean;
@@ -67,6 +70,7 @@ export const DEFAULT_CAPABILITY_SETTINGS: CapabilitySettings = Object.freeze({
67
70
  codexSearch: false,
68
71
  codexImages: false,
69
72
  codexImageEdits: false,
73
+ codexImagesAnyModel: false,
70
74
  codexUsage: false,
71
75
  codexFast: false,
72
76
  grokImagineImage: false,
@@ -84,6 +88,7 @@ export const CapabilitySettingsSchema = Schema.object({
84
88
  codexSearch: Schema.boolean().default(false),
85
89
  codexImages: Schema.boolean().default(false),
86
90
  codexImageEdits: Schema.boolean().default(false),
91
+ codexImagesAnyModel: Schema.boolean().default(false),
87
92
  codexUsage: Schema.boolean().default(false),
88
93
  codexFast: Schema.boolean().default(false),
89
94
  grokImagineImage: Schema.boolean().default(false),
@@ -238,6 +243,7 @@ export function capabilityFlags(settings: CapabilitySettings): Pick<CapabilitySe
238
243
  codexSearch: settings.codexSearch,
239
244
  codexImages: settings.codexImages,
240
245
  codexImageEdits: settings.codexImageEdits,
246
+ codexImagesAnyModel: settings.codexImagesAnyModel,
241
247
  codexUsage: settings.codexUsage,
242
248
  codexFast: settings.codexFast,
243
249
  grokImagineImage: settings.grokImagineImage,
@@ -272,10 +278,15 @@ export function resolveCapabilitySettings(
272
278
  */
273
279
  export function normalizeCapabilitySettings(input?: unknown): CapabilitySettings {
274
280
  const patch = normalizeCapabilitySettingsPatch(input);
281
+ const codexImages = patch.codexImages ?? DEFAULT_CAPABILITY_SETTINGS.codexImages;
275
282
  return Object.freeze({
276
283
  codexSearch: patch.codexSearch ?? DEFAULT_CAPABILITY_SETTINGS.codexSearch,
277
- codexImages: patch.codexImages ?? DEFAULT_CAPABILITY_SETTINGS.codexImages,
284
+ codexImages,
278
285
  codexImageEdits: patch.codexImageEdits ?? DEFAULT_CAPABILITY_SETTINGS.codexImageEdits,
286
+ // any-model is meaningless without images; clear at admit so UI cascade alone cannot leave it on
287
+ codexImagesAnyModel: codexImages
288
+ ? (patch.codexImagesAnyModel ?? DEFAULT_CAPABILITY_SETTINGS.codexImagesAnyModel)
289
+ : false,
279
290
  codexUsage: patch.codexUsage ?? DEFAULT_CAPABILITY_SETTINGS.codexUsage,
280
291
  codexFast: patch.codexFast ?? DEFAULT_CAPABILITY_SETTINGS.codexFast,
281
292
  grokImagineImage: patch.grokImagineImage ?? DEFAULT_CAPABILITY_SETTINGS.grokImagineImage,
@@ -296,6 +307,7 @@ export function normalizeCapabilitySettingsPatch(input?: unknown): CapabilitySet
296
307
  codexSearch?: boolean;
297
308
  codexImages?: boolean;
298
309
  codexImageEdits?: boolean;
310
+ codexImagesAnyModel?: boolean;
299
311
  codexUsage?: boolean;
300
312
  codexFast?: boolean;
301
313
  grokImagineImage?: boolean;
@@ -308,6 +320,7 @@ export function normalizeCapabilitySettingsPatch(input?: unknown): CapabilitySet
308
320
  assignFlag(patch, "codexSearch", flags["codexSearch"]);
309
321
  assignFlag(patch, "codexImages", flags["codexImages"]);
310
322
  assignFlag(patch, "codexImageEdits", flags["codexImageEdits"]);
323
+ assignFlag(patch, "codexImagesAnyModel", flags["codexImagesAnyModel"]);
311
324
  assignFlag(patch, "codexUsage", flags["codexUsage"]);
312
325
  assignFlag(patch, "codexFast", flags["codexFast"]);
313
326
  assignFlag(patch, "grokImagineImage", flags["grokImagineImage"]);
@@ -22,6 +22,7 @@ import {
22
22
  type CodexImageQuality,
23
23
  type CodexImageResult,
24
24
  type CodexImageRoute,
25
+ type CodexImageRoutePolicy,
25
26
  type CodexImageSessionContext,
26
27
  type CodexImageSize,
27
28
  createCodexImageController,
@@ -72,7 +73,18 @@ const IMAGINE_MEDIA_PREFIX = "/plugins/dsh-grok-build/imagine/media/";
72
73
  export type CapabilityImagineClient = Pick<GrokImagineClient, "generateImage" | "startVideo" | "videoStatus">;
73
74
 
74
75
  /** Per-exec Codex controller factory. Tests inject a fake; production binds auth + attachments. */
75
- export type CreateCodexImageController = (session: CodexImageSessionContext) => CodexImageController;
76
+ export type CreateCodexImageController = (
77
+ session: CodexImageSessionContext,
78
+ routePolicy: CodexImageRoutePolicy,
79
+ ) => CodexImageController;
80
+
81
+ /**
82
+ * Route gate for the Codex image tools. `codexImagesAnyModel` relaxes the
83
+ * Codex-route requirement; otherwise the gate stays `codex-capable`.
84
+ */
85
+ export function codexImageRoutePolicy(settings: CapabilitySettings): CodexImageRoutePolicy {
86
+ return settings.codexImagesAnyModel ? "any" : "codex-capable";
87
+ }
76
88
 
77
89
  /** Resolve authoritative host model metadata for the calling route. */
78
90
  export type ResolveCodexImageRoute = (exec: ToolRunContext) => Promise<CodexImageRoute | undefined>;
@@ -390,11 +402,12 @@ export async function createCapabilityTools(options: CapabilityToolsOptions): Pr
390
402
  const { defineTool } = await import("@deepseek-ai/dsh-tools");
391
403
  const createController: CreateCodexImageController =
392
404
  options.createCodexController ??
393
- ((session) =>
405
+ ((session, routePolicy) =>
394
406
  createCodexImageController({
395
407
  auth: options.auth,
396
408
  attachments: options.attachments,
397
409
  session,
410
+ routePolicy,
398
411
  }));
399
412
 
400
413
  const generate = defineTool({
@@ -438,7 +451,10 @@ export async function createCapabilityTools(options: CapabilityToolsOptions): Pr
438
451
  const settings = options.current();
439
452
  if (!settings.codexImages) disabled(CODEX_IMAGE_GENERATE_TOOL);
440
453
  const n = resolveImageCount(args.n, settings);
441
- const result = await createController(await generateSession(exec, options.resolveCodexImageRoute)).generate(
454
+ const result = await createController(
455
+ await generateSession(exec, options.resolveCodexImageRoute),
456
+ codexImageRoutePolicy(settings),
457
+ ).generate(
442
458
  {
443
459
  prompt: args.prompt,
444
460
  n,
@@ -499,7 +515,10 @@ export async function createCapabilityTools(options: CapabilityToolsOptions): Pr
499
515
  const settings = options.current();
500
516
  if (!settings.codexImageEdits || !settings.codexImages) disabled(CODEX_IMAGE_EDIT_TOOL);
501
517
  const n = resolveImageCount(args.n, settings);
502
- const result = await createController(await requireEditSession(exec, options.resolveCodexImageRoute)).edit(
518
+ const result = await createController(
519
+ await requireEditSession(exec, options.resolveCodexImageRoute),
520
+ codexImageRoutePolicy(settings),
521
+ ).edit(
503
522
  {
504
523
  prompt: args.prompt,
505
524
  imageIds: args.imageIds,
@@ -416,9 +416,10 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
416
416
  const patchCapability = async (key: CapabilitySettingKey, value: boolean | number): Promise<boolean> => {
417
417
  if (capabilities === undefined || !capabilities.writable) return false;
418
418
  if (key === "codexImageEdits" && value === true && !capabilities.value.codexImages) return false;
419
+ if (key === "codexImagesAnyModel" && value === true && !capabilities.value.codexImages) return false;
419
420
  const patch: Partial<CapabilitySettingsView> =
420
- key === "codexImages" && value === false && capabilities.value.codexImageEdits
421
- ? { codexImages: false, codexImageEdits: false }
421
+ key === "codexImages" && value === false
422
+ ? { codexImages: false, codexImageEdits: false, codexImagesAnyModel: false }
422
423
  : ({ [key]: value } as Partial<CapabilitySettingsView>);
423
424
  setCapabilitiesBusy(true);
424
425
  try {