dsh-coding-subscription-oauth 0.6.2 → 0.6.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.
Files changed (121) hide show
  1. package/CHANGELOG.md +222 -206
  2. package/CONTRIBUTING.md +129 -120
  3. package/INSTALL.md +218 -218
  4. package/LICENSE +19 -19
  5. package/NOTICE +11 -11
  6. package/README.de.md +289 -289
  7. package/README.es.md +290 -290
  8. package/README.fr.md +290 -290
  9. package/README.ja.md +290 -290
  10. package/README.ko.md +290 -290
  11. package/README.md +304 -304
  12. package/README.pt-BR.md +290 -290
  13. package/README.ru.md +290 -290
  14. package/README.zh-CN.md +302 -302
  15. package/compatibility/dsh-bom.json +0 -0
  16. package/cordis.patch.yml +13 -13
  17. package/docs/00-project-rules.md +212 -195
  18. package/docs/02-architecture.md +130 -130
  19. package/docs/02-architecture.zh-CN.md +130 -130
  20. package/lib/bin.js.map +1 -1
  21. package/lib/capability-settings.d.ts +8 -2
  22. package/lib/capability-settings.d.ts.map +1 -1
  23. package/lib/capability-tools.d.ts +7 -2
  24. package/lib/capability-tools.d.ts.map +1 -1
  25. package/lib/client.js +3 -3
  26. package/lib/client.js.map +2 -2
  27. package/lib/codex-images.d.ts +8 -0
  28. package/lib/codex-images.d.ts.map +1 -1
  29. package/lib/index.js +32 -7
  30. package/lib/index.js.map +2 -2
  31. package/lib/invariant.js.map +1 -1
  32. package/media/en/settings_accounts.png +0 -0
  33. package/media/en/settings_capabilities.png +0 -0
  34. package/media/en/settings_gateway.png +0 -0
  35. package/media/settings_accounts.png +0 -0
  36. package/media/settings_capabilities.png +0 -0
  37. package/media/settings_gateway.png +0 -0
  38. package/media/settings_overview.png +0 -0
  39. package/media/zh-CN/settings_accounts.png +0 -0
  40. package/media/zh-CN/settings_capabilities.png +0 -0
  41. package/media/zh-CN/settings_gateway.png +0 -0
  42. package/package.json +148 -148
  43. package/patches/dsh-agy@0.1.2.patch +25 -25
  44. package/scripts/release.mjs +186 -186
  45. package/scripts/smoke-deployed-routes.mjs +146 -146
  46. package/scripts/verify-deployed-catalog.mjs +87 -87
  47. package/src/adapter.ts +273 -273
  48. package/src/alias-adapter.ts +130 -130
  49. package/src/auth-routes.ts +921 -921
  50. package/src/auth.ts +67 -67
  51. package/src/bin.ts +350 -350
  52. package/src/capability-routes.ts +279 -279
  53. package/src/capability-runtime.ts +314 -313
  54. package/src/capability-settings.ts +671 -658
  55. package/src/capability-tools.ts +685 -666
  56. package/src/catalog.ts +271 -271
  57. package/src/client/GrokBuildSettings.tsx +771 -770
  58. package/src/client/api.ts +88 -88
  59. package/src/client/components/AboutTab.tsx +30 -30
  60. package/src/client/components/AccountsTab.tsx +241 -241
  61. package/src/client/components/Badge.tsx +33 -33
  62. package/src/client/components/CapabilitiesTab.tsx +265 -265
  63. package/src/client/components/CliPullPreview.tsx +116 -116
  64. package/src/client/components/CopyButton.tsx +57 -57
  65. package/src/client/components/GatewayTab.tsx +469 -469
  66. package/src/client/components/NoticeBanner.tsx +46 -46
  67. package/src/client/components/ProgressBar.tsx +53 -53
  68. package/src/client/components/ProviderCard.tsx +606 -606
  69. package/src/client/components/SettingsTabs.tsx +75 -75
  70. package/src/client/components/ToggleSwitch.tsx +71 -71
  71. package/src/client/constants.ts +230 -224
  72. package/src/client/display.ts +61 -61
  73. package/src/client/dshClientAdapter.ts +127 -127
  74. package/src/client/gatewaySnippets.ts +37 -37
  75. package/src/client/index.tsx +156 -156
  76. package/src/client/locales.ts +540 -535
  77. package/src/client/microStyles.ts +52 -52
  78. package/src/client/parsers.ts +398 -396
  79. package/src/client/styles.ts +325 -325
  80. package/src/client/types.ts +199 -197
  81. package/src/codex-http.ts +447 -447
  82. package/src/codex-images.ts +503 -485
  83. package/src/codex-model-capabilities.ts +320 -320
  84. package/src/codex-search.ts +245 -245
  85. package/src/codex-usage.ts +263 -263
  86. package/src/compatibility.ts +41 -41
  87. package/src/dsh-host-adapter.ts +173 -173
  88. package/src/gateway-anthropic-messages.ts +84 -84
  89. package/src/gateway-auth.ts +102 -102
  90. package/src/gateway-backend.ts +274 -274
  91. package/src/gateway-body.ts +49 -49
  92. package/src/gateway-config.ts +76 -76
  93. package/src/gateway-http.ts +104 -104
  94. package/src/gateway-openai-chat.ts +124 -124
  95. package/src/gateway-openai-responses.ts +53 -53
  96. package/src/gateway-parse.ts +224 -224
  97. package/src/gateway-protocol.ts +52 -52
  98. package/src/gateway-routes.ts +158 -158
  99. package/src/gateway.ts +258 -258
  100. package/src/grok-errors.ts +24 -24
  101. package/src/grok-imagine.ts +1627 -1627
  102. package/src/grok-import.ts +151 -151
  103. package/src/http-json.ts +82 -82
  104. package/src/ids.ts +59 -59
  105. package/src/imagine-routes.ts +463 -463
  106. package/src/index.ts +709 -709
  107. package/src/invariant.ts +17 -17
  108. package/src/kimi-errors.ts +26 -26
  109. package/src/media-store.ts +927 -927
  110. package/src/oauth-import-routes.ts +324 -324
  111. package/src/oauth-providers.ts +152 -152
  112. package/src/oauth-session.ts +183 -183
  113. package/src/oauth-sources.ts +1104 -1104
  114. package/src/oauth.ts +620 -620
  115. package/src/provider.ts +128 -128
  116. package/src/proxy.ts +11 -11
  117. package/src/redact.ts +72 -72
  118. package/src/session.ts +218 -218
  119. package/src/store.ts +217 -217
  120. package/src/web-origin.ts +296 -296
  121. package/src/web-routes.ts +38 -38
package/src/adapter.ts CHANGED
@@ -1,30 +1,30 @@
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";
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
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";
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
28
  import type { GrokBuildSession } from "./session.ts";
29
29
 
30
30
  type PiAiAuthInjection = PiAiAdapterOptions["auth"];
@@ -79,242 +79,242 @@ function oauthAuthInjection(grok: GrokBuildSession, subscriptions: readonly OAut
79
79
  },
80
80
  };
81
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
- ),
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
142
  configuredMaxTokens: new Map(),
143
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 {
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
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
- ]),
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
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;
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
166
  }),
167
167
  auth: oauthAuthInjection(session, []),
168
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
-
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
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
- },
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
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");
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
312
  return resolveOAuthToken(session.definition.displayName, () => session.resolveAccessToken());
313
313
  },
314
314
  auth: oauthAuthInjection(grok, subscriptions),
315
315
  resolveAttachments,
316
- });
317
-
316
+ });
317
+
318
318
  // The Fast wrapper restores wire/replay model identity to openai-codex.
319
319
  // Keep the opaque envelope untouched and map public Fast source identity to
320
320
  // that native replay provider before PiAiAdapter validates it.
@@ -322,27 +322,27 @@ export function createCodingOAuthAdapter(
322
322
  replayProviders.set(CODEX_OAUTH_FAST_ROUTE, CODEX_PI_PROVIDER);
323
323
  return new AliasLlmAdapter(inner, aliases, policies, replayProviders);
324
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
- }
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
+ }