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.
- package/CHANGELOG.md +222 -206
- package/CONTRIBUTING.md +129 -120
- package/INSTALL.md +218 -218
- package/LICENSE +19 -19
- package/NOTICE +11 -11
- package/README.de.md +289 -289
- package/README.es.md +290 -290
- package/README.fr.md +290 -290
- package/README.ja.md +290 -290
- package/README.ko.md +290 -290
- package/README.md +304 -304
- package/README.pt-BR.md +290 -290
- package/README.ru.md +290 -290
- package/README.zh-CN.md +302 -302
- package/compatibility/dsh-bom.json +0 -0
- package/cordis.patch.yml +13 -13
- package/docs/00-project-rules.md +212 -195
- package/docs/02-architecture.md +130 -130
- package/docs/02-architecture.zh-CN.md +130 -130
- package/lib/bin.js.map +1 -1
- package/lib/capability-settings.d.ts +8 -2
- package/lib/capability-settings.d.ts.map +1 -1
- package/lib/capability-tools.d.ts +7 -2
- package/lib/capability-tools.d.ts.map +1 -1
- package/lib/client.js +3 -3
- package/lib/client.js.map +2 -2
- package/lib/codex-images.d.ts +8 -0
- package/lib/codex-images.d.ts.map +1 -1
- package/lib/index.js +32 -7
- package/lib/index.js.map +2 -2
- package/lib/invariant.js.map +1 -1
- package/media/en/settings_accounts.png +0 -0
- package/media/en/settings_capabilities.png +0 -0
- package/media/en/settings_gateway.png +0 -0
- package/media/settings_accounts.png +0 -0
- package/media/settings_capabilities.png +0 -0
- package/media/settings_gateway.png +0 -0
- package/media/settings_overview.png +0 -0
- package/media/zh-CN/settings_accounts.png +0 -0
- package/media/zh-CN/settings_capabilities.png +0 -0
- package/media/zh-CN/settings_gateway.png +0 -0
- package/package.json +148 -148
- package/patches/dsh-agy@0.1.2.patch +25 -25
- package/scripts/release.mjs +186 -186
- package/scripts/smoke-deployed-routes.mjs +146 -146
- package/scripts/verify-deployed-catalog.mjs +87 -87
- package/src/adapter.ts +273 -273
- package/src/alias-adapter.ts +130 -130
- package/src/auth-routes.ts +921 -921
- package/src/auth.ts +67 -67
- package/src/bin.ts +350 -350
- package/src/capability-routes.ts +279 -279
- package/src/capability-runtime.ts +314 -313
- package/src/capability-settings.ts +671 -658
- package/src/capability-tools.ts +685 -666
- package/src/catalog.ts +271 -271
- package/src/client/GrokBuildSettings.tsx +771 -770
- package/src/client/api.ts +88 -88
- package/src/client/components/AboutTab.tsx +30 -30
- package/src/client/components/AccountsTab.tsx +241 -241
- package/src/client/components/Badge.tsx +33 -33
- package/src/client/components/CapabilitiesTab.tsx +265 -265
- package/src/client/components/CliPullPreview.tsx +116 -116
- package/src/client/components/CopyButton.tsx +57 -57
- package/src/client/components/GatewayTab.tsx +469 -469
- package/src/client/components/NoticeBanner.tsx +46 -46
- package/src/client/components/ProgressBar.tsx +53 -53
- package/src/client/components/ProviderCard.tsx +606 -606
- package/src/client/components/SettingsTabs.tsx +75 -75
- package/src/client/components/ToggleSwitch.tsx +71 -71
- package/src/client/constants.ts +230 -224
- package/src/client/display.ts +61 -61
- package/src/client/dshClientAdapter.ts +127 -127
- package/src/client/gatewaySnippets.ts +37 -37
- package/src/client/index.tsx +156 -156
- package/src/client/locales.ts +540 -535
- package/src/client/microStyles.ts +52 -52
- package/src/client/parsers.ts +398 -396
- package/src/client/styles.ts +325 -325
- package/src/client/types.ts +199 -197
- package/src/codex-http.ts +447 -447
- package/src/codex-images.ts +503 -485
- package/src/codex-model-capabilities.ts +320 -320
- package/src/codex-search.ts +245 -245
- package/src/codex-usage.ts +263 -263
- package/src/compatibility.ts +41 -41
- package/src/dsh-host-adapter.ts +173 -173
- package/src/gateway-anthropic-messages.ts +84 -84
- package/src/gateway-auth.ts +102 -102
- package/src/gateway-backend.ts +274 -274
- package/src/gateway-body.ts +49 -49
- package/src/gateway-config.ts +76 -76
- package/src/gateway-http.ts +104 -104
- package/src/gateway-openai-chat.ts +124 -124
- package/src/gateway-openai-responses.ts +53 -53
- package/src/gateway-parse.ts +224 -224
- package/src/gateway-protocol.ts +52 -52
- package/src/gateway-routes.ts +158 -158
- package/src/gateway.ts +258 -258
- package/src/grok-errors.ts +24 -24
- package/src/grok-imagine.ts +1627 -1627
- package/src/grok-import.ts +151 -151
- package/src/http-json.ts +82 -82
- package/src/ids.ts +59 -59
- package/src/imagine-routes.ts +463 -463
- package/src/index.ts +709 -709
- package/src/invariant.ts +17 -17
- package/src/kimi-errors.ts +26 -26
- package/src/media-store.ts +927 -927
- package/src/oauth-import-routes.ts +324 -324
- package/src/oauth-providers.ts +152 -152
- package/src/oauth-session.ts +183 -183
- package/src/oauth-sources.ts +1104 -1104
- package/src/oauth.ts +620 -620
- package/src/provider.ts +128 -128
- package/src/proxy.ts +11 -11
- package/src/redact.ts +72 -72
- package/src/session.ts +218 -218
- package/src/store.ts +217 -217
- package/src/web-origin.ts +296 -296
- 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
|
+
}
|