dsh-coding-subscription-oauth 0.6.4 → 0.7.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +291 -255
  2. package/CONTRIBUTING.md +138 -129
  3. package/INSTALL.md +261 -256
  4. package/LICENSE +19 -19
  5. package/NOTICE +11 -11
  6. package/README.de.md +303 -303
  7. package/README.es.md +304 -304
  8. package/README.fr.md +304 -304
  9. package/README.ja.md +304 -304
  10. package/README.ko.md +304 -304
  11. package/README.md +321 -320
  12. package/README.pt-BR.md +304 -304
  13. package/README.ru.md +304 -304
  14. package/README.zh-CN.md +319 -318
  15. package/compatibility/dsh-bom.json +36 -30
  16. package/cordis.patch.yml +13 -13
  17. package/docs/00-project-rules.md +213 -212
  18. package/docs/02-architecture.md +142 -138
  19. package/docs/02-architecture.zh-CN.md +142 -138
  20. package/lib/auth-routes.d.ts +10 -1
  21. package/lib/auth-routes.d.ts.map +1 -1
  22. package/lib/bin.js +483 -50
  23. package/lib/bin.js.map +4 -4
  24. package/lib/client.js +5 -5
  25. package/lib/client.js.map +4 -4
  26. package/lib/compatibility.d.ts +0 -1
  27. package/lib/compatibility.d.ts.map +1 -1
  28. package/lib/gateway-protocol.d.ts +3 -46
  29. package/lib/gateway-protocol.d.ts.map +1 -1
  30. package/lib/gateway-routes.d.ts.map +1 -1
  31. package/lib/grok-errors.d.ts +2 -12
  32. package/lib/grok-errors.d.ts.map +1 -1
  33. package/lib/grok-imagine/client.d.ts +73 -0
  34. package/lib/grok-imagine/client.d.ts.map +1 -0
  35. package/lib/grok-imagine/index.d.ts +8 -0
  36. package/lib/grok-imagine/index.d.ts.map +1 -0
  37. package/lib/grok-imagine/net.d.ts +10 -0
  38. package/lib/grok-imagine/net.d.ts.map +1 -0
  39. package/lib/grok-imagine/parse.d.ts +20 -0
  40. package/lib/grok-imagine/parse.d.ts.map +1 -0
  41. package/lib/grok-imagine/types.d.ts +191 -0
  42. package/lib/grok-imagine/types.d.ts.map +1 -0
  43. package/lib/grok-imagine.d.ts +3 -263
  44. package/lib/grok-imagine.d.ts.map +1 -1
  45. package/lib/http-json.d.ts +2 -9
  46. package/lib/http-json.d.ts.map +1 -1
  47. package/lib/ids.d.ts +4 -0
  48. package/lib/ids.d.ts.map +1 -1
  49. package/lib/index.d.ts +2 -1
  50. package/lib/index.d.ts.map +1 -1
  51. package/lib/index.js +730 -196
  52. package/lib/index.js.map +4 -4
  53. package/lib/invariant.js.map +1 -1
  54. package/lib/kimi-errors.d.ts +2 -12
  55. package/lib/kimi-errors.d.ts.map +1 -1
  56. package/lib/oauth-session.d.ts +2 -0
  57. package/lib/oauth-session.d.ts.map +1 -1
  58. package/lib/session.d.ts +2 -0
  59. package/lib/session.d.ts.map +1 -1
  60. package/lib/store.d.ts +73 -1
  61. package/lib/store.d.ts.map +1 -1
  62. package/media/en/settings_accounts.png +0 -0
  63. package/media/en/settings_capabilities.png +0 -0
  64. package/media/en/settings_gateway.png +0 -0
  65. package/media/settings_accounts.png +0 -0
  66. package/media/settings_capabilities.png +0 -0
  67. package/media/settings_gateway.png +0 -0
  68. package/media/settings_overview.png +0 -0
  69. package/media/zh-CN/settings_accounts.png +0 -0
  70. package/media/zh-CN/settings_capabilities.png +0 -0
  71. package/media/zh-CN/settings_gateway.png +0 -0
  72. package/package.json +224 -221
  73. package/patches/dsh-agy@0.1.2.patch +25 -25
  74. package/scripts/release.mjs +187 -186
  75. package/scripts/smoke-deployed-routes.mjs +146 -146
  76. package/scripts/verify-deployed-catalog.mjs +87 -87
  77. package/src/adapter.ts +0 -348
  78. package/src/alias-adapter.ts +0 -147
  79. package/src/auth-routes.ts +0 -921
  80. package/src/auth.ts +0 -67
  81. package/src/bin.ts +0 -350
  82. package/src/capability-routes.ts +0 -279
  83. package/src/capability-runtime.ts +0 -314
  84. package/src/capability-settings.ts +0 -671
  85. package/src/capability-tools.ts +0 -685
  86. package/src/catalog.ts +0 -271
  87. package/src/client/GrokBuildSettings.tsx +0 -771
  88. package/src/client/api.ts +0 -88
  89. package/src/client/components/AboutTab.tsx +0 -30
  90. package/src/client/components/AccountsTab.tsx +0 -241
  91. package/src/client/components/Badge.tsx +0 -33
  92. package/src/client/components/CapabilitiesTab.tsx +0 -265
  93. package/src/client/components/CliPullPreview.tsx +0 -116
  94. package/src/client/components/CopyButton.tsx +0 -57
  95. package/src/client/components/GatewayTab.tsx +0 -469
  96. package/src/client/components/NoticeBanner.tsx +0 -46
  97. package/src/client/components/ProgressBar.tsx +0 -53
  98. package/src/client/components/ProviderCard.tsx +0 -606
  99. package/src/client/components/SettingsTabs.tsx +0 -75
  100. package/src/client/components/ToggleSwitch.tsx +0 -71
  101. package/src/client/constants.ts +0 -230
  102. package/src/client/display.ts +0 -61
  103. package/src/client/dshClientAdapter.ts +0 -127
  104. package/src/client/gatewaySnippets.ts +0 -37
  105. package/src/client/index.tsx +0 -156
  106. package/src/client/locales.ts +0 -540
  107. package/src/client/microStyles.ts +0 -52
  108. package/src/client/parsers.ts +0 -398
  109. package/src/client/styles.ts +0 -325
  110. package/src/client/types.ts +0 -199
  111. package/src/codex-http.ts +0 -447
  112. package/src/codex-images.ts +0 -503
  113. package/src/codex-model-capabilities.ts +0 -320
  114. package/src/codex-search.ts +0 -245
  115. package/src/codex-usage.ts +0 -263
  116. package/src/compatibility.ts +0 -55
  117. package/src/dsh-host-adapter.ts +0 -173
  118. package/src/gateway-anthropic-messages.ts +0 -84
  119. package/src/gateway-auth.ts +0 -102
  120. package/src/gateway-backend.ts +0 -274
  121. package/src/gateway-body.ts +0 -49
  122. package/src/gateway-config.ts +0 -76
  123. package/src/gateway-http.ts +0 -104
  124. package/src/gateway-openai-chat.ts +0 -124
  125. package/src/gateway-openai-responses.ts +0 -53
  126. package/src/gateway-parse.ts +0 -224
  127. package/src/gateway-protocol.ts +0 -52
  128. package/src/gateway-routes.ts +0 -158
  129. package/src/gateway.ts +0 -258
  130. package/src/grok-errors.ts +0 -24
  131. package/src/grok-imagine.ts +0 -1627
  132. package/src/grok-import.ts +0 -151
  133. package/src/http-json.ts +0 -82
  134. package/src/ids.ts +0 -59
  135. package/src/imagine-routes.ts +0 -463
  136. package/src/index.ts +0 -735
  137. package/src/invariant.ts +0 -17
  138. package/src/kimi-errors.ts +0 -26
  139. package/src/media-store.ts +0 -927
  140. package/src/oauth-import-routes.ts +0 -324
  141. package/src/oauth-providers.ts +0 -152
  142. package/src/oauth-session.ts +0 -183
  143. package/src/oauth-sources.ts +0 -1104
  144. package/src/oauth.ts +0 -620
  145. package/src/provider.ts +0 -128
  146. package/src/proxy.ts +0 -11
  147. package/src/redact.ts +0 -72
  148. package/src/session.ts +0 -218
  149. package/src/store.ts +0 -217
  150. package/src/web-origin.ts +0 -296
  151. package/src/web-routes.ts +0 -38
package/src/adapter.ts DELETED
@@ -1,348 +0,0 @@
1
- /** Coding-subscription adapter assembled from public dsh-llm-pi-ai extension points. */
2
-
3
- import type { AttachmentStore } from "@deepseek-ai/dsh-attachment";
4
- import type { RetryPolicyConfig } from "@deepseek-ai/dsh-llm";
5
- import { type LlmAdapter, LlmError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
6
- import type { PiAiAdapterOptions, ResolvedPiAiProviderProfile } from "@deepseek-ai/dsh-llm-pi-ai";
7
- import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
8
- import type { AliasLlmRoutePolicy } from "./alias-adapter.ts";
9
- import { AliasLlmAdapter } from "./alias-adapter.ts";
10
- import { preferredGrokBuildModelFrom } from "./catalog.ts";
11
- import { withCodexFastRouting } from "./codex-model-capabilities.ts";
12
- import {
13
- CLAUDE_CODE_OAUTH_ROUTE,
14
- CLAUDE_PI_PROVIDER,
15
- CODEX_OAUTH_FAST_ROUTE,
16
- CODEX_OAUTH_ROUTE,
17
- CODEX_PI_PROVIDER,
18
- DEFAULT_GROK_BUILD_MODEL,
19
- GROK_BUILD_ROUTE,
20
- GROK_BUILD_STREAM_IDLE_TIMEOUT_MS,
21
- KIMI_CODE_OAUTH_ROUTE,
22
- KIMI_PI_PROVIDER,
23
- XAI_PI_PROVIDER,
24
- } from "./ids.ts";
25
- import type { OAuthProviderSession } from "./oauth-session.ts";
26
- import { grokBuildBaselineModels, grokBuildFingerprintHeaders } from "./provider.ts";
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
- }
82
-
83
- /** Prefer grok-4.6 when the current (live or baseline) list has it. */
84
- export function preferredGrokBuildModel(models: readonly { id: string }[] = grokBuildBaselineModels()): string {
85
- return preferredGrokBuildModelFrom(models.length === 0 ? [{ id: DEFAULT_GROK_BUILD_MODEL }] : models);
86
- }
87
-
88
- function missingCredential(name: string): never {
89
- throw new LlmError(
90
- `${name} is not signed in. Open Settings → Coding OAuth and sign in with your subscription.`,
91
- "MISSING_CREDENTIAL",
92
- );
93
- }
94
-
95
- /**
96
- * Minimum remaining validity demanded of an exported OAuth access token.
97
- * pi-ai 0.84+ already refreshes five minutes before the stored expiry; this
98
- * explicit floor documents the plugin contract and hard-fails a refresh that
99
- * returns an even-shorter-lived token instead of handing it to a request.
100
- */
101
- const MIN_OAUTH_VALIDITY_MS = 60_000;
102
-
103
- /**
104
- * Provider retry policy for the coding-subscription routes. The harness
105
- * default retryable set (EMPTY_RESPONSE/RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT)
106
- * deliberately excludes AUTH, so an upstream 401 — e.g. an access token the
107
- * server revoked before its local expiry — used to kill the turn outright.
108
- * AUTH is added here because {@link AliasLlmAdapter} invalidates the stored
109
- * credential on every AUTH finish, so the retried step refreshes first and
110
- * does not repeat the same rejected token. Quota exhaustion stays outside the
111
- * set: retrying a billing-limit 403 cannot succeed and only delays the real
112
- * message. Genuine credential death is converted to MISSING_CREDENTIAL (not
113
- * retryable) by the resolver below, so it cannot loop either.
114
- *
115
- * Five stacked exponential delays (5s → 10s → 20s → 40s → 80s, ~155s total)
116
- * pair with the xAI capacity remap in {@link AliasLlmAdapter}: "at capacity"
117
- * finish errors become RATE_LIMIT so they enter this policy instead of failing
118
- * as PI_AI_ERROR.
119
- */
120
- const CODING_OAUTH_RETRY_POLICY = {
121
- mode: "normal" as const,
122
- maxRetries: 5,
123
- retryableCodes: ["EMPTY_RESPONSE", "RATE_LIMIT", "SERVER", "TIMEOUT", "TRANSPORT", "AUTH"],
124
- backoff: { initialDelayMs: 5_000, maxDelayMs: 80_000, jitterRatio: 0.1 },
125
- };
126
-
127
- function profile(
128
- provider: string,
129
- displayName: string,
130
- piProvider: ResolvedPiAiProviderProfile["piProvider"],
131
- retryPolicy?: RetryPolicyConfig | undefined,
132
- headers?: Record<string, string> | undefined,
133
- ): ResolvedPiAiProviderProfile {
134
- return {
135
- provider,
136
- displayName,
137
- streamIdleTimeoutMs: GROK_BUILD_STREAM_IDLE_TIMEOUT_MS,
138
- retryPolicy: resolveRetryPolicy(
139
- retryPolicy ?? CODING_OAUTH_RETRY_POLICY,
140
- "dsh-coding-subscription-oauth retryPolicy",
141
- ),
142
- configuredMaxTokens: new Map(),
143
- ...REQUEST_IMAGE_POLICY,
144
- ...(headers === undefined ? {} : { headers }),
145
- piProvider,
146
- };
147
- }
148
-
149
- /** Existing Grok-only constructor retained for public API compatibility. */
150
- export function createGrokBuildAdapter(
151
- session: GrokBuildSession,
152
- resolveAttachments: () => AttachmentStore | undefined,
153
- ): PiAiAdapter {
154
- return new PiAiAdapter({
155
- profiles: () =>
156
- new Map<string, ResolvedPiAiProviderProfile>([
157
- [
158
- GROK_BUILD_ROUTE,
159
- profile(GROK_BUILD_ROUTE, "xAI Grok Build", session.provider(), undefined, grokBuildFingerprintHeaders()),
160
- ],
161
- ]),
162
- resolveApiKey: async () =>
163
- resolveOAuthToken("Grok Build", async () => {
164
- const auth = await session.models.getAuth(XAI_PI_PROVIDER, { minOAuthValidityMs: MIN_OAUTH_VALIDITY_MS });
165
- return auth?.auth.apiKey;
166
- }),
167
- auth: oauthAuthInjection(session, []),
168
- resolveAttachments,
169
- });
170
- }
171
-
172
- /** Opt-in Codex Fast wiring; ordinary `codex-oauth` is unchanged when this is omitted. */
173
- export interface CodingOAuthAdapterOptions {
174
- retryPolicy?: RetryPolicyConfig;
175
- codexFast?: { isEligible(modelId: string): boolean };
176
- }
177
-
178
- function isRetryPolicyConfig(value: object): value is RetryPolicyConfig {
179
- return "mode" in value;
180
- }
181
-
182
- function splitCodingOAuthAdapterArgs(
183
- fourth?: RetryPolicyConfig | CodingOAuthAdapterOptions,
184
- fifth?: CodingOAuthAdapterOptions,
185
- ): CodingOAuthAdapterOptions {
186
- if (fifth !== undefined) {
187
- return {
188
- ...(fourth !== undefined && isRetryPolicyConfig(fourth) ? { retryPolicy: fourth } : {}),
189
- ...fifth,
190
- };
191
- }
192
- if (fourth === undefined) return {};
193
- if (isRetryPolicyConfig(fourth)) return { retryPolicy: fourth };
194
- return {
195
- ...(fourth.retryPolicy === undefined ? {} : { retryPolicy: fourth.retryPolicy }),
196
- ...(fourth.codexFast === undefined ? {} : { codexFast: fourth.codexFast }),
197
- };
198
- }
199
-
200
- /** Create the four-route OAuth adapter while preserving each pi-ai native id. */
201
- export function createCodingOAuthAdapter(
202
- grok: GrokBuildSession,
203
- subscriptions: readonly OAuthProviderSession[],
204
- resolveAttachments: () => AttachmentStore | undefined,
205
- retryPolicy?: RetryPolicyConfig,
206
- options?: CodingOAuthAdapterOptions,
207
- ): LlmAdapter;
208
- export function createCodingOAuthAdapter(
209
- grok: GrokBuildSession,
210
- subscriptions: readonly OAuthProviderSession[],
211
- resolveAttachments: () => AttachmentStore | undefined,
212
- options?: CodingOAuthAdapterOptions,
213
- ): LlmAdapter;
214
- export function createCodingOAuthAdapter(
215
- grok: GrokBuildSession,
216
- subscriptions: readonly OAuthProviderSession[],
217
- resolveAttachments: () => AttachmentStore | undefined,
218
- retryPolicyOrOptions?: RetryPolicyConfig | CodingOAuthAdapterOptions,
219
- options?: CodingOAuthAdapterOptions,
220
- ): LlmAdapter {
221
- const { retryPolicy, codexFast } = splitCodingOAuthAdapterArgs(retryPolicyOrOptions, options);
222
- const byNativeId = new Map(subscriptions.map((session) => [session.definition.nativeProviderId, session]));
223
- const codexSession = byNativeId.get(CODEX_PI_PROVIDER);
224
- const aliases = new Map<string, string>([
225
- [GROK_BUILD_ROUTE, GROK_BUILD_ROUTE],
226
- [CODEX_OAUTH_ROUTE, CODEX_PI_PROVIDER],
227
- [KIMI_CODE_OAUTH_ROUTE, KIMI_PI_PROVIDER],
228
- [CLAUDE_CODE_OAUTH_ROUTE, CLAUDE_PI_PROVIDER],
229
- ]);
230
- if (codexFast !== undefined && codexSession !== undefined) {
231
- aliases.set(CODEX_OAUTH_FAST_ROUTE, CODEX_OAUTH_FAST_ROUTE);
232
- }
233
- const policies = new Map<string, AliasLlmRoutePolicy>([
234
- [
235
- GROK_BUILD_ROUTE,
236
- {
237
- displayName: "xAI Grok Build (OAuth)",
238
- isAuthenticated: async () => (await grok.store.read(XAI_PI_PROVIDER))?.type === "oauth",
239
- onAuthFailure: () => grok.invalidateAccessToken(),
240
- },
241
- ],
242
- ]);
243
- for (const session of subscriptions) {
244
- policies.set(session.definition.route, {
245
- displayName: `${session.definition.displayName.replace(/\s*\([^)]*\)$/u, "")} (OAuth)`,
246
- isAuthenticated: async () => (await session.status()).authenticated,
247
- onAuthFailure: () => session.invalidateAccessToken(),
248
- });
249
- }
250
- if (codexFast !== undefined && codexSession !== undefined) {
251
- policies.set(CODEX_OAUTH_FAST_ROUTE, {
252
- displayName: "OpenAI Codex Fast requested (OAuth)",
253
- isAuthenticated: async () => (await codexSession.status()).authenticated,
254
- includeModel: (modelId) => codexFast.isEligible(modelId),
255
- onAuthFailure: () => codexSession.invalidateAccessToken(),
256
- });
257
- }
258
-
259
- const inner = new PiAiAdapter({
260
- profiles: () => {
261
- const profiles = new Map<string, ResolvedPiAiProviderProfile>();
262
- profiles.set(
263
- GROK_BUILD_ROUTE,
264
- profile(GROK_BUILD_ROUTE, "xAI Grok Build", grok.provider(), retryPolicy, grokBuildFingerprintHeaders()),
265
- );
266
- for (const session of subscriptions) {
267
- profiles.set(
268
- session.definition.nativeProviderId,
269
- profile(session.definition.nativeProviderId, session.definition.displayName, session.provider(), retryPolicy),
270
- );
271
- }
272
- if (codexFast !== undefined && codexSession !== undefined) {
273
- const wrapped = withCodexFastRouting(codexSession.provider(), {
274
- isEligible: (modelId) => codexFast.isEligible(modelId),
275
- profileProviderId: CODEX_OAUTH_FAST_ROUTE,
276
- nativeProviderId: CODEX_PI_PROVIDER,
277
- });
278
- // Models.streamSimple dispatches on model.provider. Advertise the Fast
279
- // profile id on the catalog so the wrapper runs, then restore native
280
- // identity inside withCodexFastRouting before the wire call.
281
- const fastProvider = {
282
- ...wrapped,
283
- getModels: () =>
284
- wrapped
285
- .getModels()
286
- .map((model) =>
287
- model.provider === CODEX_OAUTH_FAST_ROUTE ? model : { ...model, provider: CODEX_OAUTH_FAST_ROUTE },
288
- ),
289
- };
290
- profiles.set(
291
- CODEX_OAUTH_FAST_ROUTE,
292
- profile(
293
- CODEX_OAUTH_FAST_ROUTE,
294
- "OpenAI Codex Fast requested",
295
- fastProvider as unknown as ResolvedPiAiProviderProfile["piProvider"],
296
- retryPolicy,
297
- ),
298
- );
299
- }
300
- return profiles;
301
- },
302
- resolveApiKey: async (provider) => {
303
- if (provider === GROK_BUILD_ROUTE) {
304
- return resolveOAuthToken("Grok Build", async () => {
305
- const auth = await grok.models.getAuth(XAI_PI_PROVIDER, { minOAuthValidityMs: MIN_OAUTH_VALIDITY_MS });
306
- return auth?.auth.apiKey;
307
- });
308
- }
309
- const session =
310
- provider === CODEX_OAUTH_FAST_ROUTE ? byNativeId.get(CODEX_PI_PROVIDER) : byNativeId.get(provider);
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,
316
- });
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
- }
325
-
326
- /**
327
- * Resolve an OAuth access token for one route, translating a failed refresh
328
- * (revoked refresh token, dead grant) into MISSING_CREDENTIAL so the failure
329
- * is not retried and the user is told to sign in again rather than shown a
330
- * bare upstream 401.
331
- */
332
- async function resolveOAuthToken(
333
- displayName: string,
334
- getAccessToken: () => Promise<string | undefined>,
335
- ): Promise<string> {
336
- let accessToken: string | undefined;
337
- try {
338
- accessToken = await getAccessToken();
339
- } catch (error) {
340
- throw new LlmError(
341
- `${displayName} could not refresh its sign-in (${safeMessage(error)}).` +
342
- " Open Settings → Coding OAuth and sign in again.",
343
- "MISSING_CREDENTIAL",
344
- );
345
- }
346
- if (accessToken === undefined || accessToken.length === 0) return missingCredential(displayName);
347
- return accessToken;
348
- }
@@ -1,147 +0,0 @@
1
- /**
2
- * Harness route aliases over a native-id PiAiAdapter.
3
- * @module dsh-coding-subscription-oauth/alias-adapter
4
- */
5
-
6
- import type {
7
- GenerateOptions,
8
- LlmModelInfo,
9
- LlmProviderInfo,
10
- LlmResolvedModelInfo,
11
- ResolvedRetryPolicy,
12
- StreamChunk,
13
- } from "@deepseek-ai/dsh-llm";
14
- import { LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
15
- import { remapXaiCapacityFailure } from "./grok-errors.ts";
16
- import { remapAuthFailureIfContextOverflow } from "./kimi-errors.ts";
17
-
18
- export interface AliasLlmRoutePolicy {
19
- /** User-facing provider name shown above models in the model selector. */
20
- displayName?: string;
21
- /** Return false to hide every model for this route from discovery. */
22
- isAuthenticated?: () => Promise<boolean>;
23
- /**
24
- * After authentication, keep only models this predicate accepts.
25
- * Used by opt-in routes (e.g. Codex Fast) so ineligible ids stay hidden
26
- * without hiding the whole authenticated catalog.
27
- */
28
- includeModel?: (modelId: string) => boolean;
29
- /**
30
- * Called once when a stream for this route finishes with an AUTH failure
31
- * (upstream rejected a locally-valid token). Implementations backdate the
32
- * stored credential's expiry so the retried step refreshes before reuse.
33
- * Awaiting it here keeps invalidation ordered before the retry executor
34
- * reruns the step; failures are swallowed so the original AUTH surfaces.
35
- */
36
- onAuthFailure?: () => Promise<void>;
37
- }
38
-
39
- export function normalizeReplayForRoute(
40
- message: GenerateOptions["messages"][number],
41
- route: string,
42
- nativeReplayProvider: string,
43
- ): GenerateOptions["messages"][number] {
44
- if (message.role !== "assistant" || message.source.kind !== "model") return message;
45
- if (message.source.provider !== route) {
46
- if (message.source.replayState === undefined) return message;
47
- const { replayState: _foreignReplay, ...source } = message.source;
48
- return { ...message, source };
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 } };
54
- }
55
-
56
- /**
57
- * Keeps pi-ai model.provider identities native while exposing collision-free
58
- * Harness route names. Every public operation translates exactly once.
59
- */
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,
66
- ) {
67
- super();
68
- }
69
-
70
- private nativeProvider(route: string): string {
71
- const provider = this.aliases.get(route);
72
- if (provider === undefined) throw new LlmError(`OAuth adapter does not own provider "${route}"`, "NO_ADAPTER");
73
- return provider;
74
- }
75
-
76
- providerInfo(provider: string): LlmProviderInfo {
77
- const native = this.nativeProvider(provider);
78
- const info = this.inner.providerInfo(native);
79
- const displayName = this.policies.get(provider)?.displayName;
80
- return { ...info, id: provider, ...(displayName === undefined ? {} : { name: displayName }) };
81
- }
82
-
83
- providerRetryPolicy(provider: string): ResolvedRetryPolicy | undefined {
84
- return this.inner.providerRetryPolicy(this.nativeProvider(provider));
85
- }
86
-
87
- async listModels(provider: string): Promise<readonly LlmModelInfo[]> {
88
- const native = this.nativeProvider(provider);
89
- const isAuthenticated = this.policies.get(provider)?.isAuthenticated;
90
- if (isAuthenticated !== undefined) {
91
- try {
92
- if (!(await isAuthenticated())) return [];
93
- } catch {
94
- // A corrupt/unreadable credential must hide models instead of breaking
95
- // the entire model picker or presenting a route that cannot be used.
96
- return [];
97
- }
98
- }
99
- const listed = (await this.inner.listModels(native)).map((model) => ({ ...model, provider }));
100
- const includeModel = this.policies.get(provider)?.includeModel;
101
- return includeModel === undefined ? listed : listed.filter((model) => includeModel(model.id));
102
- }
103
-
104
- async resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo> {
105
- const native = this.nativeProvider(provider);
106
- return { ...(await this.inner.resolveModel(native, model, signal)), provider };
107
- }
108
-
109
- async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
110
- const route = options.provider;
111
- const native = this.nativeProvider(route);
112
- const messages = options.messages.map((message) =>
113
- normalizeReplayForRoute(message, route, this.replayProviders.get(route) ?? native),
114
- );
115
- let authFailureNotified = false;
116
- for await (const raw of this.inner.stream({ ...options, provider: native, messages })) {
117
- const chunk =
118
- raw.type === "finish" && raw.reason.kind === "error"
119
- ? {
120
- ...raw,
121
- reason: {
122
- ...raw.reason,
123
- failure: remapXaiCapacityFailure(remapAuthFailureIfContextOverflow(raw.reason.failure)),
124
- },
125
- }
126
- : raw;
127
- if (
128
- !authFailureNotified &&
129
- chunk.type === "finish" &&
130
- chunk.reason.kind === "error" &&
131
- chunk.reason.failure.code === "AUTH"
132
- ) {
133
- authFailureNotified = true;
134
- const onAuthFailure = this.policies.get(route)?.onAuthFailure;
135
- if (onAuthFailure !== undefined) {
136
- try {
137
- await onAuthFailure();
138
- } catch {
139
- // Invalidation is best-effort: the original AUTH failure must
140
- // surface unchanged even when the credential store is unreadable.
141
- }
142
- }
143
- }
144
- yield chunk;
145
- }
146
- }
147
- }