dsh-coding-subscription-oauth 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +258 -250
  2. package/CONTRIBUTING.md +129 -129
  3. package/INSTALL.md +256 -255
  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 +320 -320
  12. package/README.pt-BR.md +304 -304
  13. package/README.ru.md +304 -304
  14. package/README.zh-CN.md +318 -318
  15. package/compatibility/dsh-bom.json +30 -30
  16. package/cordis.patch.yml +13 -13
  17. package/docs/00-project-rules.md +212 -212
  18. package/docs/02-architecture.md +138 -138
  19. package/docs/02-architecture.zh-CN.md +138 -138
  20. package/lib/bin.js +1333 -569
  21. package/lib/bin.js.map +4 -4
  22. package/lib/client.js +1 -1
  23. package/lib/client.js.map +2 -2
  24. package/lib/index.js +689 -25492
  25. package/lib/index.js.map +4 -4
  26. package/lib/invariant.js.map +1 -1
  27. package/media/en/settings_accounts.png +0 -0
  28. package/media/en/settings_capabilities.png +0 -0
  29. package/media/en/settings_gateway.png +0 -0
  30. package/media/settings_accounts.png +0 -0
  31. package/media/settings_capabilities.png +0 -0
  32. package/media/settings_gateway.png +0 -0
  33. package/media/settings_overview.png +0 -0
  34. package/media/zh-CN/settings_accounts.png +0 -0
  35. package/media/zh-CN/settings_capabilities.png +0 -0
  36. package/media/zh-CN/settings_gateway.png +0 -0
  37. package/package.json +223 -223
  38. package/patches/dsh-agy@0.1.2.patch +25 -25
  39. package/scripts/release.mjs +186 -186
  40. package/scripts/smoke-deployed-routes.mjs +146 -146
  41. package/scripts/verify-deployed-catalog.mjs +87 -87
  42. package/src/adapter.ts +348 -348
  43. package/src/alias-adapter.ts +147 -147
  44. package/src/auth-routes.ts +921 -921
  45. package/src/auth.ts +67 -67
  46. package/src/bin.ts +350 -350
  47. package/src/capability-routes.ts +279 -279
  48. package/src/capability-runtime.ts +314 -314
  49. package/src/capability-settings.ts +671 -671
  50. package/src/capability-tools.ts +685 -685
  51. package/src/catalog.ts +271 -271
  52. package/src/client/GrokBuildSettings.tsx +771 -771
  53. package/src/client/api.ts +88 -88
  54. package/src/client/components/AboutTab.tsx +30 -30
  55. package/src/client/components/AccountsTab.tsx +241 -241
  56. package/src/client/components/Badge.tsx +33 -33
  57. package/src/client/components/CapabilitiesTab.tsx +265 -265
  58. package/src/client/components/CliPullPreview.tsx +116 -116
  59. package/src/client/components/CopyButton.tsx +57 -57
  60. package/src/client/components/GatewayTab.tsx +469 -469
  61. package/src/client/components/NoticeBanner.tsx +46 -46
  62. package/src/client/components/ProgressBar.tsx +53 -53
  63. package/src/client/components/ProviderCard.tsx +606 -606
  64. package/src/client/components/SettingsTabs.tsx +75 -75
  65. package/src/client/components/ToggleSwitch.tsx +71 -71
  66. package/src/client/constants.ts +230 -230
  67. package/src/client/display.ts +61 -61
  68. package/src/client/dshClientAdapter.ts +127 -127
  69. package/src/client/gatewaySnippets.ts +37 -37
  70. package/src/client/index.tsx +156 -156
  71. package/src/client/locales.ts +540 -540
  72. package/src/client/microStyles.ts +52 -52
  73. package/src/client/parsers.ts +398 -398
  74. package/src/client/styles.ts +325 -325
  75. package/src/client/types.ts +199 -199
  76. package/src/codex-http.ts +447 -447
  77. package/src/codex-images.ts +503 -503
  78. package/src/codex-model-capabilities.ts +320 -320
  79. package/src/codex-search.ts +245 -245
  80. package/src/codex-usage.ts +263 -263
  81. package/src/compatibility.ts +55 -55
  82. package/src/dsh-host-adapter.ts +173 -173
  83. package/src/gateway-anthropic-messages.ts +84 -84
  84. package/src/gateway-auth.ts +102 -102
  85. package/src/gateway-backend.ts +274 -274
  86. package/src/gateway-body.ts +49 -49
  87. package/src/gateway-config.ts +76 -76
  88. package/src/gateway-http.ts +104 -104
  89. package/src/gateway-openai-chat.ts +124 -124
  90. package/src/gateway-openai-responses.ts +53 -53
  91. package/src/gateway-parse.ts +224 -224
  92. package/src/gateway-protocol.ts +52 -52
  93. package/src/gateway-routes.ts +158 -158
  94. package/src/gateway.ts +258 -258
  95. package/src/grok-errors.ts +24 -24
  96. package/src/grok-imagine.ts +1627 -1627
  97. package/src/grok-import.ts +151 -151
  98. package/src/http-json.ts +82 -82
  99. package/src/ids.ts +59 -59
  100. package/src/imagine-routes.ts +463 -463
  101. package/src/index.ts +735 -735
  102. package/src/invariant.ts +17 -17
  103. package/src/kimi-errors.ts +26 -26
  104. package/src/media-store.ts +927 -927
  105. package/src/oauth-import-routes.ts +324 -324
  106. package/src/oauth-providers.ts +152 -152
  107. package/src/oauth-session.ts +183 -183
  108. package/src/oauth-sources.ts +1104 -1104
  109. package/src/oauth.ts +620 -620
  110. package/src/provider.ts +128 -128
  111. package/src/proxy.ts +11 -11
  112. package/src/redact.ts +72 -72
  113. package/src/session.ts +218 -218
  114. package/src/store.ts +217 -217
  115. package/src/web-origin.ts +296 -296
  116. package/src/web-routes.ts +38 -38
package/src/provider.ts CHANGED
@@ -1,128 +1,128 @@
1
- /**
2
- * Grok Build provider: a pi-ai provider pointed at the official Grok CLI
3
- * coding backend (`cli-chat-proxy.grok.com`) carrying the CLI fingerprint
4
- * headers the risk-control middleware requires.
5
- * @module dsh-coding-subscription-oauth/provider
6
- */
7
-
8
- import type { Api, Model, Provider, ThinkingLevelMap } from "@earendil-works/pi-ai";
9
- import { createProvider } from "@earendil-works/pi-ai";
10
- import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
11
- import { GROK_BUILD_ROUTE } from "./ids.ts";
12
-
13
- /**
14
- * Grok Build cannot disable reasoning (`reasoning_effort: "none"` is 400).
15
- * pi-ai treats an absent `xhigh`/`max` key as unsupported, so those levels
16
- * must be declared explicitly when the model offers them.
17
- */
18
- export function grokBuildReasoningMap(levels: readonly ("low" | "medium" | "high" | "xhigh")[]): ThinkingLevelMap {
19
- const offered = new Set(levels);
20
- return {
21
- off: null,
22
- minimal: null,
23
- low: offered.has("low") ? "low" : null,
24
- medium: offered.has("medium") ? "medium" : null,
25
- high: offered.has("high") ? "high" : null,
26
- xhigh: offered.has("xhigh") ? "xhigh" : null,
27
- max: null,
28
- };
29
- }
30
-
31
- /** Inference backend base URL (Responses API lives under `${baseUrl}/responses`). */
32
- export const GROK_BUILD_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
33
-
34
- /** Account model catalog endpoint fetched by the official CLI. */
35
- export const GROK_BUILD_MODELS_URL = `${GROK_BUILD_BASE_URL}/models-v2`;
36
-
37
- /**
38
- * Official Grok CLI version this plugin fingerprints as.
39
- * Track the `@xai-official/grok` npm release stream; make overridable via
40
- * GROK_BUILD_CLIENT_VERSION for urgent drift fixes without a release.
41
- */
42
- export const GROK_CLIENT_VERSION: string = process.env["GROK_BUILD_CLIENT_VERSION"] ?? "0.1.220";
43
-
44
- /**
45
- * Fingerprint headers required by the Grok Build middleware. Missing headers
46
- * are a known 403 trigger (codex-app-transfer field notes, 2026-07).
47
- */
48
- export function grokBuildFingerprintHeaders(): Record<string, string> {
49
- return {
50
- "X-XAI-Token-Auth": "xai-grok-cli",
51
- "x-grok-client-identifier": "grok-shell",
52
- "x-grok-client-version": GROK_CLIENT_VERSION,
53
- "User-Agent": `grok-shell/${GROK_CLIENT_VERSION}`,
54
- };
55
- }
56
-
57
- /** Static baseline catalog, used until a live `/models-v2` listing succeeds. */
58
- export function grokBuildBaselineModels(): Model<"openai-responses">[] {
59
- return [
60
- {
61
- id: "grok-4.5",
62
- name: "Grok 4.5",
63
- api: "openai-responses",
64
- provider: GROK_BUILD_ROUTE,
65
- baseUrl: GROK_BUILD_BASE_URL,
66
- reasoning: true,
67
- // Official xAI docs + live /models-v2: grok-4.5 is low/medium/high only.
68
- thinkingLevelMap: grokBuildReasoningMap(["low", "medium", "high"]),
69
- input: ["text", "image"],
70
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
71
- contextWindow: 500_000,
72
- maxTokens: 128_000,
73
- },
74
- {
75
- id: "grok-4.6",
76
- name: "Grok 4.6",
77
- api: "openai-responses",
78
- provider: GROK_BUILD_ROUTE,
79
- baseUrl: GROK_BUILD_BASE_URL,
80
- reasoning: true,
81
- // grok-4.6 and later advertise xhigh. pi-ai hides xhigh unless this key
82
- // is an explicit non-null mapping (absent !== supported).
83
- thinkingLevelMap: grokBuildReasoningMap(["low", "medium", "high", "xhigh"]),
84
- input: ["text", "image"],
85
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
86
- contextWindow: 500_000,
87
- maxTokens: 128_000,
88
- },
89
- {
90
- id: "grok-composer-2.5-fast",
91
- name: "Grok Composer 2.5 Fast",
92
- api: "openai-responses",
93
- provider: GROK_BUILD_ROUTE,
94
- baseUrl: GROK_BUILD_BASE_URL,
95
- reasoning: false,
96
- input: ["text"],
97
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
98
- contextWindow: 200_000,
99
- maxTokens: 64_000,
100
- },
101
- ];
102
- }
103
-
104
- /**
105
- * Build the Grok Build pi-ai provider. Auth is apiKey-shaped: the OAuth
106
- * access token is injected as the bearer key by the surrounding adapter
107
- * (`Models.getAuth` on the login provider performs refresh under the store
108
- * lock before the key ever reaches here).
109
- */
110
- export function grokBuildProvider(models: readonly Model<Api>[]): Provider {
111
- return createProvider({
112
- id: GROK_BUILD_ROUTE,
113
- name: "xAI Grok Build",
114
- baseUrl: GROK_BUILD_BASE_URL,
115
- headers: grokBuildFingerprintHeaders(),
116
- auth: {
117
- apiKey: {
118
- name: "Grok Build OAuth access token",
119
- async resolve({ credential }) {
120
- const apiKey = credential?.key;
121
- return apiKey === undefined || apiKey.length === 0 ? undefined : { auth: { apiKey }, source: "OAuth" };
122
- },
123
- },
124
- },
125
- models,
126
- api: { "openai-responses": openAIResponsesApi() },
127
- });
128
- }
1
+ /**
2
+ * Grok Build provider: a pi-ai provider pointed at the official Grok CLI
3
+ * coding backend (`cli-chat-proxy.grok.com`) carrying the CLI fingerprint
4
+ * headers the risk-control middleware requires.
5
+ * @module dsh-coding-subscription-oauth/provider
6
+ */
7
+
8
+ import type { Api, Model, Provider, ThinkingLevelMap } from "@earendil-works/pi-ai";
9
+ import { createProvider } from "@earendil-works/pi-ai";
10
+ import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
11
+ import { GROK_BUILD_ROUTE } from "./ids.ts";
12
+
13
+ /**
14
+ * Grok Build cannot disable reasoning (`reasoning_effort: "none"` is 400).
15
+ * pi-ai treats an absent `xhigh`/`max` key as unsupported, so those levels
16
+ * must be declared explicitly when the model offers them.
17
+ */
18
+ export function grokBuildReasoningMap(levels: readonly ("low" | "medium" | "high" | "xhigh")[]): ThinkingLevelMap {
19
+ const offered = new Set(levels);
20
+ return {
21
+ off: null,
22
+ minimal: null,
23
+ low: offered.has("low") ? "low" : null,
24
+ medium: offered.has("medium") ? "medium" : null,
25
+ high: offered.has("high") ? "high" : null,
26
+ xhigh: offered.has("xhigh") ? "xhigh" : null,
27
+ max: null,
28
+ };
29
+ }
30
+
31
+ /** Inference backend base URL (Responses API lives under `${baseUrl}/responses`). */
32
+ export const GROK_BUILD_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
33
+
34
+ /** Account model catalog endpoint fetched by the official CLI. */
35
+ export const GROK_BUILD_MODELS_URL = `${GROK_BUILD_BASE_URL}/models-v2`;
36
+
37
+ /**
38
+ * Official Grok CLI version this plugin fingerprints as.
39
+ * Track the `@xai-official/grok` npm release stream; make overridable via
40
+ * GROK_BUILD_CLIENT_VERSION for urgent drift fixes without a release.
41
+ */
42
+ export const GROK_CLIENT_VERSION: string = process.env["GROK_BUILD_CLIENT_VERSION"] ?? "0.1.220";
43
+
44
+ /**
45
+ * Fingerprint headers required by the Grok Build middleware. Missing headers
46
+ * are a known 403 trigger (codex-app-transfer field notes, 2026-07).
47
+ */
48
+ export function grokBuildFingerprintHeaders(): Record<string, string> {
49
+ return {
50
+ "X-XAI-Token-Auth": "xai-grok-cli",
51
+ "x-grok-client-identifier": "grok-shell",
52
+ "x-grok-client-version": GROK_CLIENT_VERSION,
53
+ "User-Agent": `grok-shell/${GROK_CLIENT_VERSION}`,
54
+ };
55
+ }
56
+
57
+ /** Static baseline catalog, used until a live `/models-v2` listing succeeds. */
58
+ export function grokBuildBaselineModels(): Model<"openai-responses">[] {
59
+ return [
60
+ {
61
+ id: "grok-4.5",
62
+ name: "Grok 4.5",
63
+ api: "openai-responses",
64
+ provider: GROK_BUILD_ROUTE,
65
+ baseUrl: GROK_BUILD_BASE_URL,
66
+ reasoning: true,
67
+ // Official xAI docs + live /models-v2: grok-4.5 is low/medium/high only.
68
+ thinkingLevelMap: grokBuildReasoningMap(["low", "medium", "high"]),
69
+ input: ["text", "image"],
70
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
71
+ contextWindow: 500_000,
72
+ maxTokens: 128_000,
73
+ },
74
+ {
75
+ id: "grok-4.6",
76
+ name: "Grok 4.6",
77
+ api: "openai-responses",
78
+ provider: GROK_BUILD_ROUTE,
79
+ baseUrl: GROK_BUILD_BASE_URL,
80
+ reasoning: true,
81
+ // grok-4.6 and later advertise xhigh. pi-ai hides xhigh unless this key
82
+ // is an explicit non-null mapping (absent !== supported).
83
+ thinkingLevelMap: grokBuildReasoningMap(["low", "medium", "high", "xhigh"]),
84
+ input: ["text", "image"],
85
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
86
+ contextWindow: 500_000,
87
+ maxTokens: 128_000,
88
+ },
89
+ {
90
+ id: "grok-composer-2.5-fast",
91
+ name: "Grok Composer 2.5 Fast",
92
+ api: "openai-responses",
93
+ provider: GROK_BUILD_ROUTE,
94
+ baseUrl: GROK_BUILD_BASE_URL,
95
+ reasoning: false,
96
+ input: ["text"],
97
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
98
+ contextWindow: 200_000,
99
+ maxTokens: 64_000,
100
+ },
101
+ ];
102
+ }
103
+
104
+ /**
105
+ * Build the Grok Build pi-ai provider. Auth is apiKey-shaped: the OAuth
106
+ * access token is injected as the bearer key by the surrounding adapter
107
+ * (`Models.getAuth` on the login provider performs refresh under the store
108
+ * lock before the key ever reaches here).
109
+ */
110
+ export function grokBuildProvider(models: readonly Model<Api>[]): Provider {
111
+ return createProvider({
112
+ id: GROK_BUILD_ROUTE,
113
+ name: "xAI Grok Build",
114
+ baseUrl: GROK_BUILD_BASE_URL,
115
+ headers: grokBuildFingerprintHeaders(),
116
+ auth: {
117
+ apiKey: {
118
+ name: "Grok Build OAuth access token",
119
+ async resolve({ credential }) {
120
+ const apiKey = credential?.key;
121
+ return apiKey === undefined || apiKey.length === 0 ? undefined : { auth: { apiKey }, source: "OAuth" };
122
+ },
123
+ },
124
+ },
125
+ models,
126
+ api: { "openai-responses": openAIResponsesApi() },
127
+ });
128
+ }
package/src/proxy.ts CHANGED
@@ -1,11 +1,11 @@
1
- /** Compatibility facade for the shared process-wide coding OAuth proxy. */
2
-
3
- export type { CodingOAuthProxyLease, CodingOAuthProxyOptions } from "dsh-coding-oauth-core";
4
- export {
5
- acquireCodingOAuthProxy,
6
- codingOAuthProxyInEffect,
7
- codingOAuthProxyUnreachableHint,
8
- ensureCodingOAuthProxy,
9
- ensureGrokBuildProxy,
10
- grokBuildProxyInEffect,
11
- } from "dsh-coding-oauth-core";
1
+ /** Compatibility facade for the shared process-wide coding OAuth proxy. */
2
+
3
+ export type { CodingOAuthProxyLease, CodingOAuthProxyOptions } from "dsh-coding-oauth-core";
4
+ export {
5
+ acquireCodingOAuthProxy,
6
+ codingOAuthProxyInEffect,
7
+ codingOAuthProxyUnreachableHint,
8
+ ensureCodingOAuthProxy,
9
+ ensureGrokBuildProxy,
10
+ grokBuildProxyInEffect,
11
+ } from "dsh-coding-oauth-core";
package/src/redact.ts CHANGED
@@ -1,72 +1,72 @@
1
- /** Remove token-like strings from an external OAuth diagnostic. */
2
-
3
- /**
4
- * An opaque credential value run. We require the surrounding label to be one of
5
- * the known credential field names AND the value to be at least this many
6
- * characters so that ordinary prose ("code is fun", "token missing") is left
7
- * alone. The minimum also keeps the regex from matching in code-style
8
- * strings like `code: a`.
9
- */
10
- const OPAQUE_TOKEN_MIN_LENGTH = 16;
11
-
12
- /** The opaque-value class: at least the minimum chars of URL-safe alphabet. */
13
- const OPAQUE_TOKEN_RUN = `[A-Za-z0-9_\\-.+]{${OPAQUE_TOKEN_MIN_LENGTH},}`;
14
-
15
- /** Known sensitive field names that may precede an opaque credential value. */
16
- const SENSITIVE_LABEL =
17
- "(?:token|bearer|code|user_code|access_token|refresh_token|id_token|api[_-]?key|secret|password|client[_-]?secret|authorization)";
18
-
19
- /**
20
- * Sweep additional opaque tokens that follow a known credential label. We
21
- * require the value to be long and URL-safe-shaped so that ordinary prose
22
- * ("code is fun", "token missing") is left alone. This complements the short
23
- * and structured sweep above — it catches longer opaque tokens that survive
24
- * the more specific regexes.
25
- */
26
- function redactOpaqueTokens(input: string): string {
27
- let output = input.replace(
28
- new RegExp(`(["']?${SENSITIVE_LABEL}["']?\\s*:\\s*["'])(${OPAQUE_TOKEN_RUN})(["'])`, "giu"),
29
- `$1[redacted]$3`,
30
- );
31
- output = output.replace(
32
- new RegExp(`\\b(${SENSITIVE_LABEL})=(${OPAQUE_TOKEN_RUN})(?=[&\\s"']|$)`, "giu"),
33
- `$1=[redacted]`,
34
- );
35
- output = output.replace(
36
- new RegExp(`\\b(${SENSITIVE_LABEL}\\s*:\\s*)(${OPAQUE_TOKEN_RUN})(?=[\\s"',}]|$)`, "giu"),
37
- `$1[redacted]`,
38
- );
39
- output = output.replace(new RegExp(`\\b(Authorization)\\s+(${OPAQUE_TOKEN_RUN})\\b`, "giu"), `$1 [redacted]`);
40
- return output;
41
- }
42
-
43
- /** Strip userinfo from a proxy URL so CLI/logs never print `user:pass@`. */
44
- export function redactProxyUrl(value: string): string {
45
- try {
46
- const parsed = new URL(value);
47
- if (parsed.username === "" && parsed.password === "") return value;
48
- parsed.username = "";
49
- parsed.password = "";
50
- const href = parsed.href;
51
- if (!value.endsWith("/") && href.endsWith("/") && parsed.pathname === "/") return href.slice(0, -1);
52
- return href;
53
- } catch {
54
- return value.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/iu, "$1");
55
- }
56
- }
57
-
58
- export function safeMessage(error: unknown): string {
59
- let text = (error instanceof Error ? error.message : String(error))
60
- .replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted token]")
61
- .replace(/\bsk-ant-oat[A-Za-z0-9_-]*\b/giu, "[redacted token]")
62
- .replace(/\bsk-[A-Za-z0-9_-]{16,}\b/gu, "[redacted token]")
63
- .replace(/\bxai-[A-Za-z0-9._-]{8,}\b/gu, "[redacted token]")
64
- .replace(/(\bBearer\s+)[^\s"',}]+/giu, "$1[redacted]")
65
- .replace(/(\b(?:code|user_code|token|id_token|refresh_token|access_token)=)[^&\s]+/giu, "$1[redacted]")
66
- .replace(/(["']?(?:code|user_code|id_token|refresh_token|access_token)["']?\s*:\s*["'])[^"']+/giu, "$1[redacted]");
67
- // Generic opaque sweep catches long values preceded by labels such as
68
- // client_secret, api_key, password, or Authorization. The minimum length
69
- // guards against ordinary prose and the case-insensitive pass handles repeats.
70
- text = redactOpaqueTokens(text);
71
- return text.slice(0, 1000);
72
- }
1
+ /** Remove token-like strings from an external OAuth diagnostic. */
2
+
3
+ /**
4
+ * An opaque credential value run. We require the surrounding label to be one of
5
+ * the known credential field names AND the value to be at least this many
6
+ * characters so that ordinary prose ("code is fun", "token missing") is left
7
+ * alone. The minimum also keeps the regex from matching in code-style
8
+ * strings like `code: a`.
9
+ */
10
+ const OPAQUE_TOKEN_MIN_LENGTH = 16;
11
+
12
+ /** The opaque-value class: at least the minimum chars of URL-safe alphabet. */
13
+ const OPAQUE_TOKEN_RUN = `[A-Za-z0-9_\\-.+]{${OPAQUE_TOKEN_MIN_LENGTH},}`;
14
+
15
+ /** Known sensitive field names that may precede an opaque credential value. */
16
+ const SENSITIVE_LABEL =
17
+ "(?:token|bearer|code|user_code|access_token|refresh_token|id_token|api[_-]?key|secret|password|client[_-]?secret|authorization)";
18
+
19
+ /**
20
+ * Sweep additional opaque tokens that follow a known credential label. We
21
+ * require the value to be long and URL-safe-shaped so that ordinary prose
22
+ * ("code is fun", "token missing") is left alone. This complements the short
23
+ * and structured sweep above — it catches longer opaque tokens that survive
24
+ * the more specific regexes.
25
+ */
26
+ function redactOpaqueTokens(input: string): string {
27
+ let output = input.replace(
28
+ new RegExp(`(["']?${SENSITIVE_LABEL}["']?\\s*:\\s*["'])(${OPAQUE_TOKEN_RUN})(["'])`, "giu"),
29
+ `$1[redacted]$3`,
30
+ );
31
+ output = output.replace(
32
+ new RegExp(`\\b(${SENSITIVE_LABEL})=(${OPAQUE_TOKEN_RUN})(?=[&\\s"']|$)`, "giu"),
33
+ `$1=[redacted]`,
34
+ );
35
+ output = output.replace(
36
+ new RegExp(`\\b(${SENSITIVE_LABEL}\\s*:\\s*)(${OPAQUE_TOKEN_RUN})(?=[\\s"',}]|$)`, "giu"),
37
+ `$1[redacted]`,
38
+ );
39
+ output = output.replace(new RegExp(`\\b(Authorization)\\s+(${OPAQUE_TOKEN_RUN})\\b`, "giu"), `$1 [redacted]`);
40
+ return output;
41
+ }
42
+
43
+ /** Strip userinfo from a proxy URL so CLI/logs never print `user:pass@`. */
44
+ export function redactProxyUrl(value: string): string {
45
+ try {
46
+ const parsed = new URL(value);
47
+ if (parsed.username === "" && parsed.password === "") return value;
48
+ parsed.username = "";
49
+ parsed.password = "";
50
+ const href = parsed.href;
51
+ if (!value.endsWith("/") && href.endsWith("/") && parsed.pathname === "/") return href.slice(0, -1);
52
+ return href;
53
+ } catch {
54
+ return value.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/iu, "$1");
55
+ }
56
+ }
57
+
58
+ export function safeMessage(error: unknown): string {
59
+ let text = (error instanceof Error ? error.message : String(error))
60
+ .replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted token]")
61
+ .replace(/\bsk-ant-oat[A-Za-z0-9_-]*\b/giu, "[redacted token]")
62
+ .replace(/\bsk-[A-Za-z0-9_-]{16,}\b/gu, "[redacted token]")
63
+ .replace(/\bxai-[A-Za-z0-9._-]{8,}\b/gu, "[redacted token]")
64
+ .replace(/(\bBearer\s+)[^\s"',}]+/giu, "$1[redacted]")
65
+ .replace(/(\b(?:code|user_code|token|id_token|refresh_token|access_token)=)[^&\s]+/giu, "$1[redacted]")
66
+ .replace(/(["']?(?:code|user_code|id_token|refresh_token|access_token)["']?\s*:\s*["'])[^"']+/giu, "$1[redacted]");
67
+ // Generic opaque sweep catches long values preceded by labels such as
68
+ // client_secret, api_key, password, or Authorization. The minimum length
69
+ // guards against ordinary prose and the case-insensitive pass handles repeats.
70
+ text = redactOpaqueTokens(text);
71
+ return text.slice(0, 1000);
72
+ }