dsh-coding-subscription-oauth 0.5.8 → 0.6.2

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 (148) hide show
  1. package/CHANGELOG.md +217 -188
  2. package/CONTRIBUTING.md +120 -120
  3. package/INSTALL.md +241 -209
  4. package/LICENSE +19 -19
  5. package/NOTICE +11 -11
  6. package/README.de.md +298 -294
  7. package/README.es.md +299 -295
  8. package/README.fr.md +299 -295
  9. package/README.ja.md +299 -295
  10. package/README.ko.md +299 -295
  11. package/README.md +313 -307
  12. package/README.pt-BR.md +299 -295
  13. package/README.ru.md +299 -295
  14. package/README.zh-CN.md +311 -305
  15. package/compatibility/dsh-bom.json +30 -0
  16. package/cordis.patch.yml +13 -13
  17. package/docs/00-project-rules.md +195 -195
  18. package/docs/02-architecture.md +134 -129
  19. package/docs/02-architecture.zh-CN.md +134 -129
  20. package/lib/adapter.d.ts.map +1 -1
  21. package/lib/alias-adapter.d.ts +3 -1
  22. package/lib/alias-adapter.d.ts.map +1 -1
  23. package/lib/auth-routes.d.ts +8 -15
  24. package/lib/auth-routes.d.ts.map +1 -1
  25. package/lib/bin.js +746 -1402
  26. package/lib/bin.js.map +4 -4
  27. package/lib/capability-routes.d.ts +3 -3
  28. package/lib/capability-routes.d.ts.map +1 -1
  29. package/lib/capability-settings.d.ts +5 -4
  30. package/lib/capability-settings.d.ts.map +1 -1
  31. package/lib/client.js +27 -8
  32. package/lib/client.js.map +4 -4
  33. package/lib/compatibility.d.ts +46 -0
  34. package/lib/compatibility.d.ts.map +1 -0
  35. package/lib/dsh-host-adapter.d.ts +18 -0
  36. package/lib/dsh-host-adapter.d.ts.map +1 -0
  37. package/lib/gateway-auth.d.ts +1 -2
  38. package/lib/gateway-auth.d.ts.map +1 -1
  39. package/lib/gateway-routes.d.ts +3 -4
  40. package/lib/gateway-routes.d.ts.map +1 -1
  41. package/lib/gateway.d.ts +4 -0
  42. package/lib/gateway.d.ts.map +1 -1
  43. package/lib/ids.d.ts +3 -32
  44. package/lib/ids.d.ts.map +1 -1
  45. package/lib/imagine-routes.d.ts +2 -0
  46. package/lib/imagine-routes.d.ts.map +1 -1
  47. package/lib/index.d.ts +19 -4
  48. package/lib/index.d.ts.map +1 -1
  49. package/lib/index.js +26627 -1047
  50. package/lib/index.js.map +4 -4
  51. package/lib/invariant.js.map +1 -1
  52. package/lib/oauth-import-routes.d.ts +3 -4
  53. package/lib/oauth-import-routes.d.ts.map +1 -1
  54. package/lib/proxy.d.ts +3 -16
  55. package/lib/proxy.d.ts.map +1 -1
  56. package/lib/web-origin.d.ts +28 -4
  57. package/lib/web-origin.d.ts.map +1 -1
  58. package/lib/web-routes.d.ts.map +1 -1
  59. package/media/en/settings_accounts.png +0 -0
  60. package/media/en/settings_capabilities.png +0 -0
  61. package/media/en/settings_gateway.png +0 -0
  62. package/media/settings_accounts.png +0 -0
  63. package/media/settings_capabilities.png +0 -0
  64. package/media/settings_gateway.png +0 -0
  65. package/media/settings_overview.png +0 -0
  66. package/media/zh-CN/settings_accounts.png +0 -0
  67. package/media/zh-CN/settings_capabilities.png +0 -0
  68. package/media/zh-CN/settings_gateway.png +0 -0
  69. package/package.json +211 -174
  70. package/patches/dsh-agy@0.1.2.patch +25 -25
  71. package/scripts/release.mjs +186 -170
  72. package/scripts/smoke-deployed-routes.mjs +146 -146
  73. package/scripts/verify-deployed-catalog.mjs +87 -87
  74. package/src/adapter.ts +329 -268
  75. package/src/alias-adapter.ts +141 -147
  76. package/src/auth-routes.ts +921 -871
  77. package/src/auth.ts +67 -67
  78. package/src/bin.ts +350 -350
  79. package/src/capability-routes.ts +279 -275
  80. package/src/capability-runtime.ts +313 -313
  81. package/src/capability-settings.ts +658 -657
  82. package/src/capability-tools.ts +666 -666
  83. package/src/catalog.ts +271 -271
  84. package/src/client/GrokBuildSettings.tsx +770 -718
  85. package/src/client/api.ts +88 -88
  86. package/src/client/components/AboutTab.tsx +30 -30
  87. package/src/client/components/AccountsTab.tsx +241 -224
  88. package/src/client/components/Badge.tsx +33 -33
  89. package/src/client/components/CapabilitiesTab.tsx +265 -227
  90. package/src/client/components/CliPullPreview.tsx +116 -107
  91. package/src/client/components/CopyButton.tsx +57 -57
  92. package/src/client/components/GatewayTab.tsx +469 -409
  93. package/src/client/components/NoticeBanner.tsx +46 -46
  94. package/src/client/components/ProgressBar.tsx +53 -53
  95. package/src/client/components/ProviderCard.tsx +606 -502
  96. package/src/client/components/SettingsTabs.tsx +75 -75
  97. package/src/client/components/ToggleSwitch.tsx +71 -62
  98. package/src/client/constants.ts +224 -206
  99. package/src/client/display.ts +61 -61
  100. package/src/client/dshClientAdapter.ts +127 -0
  101. package/src/client/gatewaySnippets.ts +37 -36
  102. package/src/client/index.tsx +156 -37
  103. package/src/client/locales.ts +535 -491
  104. package/src/client/microStyles.ts +52 -33
  105. package/src/client/parsers.ts +396 -386
  106. package/src/client/styles.ts +325 -307
  107. package/src/client/types.ts +197 -185
  108. package/src/codex-http.ts +447 -447
  109. package/src/codex-images.ts +485 -485
  110. package/src/codex-model-capabilities.ts +320 -320
  111. package/src/codex-search.ts +245 -245
  112. package/src/codex-usage.ts +263 -263
  113. package/src/compatibility.ts +55 -0
  114. package/src/dsh-host-adapter.ts +173 -0
  115. package/src/gateway-anthropic-messages.ts +84 -84
  116. package/src/gateway-auth.ts +102 -100
  117. package/src/gateway-backend.ts +274 -274
  118. package/src/gateway-body.ts +49 -49
  119. package/src/gateway-config.ts +76 -76
  120. package/src/gateway-http.ts +104 -104
  121. package/src/gateway-openai-chat.ts +124 -124
  122. package/src/gateway-openai-responses.ts +53 -53
  123. package/src/gateway-parse.ts +224 -224
  124. package/src/gateway-protocol.ts +52 -52
  125. package/src/gateway-routes.ts +158 -152
  126. package/src/gateway.ts +258 -242
  127. package/src/grok-errors.ts +24 -24
  128. package/src/grok-imagine.ts +1627 -1627
  129. package/src/grok-import.ts +151 -151
  130. package/src/http-json.ts +82 -82
  131. package/src/ids.ts +59 -45
  132. package/src/imagine-routes.ts +463 -461
  133. package/src/index.ts +729 -592
  134. package/src/invariant.ts +17 -17
  135. package/src/kimi-errors.ts +26 -26
  136. package/src/media-store.ts +927 -927
  137. package/src/oauth-import-routes.ts +324 -314
  138. package/src/oauth-providers.ts +152 -152
  139. package/src/oauth-session.ts +183 -183
  140. package/src/oauth-sources.ts +1104 -1104
  141. package/src/oauth.ts +620 -620
  142. package/src/provider.ts +128 -128
  143. package/src/proxy.ts +11 -99
  144. package/src/redact.ts +72 -72
  145. package/src/session.ts +218 -218
  146. package/src/store.ts +217 -217
  147. package/src/web-origin.ts +296 -60
  148. package/src/web-routes.ts +38 -75
package/src/adapter.ts CHANGED
@@ -1,287 +1,348 @@
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 { 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";
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
28
  import type { GrokBuildSession } from "./session.ts";
29
29
 
30
- /** Prefer grok-4.6 when the current (live or baseline) list has it. */
31
- export function preferredGrokBuildModel(models: readonly { id: string }[] = grokBuildBaselineModels()): string {
32
- return preferredGrokBuildModelFrom(models.length === 0 ? [{ id: DEFAULT_GROK_BUILD_MODEL }] : models);
33
- }
30
+ type PiAiAuthInjection = PiAiAdapterOptions["auth"];
31
+ type PiAiCredentialStore = PiAiAuthInjection["credentials"];
34
32
 
35
- function missingCredential(name: string): never {
36
- throw new LlmError(
37
- `${name} is not signed in. Open Settings → Coding OAuth and sign in with your subscription.`,
38
- "MISSING_CREDENTIAL",
39
- );
40
- }
41
-
42
- /**
43
- * Minimum remaining validity demanded of an exported OAuth access token.
44
- * pi-ai 0.84+ already refreshes five minutes before the stored expiry; this
45
- * explicit floor documents the plugin contract and hard-fails a refresh that
46
- * returns an even-shorter-lived token instead of handing it to a request.
47
- */
48
- const MIN_OAUTH_VALIDITY_MS = 60_000;
33
+ const REQUEST_IMAGE_POLICY = {
34
+ maxRequestImageBytes: 20 * 1024 * 1024,
35
+ requestImagePixelBudget: 2048 * 2048,
36
+ requestImageMaxBytes: 1024 * 1024,
37
+ } as const;
49
38
 
50
39
  /**
51
- * Provider retry policy for the coding-subscription routes. The harness
52
- * default retryable set (EMPTY_RESPONSE/RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT)
53
- * deliberately excludes AUTH, so an upstream 401 e.g. an access token the
54
- * server revoked before its local expiry used to kill the turn outright.
55
- * AUTH is added here because {@link AliasLlmAdapter} invalidates the stored
56
- * credential on every AUTH finish, so the retried step refreshes first and
57
- * does not repeat the same rejected token. Quota exhaustion stays outside the
58
- * set: retrying a billing-limit 403 cannot succeed and only delays the real
59
- * message. Genuine credential death is converted to MISSING_CREDENTIAL (not
60
- * retryable) by the resolver below, so it cannot loop either.
61
- *
62
- * Five stacked exponential delays (5s → 10s → 20s → 40s → 80s, ~155s total)
63
- * pair with the xAI capacity remap in {@link AliasLlmAdapter}: "at capacity"
64
- * finish errors become RATE_LIMIT so they enter this policy instead of failing
65
- * as PI_AI_ERROR.
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.
66
44
  */
67
- const CODING_OAUTH_RETRY_POLICY = {
68
- mode: "normal" as const,
69
- maxRetries: 5,
70
- retryableCodes: ["EMPTY_RESPONSE", "RATE_LIMIT", "SERVER", "TIMEOUT", "TRANSPORT", "AUTH"],
71
- backoff: { initialDelayMs: 5_000, maxDelayMs: 80_000, jitterRatio: 0.1 },
72
- };
73
-
74
- function profile(
75
- provider: string,
76
- displayName: string,
77
- piProvider: ResolvedPiAiProviderProfile["piProvider"],
78
- retryPolicy?: RetryPolicyConfig | undefined,
79
- headers?: Record<string, string> | undefined,
80
- ): ResolvedPiAiProviderProfile {
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);
81
51
  return {
82
- provider,
83
- displayName,
84
- streamIdleTimeoutMs: GROK_BUILD_STREAM_IDLE_TIMEOUT_MS,
85
- retryPolicy: resolveRetryPolicy(
86
- retryPolicy ?? CODING_OAUTH_RETRY_POLICY,
87
- "dsh-coding-subscription-oauth retryPolicy",
88
- ),
89
- configuredMaxTokens: new Map(),
90
- ...(headers === undefined ? {} : { headers }),
91
- piProvider,
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
+ },
92
80
  };
93
81
  }
94
-
95
- /** Existing Grok-only constructor retained for public API compatibility. */
96
- export function createGrokBuildAdapter(
97
- session: GrokBuildSession,
98
- resolveAttachments: () => AttachmentStore | undefined,
99
- ): PiAiAdapter {
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 {
100
154
  return new PiAiAdapter({
101
- profiles: () =>
102
- new Map<string, ResolvedPiAiProviderProfile>([
103
- [
104
- GROK_BUILD_ROUTE,
105
- profile(GROK_BUILD_ROUTE, "xAI Grok Build", session.provider(), undefined, grokBuildFingerprintHeaders()),
106
- ],
107
- ]),
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
+ ]),
108
162
  resolveApiKey: async () =>
109
- resolveOAuthToken("Grok Build", async () => {
110
- const auth = await session.models.getAuth(XAI_PI_PROVIDER, { minOAuthValidityMs: MIN_OAUTH_VALIDITY_MS });
111
- 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;
112
166
  }),
167
+ auth: oauthAuthInjection(session, []),
113
168
  resolveAttachments,
114
- });
115
- }
116
-
117
- /** Opt-in Codex Fast wiring; ordinary `codex-oauth` is unchanged when this is omitted. */
118
- export interface CodingOAuthAdapterOptions {
119
- retryPolicy?: RetryPolicyConfig;
120
- codexFast?: { isEligible(modelId: string): boolean };
121
- }
122
-
123
- function isRetryPolicyConfig(value: object): value is RetryPolicyConfig {
124
- return "mode" in value;
125
- }
126
-
127
- function splitCodingOAuthAdapterArgs(
128
- fourth?: RetryPolicyConfig | CodingOAuthAdapterOptions,
129
- fifth?: CodingOAuthAdapterOptions,
130
- ): CodingOAuthAdapterOptions {
131
- if (fifth !== undefined) {
132
- return {
133
- ...(fourth !== undefined && isRetryPolicyConfig(fourth) ? { retryPolicy: fourth } : {}),
134
- ...fifth,
135
- };
136
- }
137
- if (fourth === undefined) return {};
138
- if (isRetryPolicyConfig(fourth)) return { retryPolicy: fourth };
139
- return {
140
- ...(fourth.retryPolicy === undefined ? {} : { retryPolicy: fourth.retryPolicy }),
141
- ...(fourth.codexFast === undefined ? {} : { codexFast: fourth.codexFast }),
142
- };
143
- }
144
-
145
- /** Create the four-route OAuth adapter while preserving each pi-ai native id. */
146
- export function createCodingOAuthAdapter(
147
- grok: GrokBuildSession,
148
- subscriptions: readonly OAuthProviderSession[],
149
- resolveAttachments: () => AttachmentStore | undefined,
150
- retryPolicy?: RetryPolicyConfig,
151
- options?: CodingOAuthAdapterOptions,
152
- ): LlmAdapter;
153
- export function createCodingOAuthAdapter(
154
- grok: GrokBuildSession,
155
- subscriptions: readonly OAuthProviderSession[],
156
- resolveAttachments: () => AttachmentStore | undefined,
157
- options?: CodingOAuthAdapterOptions,
158
- ): LlmAdapter;
159
- export function createCodingOAuthAdapter(
160
- grok: GrokBuildSession,
161
- subscriptions: readonly OAuthProviderSession[],
162
- resolveAttachments: () => AttachmentStore | undefined,
163
- retryPolicyOrOptions?: RetryPolicyConfig | CodingOAuthAdapterOptions,
164
- options?: CodingOAuthAdapterOptions,
165
- ): LlmAdapter {
166
- const { retryPolicy, codexFast } = splitCodingOAuthAdapterArgs(retryPolicyOrOptions, options);
167
- const byNativeId = new Map(subscriptions.map((session) => [session.definition.nativeProviderId, session]));
168
- const codexSession = byNativeId.get(CODEX_PI_PROVIDER);
169
- const aliases = new Map<string, string>([
170
- [GROK_BUILD_ROUTE, GROK_BUILD_ROUTE],
171
- [CODEX_OAUTH_ROUTE, CODEX_PI_PROVIDER],
172
- [KIMI_CODE_OAUTH_ROUTE, KIMI_PI_PROVIDER],
173
- [CLAUDE_CODE_OAUTH_ROUTE, CLAUDE_PI_PROVIDER],
174
- ]);
175
- if (codexFast !== undefined && codexSession !== undefined) {
176
- aliases.set(CODEX_OAUTH_FAST_ROUTE, CODEX_OAUTH_FAST_ROUTE);
177
- }
178
- const policies = new Map<string, AliasLlmRoutePolicy>([
179
- [
180
- GROK_BUILD_ROUTE,
181
- {
182
- displayName: "xAI Grok Build (OAuth)",
183
- isAuthenticated: async () => (await grok.store.read(XAI_PI_PROVIDER))?.type === "oauth",
184
- onAuthFailure: () => grok.invalidateAccessToken(),
185
- },
186
- ],
187
- ]);
188
- for (const session of subscriptions) {
189
- policies.set(session.definition.route, {
190
- displayName: `${session.definition.displayName.replace(/\s*\([^)]*\)$/u, "")} (OAuth)`,
191
- isAuthenticated: async () => (await session.status()).authenticated,
192
- onAuthFailure: () => session.invalidateAccessToken(),
193
- });
194
- }
195
- if (codexFast !== undefined && codexSession !== undefined) {
196
- policies.set(CODEX_OAUTH_FAST_ROUTE, {
197
- displayName: "OpenAI Codex Fast requested (OAuth)",
198
- isAuthenticated: async () => (await codexSession.status()).authenticated,
199
- includeModel: (modelId) => codexFast.isEligible(modelId),
200
- onAuthFailure: () => codexSession.invalidateAccessToken(),
201
- });
202
- }
203
-
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
+
204
259
  const inner = new PiAiAdapter({
205
- profiles: () => {
206
- const profiles = new Map<string, ResolvedPiAiProviderProfile>();
207
- profiles.set(
208
- GROK_BUILD_ROUTE,
209
- profile(GROK_BUILD_ROUTE, "xAI Grok Build", grok.provider(), retryPolicy, grokBuildFingerprintHeaders()),
210
- );
211
- for (const session of subscriptions) {
212
- profiles.set(
213
- session.definition.nativeProviderId,
214
- profile(session.definition.nativeProviderId, session.definition.displayName, session.provider(), retryPolicy),
215
- );
216
- }
217
- if (codexFast !== undefined && codexSession !== undefined) {
218
- const wrapped = withCodexFastRouting(codexSession.provider(), {
219
- isEligible: (modelId) => codexFast.isEligible(modelId),
220
- profileProviderId: CODEX_OAUTH_FAST_ROUTE,
221
- nativeProviderId: CODEX_PI_PROVIDER,
222
- });
223
- // Models.streamSimple dispatches on model.provider. Advertise the Fast
224
- // profile id on the catalog so the wrapper runs, then restore native
225
- // identity inside withCodexFastRouting before the wire call.
226
- const fastProvider = {
227
- ...wrapped,
228
- getModels: () =>
229
- wrapped
230
- .getModels()
231
- .map((model) =>
232
- model.provider === CODEX_OAUTH_FAST_ROUTE ? model : { ...model, provider: CODEX_OAUTH_FAST_ROUTE },
233
- ),
234
- };
235
- profiles.set(
236
- CODEX_OAUTH_FAST_ROUTE,
237
- profile(
238
- CODEX_OAUTH_FAST_ROUTE,
239
- "OpenAI Codex Fast requested",
240
- fastProvider as unknown as ResolvedPiAiProviderProfile["piProvider"],
241
- retryPolicy,
242
- ),
243
- );
244
- }
245
- return profiles;
246
- },
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
+ },
247
302
  resolveApiKey: async (provider) => {
248
- if (provider === GROK_BUILD_ROUTE) {
249
- return resolveOAuthToken("Grok Build", async () => {
250
- const auth = await grok.models.getAuth(XAI_PI_PROVIDER, { minOAuthValidityMs: MIN_OAUTH_VALIDITY_MS });
251
- return auth?.auth.apiKey;
252
- });
253
- }
254
- const session =
255
- provider === CODEX_OAUTH_FAST_ROUTE ? byNativeId.get(CODEX_PI_PROVIDER) : byNativeId.get(provider);
256
- 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");
257
312
  return resolveOAuthToken(session.definition.displayName, () => session.resolveAccessToken());
258
313
  },
314
+ auth: oauthAuthInjection(grok, subscriptions),
259
315
  resolveAttachments,
260
- });
261
-
262
- return new AliasLlmAdapter(inner, aliases, policies);
263
- }
264
-
265
- /**
266
- * Resolve an OAuth access token for one route, translating a failed refresh
267
- * (revoked refresh token, dead grant) into MISSING_CREDENTIAL so the failure
268
- * is not retried and the user is told to sign in again rather than shown a
269
- * bare upstream 401.
270
- */
271
- async function resolveOAuthToken(
272
- displayName: string,
273
- getAccessToken: () => Promise<string | undefined>,
274
- ): Promise<string> {
275
- let accessToken: string | undefined;
276
- try {
277
- accessToken = await getAccessToken();
278
- } catch (error) {
279
- throw new LlmError(
280
- `${displayName} could not refresh its sign-in (${safeMessage(error)}).` +
281
- " Open Settings → Coding OAuth and sign in again.",
282
- "MISSING_CREDENTIAL",
283
- );
284
- }
285
- if (accessToken === undefined || accessToken.length === 0) return missingCredential(displayName);
286
- return accessToken;
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);
287
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
+ }