@openshamela/ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1383 -0
- package/dist/api-registry.d.ts +20 -0
- package/dist/api-registry.d.ts.map +1 -0
- package/dist/api-registry.js +44 -0
- package/dist/api-registry.js.map +1 -0
- package/dist/bedrock-provider.d.ts +5 -0
- package/dist/bedrock-provider.d.ts.map +1 -0
- package/dist/bedrock-provider.js +6 -0
- package/dist/bedrock-provider.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/env-api-keys.d.ts +18 -0
- package/dist/env-api-keys.d.ts.map +1 -0
- package/dist/env-api-keys.js +182 -0
- package/dist/env-api-keys.js.map +1 -0
- package/dist/image-models.d.ts +10 -0
- package/dist/image-models.d.ts.map +1 -0
- package/dist/image-models.generated.d.ts +440 -0
- package/dist/image-models.generated.d.ts.map +1 -0
- package/dist/image-models.generated.js +442 -0
- package/dist/image-models.generated.js.map +1 -0
- package/dist/image-models.js +23 -0
- package/dist/image-models.js.map +1 -0
- package/dist/images-api-registry.d.ts +14 -0
- package/dist/images-api-registry.d.ts.map +1 -0
- package/dist/images-api-registry.js +22 -0
- package/dist/images-api-registry.js.map +1 -0
- package/dist/images.d.ts +4 -0
- package/dist/images.d.ts.map +1 -0
- package/dist/images.js +14 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +18 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.generated.d.ts +17252 -0
- package/dist/models.generated.d.ts.map +1 -0
- package/dist/models.generated.js +16113 -0
- package/dist/models.generated.js.map +1 -0
- package/dist/models.js +71 -0
- package/dist/models.js.map +1 -0
- package/dist/oauth.d.ts +2 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +2 -0
- package/dist/oauth.js.map +1 -0
- package/dist/providers/amazon-bedrock.d.ts +38 -0
- package/dist/providers/amazon-bedrock.d.ts.map +1 -0
- package/dist/providers/amazon-bedrock.js +763 -0
- package/dist/providers/amazon-bedrock.js.map +1 -0
- package/dist/providers/anthropic.d.ts +71 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +949 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/azure-openai-responses.d.ts +15 -0
- package/dist/providers/azure-openai-responses.d.ts.map +1 -0
- package/dist/providers/azure-openai-responses.js +225 -0
- package/dist/providers/azure-openai-responses.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +13 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +26 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/faux.d.ts +56 -0
- package/dist/providers/faux.d.ts.map +1 -0
- package/dist/providers/faux.js +368 -0
- package/dist/providers/faux.js.map +1 -0
- package/dist/providers/github-copilot-headers.d.ts +8 -0
- package/dist/providers/github-copilot-headers.d.ts.map +1 -0
- package/dist/providers/github-copilot-headers.js +29 -0
- package/dist/providers/github-copilot-headers.js.map +1 -0
- package/dist/providers/google-shared.d.ts +70 -0
- package/dist/providers/google-shared.d.ts.map +1 -0
- package/dist/providers/google-shared.js +329 -0
- package/dist/providers/google-shared.js.map +1 -0
- package/dist/providers/google-vertex.d.ts +15 -0
- package/dist/providers/google-vertex.d.ts.map +1 -0
- package/dist/providers/google-vertex.js +442 -0
- package/dist/providers/google-vertex.js.map +1 -0
- package/dist/providers/google.d.ts +13 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +400 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/images/openrouter.d.ts +3 -0
- package/dist/providers/images/openrouter.d.ts.map +1 -0
- package/dist/providers/images/openrouter.js +129 -0
- package/dist/providers/images/openrouter.js.map +1 -0
- package/dist/providers/images/register-builtins.d.ts +4 -0
- package/dist/providers/images/register-builtins.d.ts.map +1 -0
- package/dist/providers/images/register-builtins.js +34 -0
- package/dist/providers/images/register-builtins.js.map +1 -0
- package/dist/providers/mistral.d.ts +25 -0
- package/dist/providers/mistral.d.ts.map +1 -0
- package/dist/providers/mistral.js +535 -0
- package/dist/providers/mistral.js.map +1 -0
- package/dist/providers/openai-codex-responses.d.ts +30 -0
- package/dist/providers/openai-codex-responses.d.ts.map +1 -0
- package/dist/providers/openai-codex-responses.js +1089 -0
- package/dist/providers/openai-codex-responses.js.map +1 -0
- package/dist/providers/openai-completions.d.ts +19 -0
- package/dist/providers/openai-completions.d.ts.map +1 -0
- package/dist/providers/openai-completions.js +950 -0
- package/dist/providers/openai-completions.js.map +1 -0
- package/dist/providers/openai-prompt-cache.d.ts +3 -0
- package/dist/providers/openai-prompt-cache.d.ts.map +1 -0
- package/dist/providers/openai-prompt-cache.js +10 -0
- package/dist/providers/openai-prompt-cache.js.map +1 -0
- package/dist/providers/openai-responses-shared.d.ts +18 -0
- package/dist/providers/openai-responses-shared.d.ts.map +1 -0
- package/dist/providers/openai-responses-shared.js +492 -0
- package/dist/providers/openai-responses-shared.js.map +1 -0
- package/dist/providers/openai-responses.d.ts +13 -0
- package/dist/providers/openai-responses.d.ts.map +1 -0
- package/dist/providers/openai-responses.js +237 -0
- package/dist/providers/openai-responses.js.map +1 -0
- package/dist/providers/register-builtins.d.ts +35 -0
- package/dist/providers/register-builtins.d.ts.map +1 -0
- package/dist/providers/register-builtins.js +254 -0
- package/dist/providers/register-builtins.js.map +1 -0
- package/dist/providers/simple-options.d.ts +8 -0
- package/dist/providers/simple-options.d.ts.map +1 -0
- package/dist/providers/simple-options.js +41 -0
- package/dist/providers/simple-options.js.map +1 -0
- package/dist/providers/transform-messages.d.ts +8 -0
- package/dist/providers/transform-messages.d.ts.map +1 -0
- package/dist/providers/transform-messages.js +184 -0
- package/dist/providers/transform-messages.js.map +1 -0
- package/dist/session-resources.d.ts +4 -0
- package/dist/session-resources.d.ts.map +1 -0
- package/dist/session-resources.js +22 -0
- package/dist/session-resources.js.map +1 -0
- package/dist/stream.d.ts +8 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +27 -0
- package/dist/stream.js.map +1 -0
- package/dist/types.d.ts +498 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/diagnostics.d.ts +19 -0
- package/dist/utils/diagnostics.d.ts.map +1 -0
- package/dist/utils/diagnostics.js +25 -0
- package/dist/utils/diagnostics.js.map +1 -0
- package/dist/utils/event-stream.d.ts +21 -0
- package/dist/utils/event-stream.d.ts.map +1 -0
- package/dist/utils/event-stream.js +81 -0
- package/dist/utils/event-stream.js.map +1 -0
- package/dist/utils/hash.d.ts +3 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.d.ts.map +1 -0
- package/dist/utils/headers.js +8 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/json-parse.d.ts +16 -0
- package/dist/utils/json-parse.d.ts.map +1 -0
- package/dist/utils/json-parse.js +113 -0
- package/dist/utils/json-parse.js.map +1 -0
- package/dist/utils/node-http-proxy.d.ts +10 -0
- package/dist/utils/node-http-proxy.d.ts.map +1 -0
- package/dist/utils/node-http-proxy.js +97 -0
- package/dist/utils/node-http-proxy.js.map +1 -0
- package/dist/utils/oauth/anthropic.d.ts +25 -0
- package/dist/utils/oauth/anthropic.d.ts.map +1 -0
- package/dist/utils/oauth/anthropic.js +335 -0
- package/dist/utils/oauth/anthropic.js.map +1 -0
- package/dist/utils/oauth/device-code.d.ts +19 -0
- package/dist/utils/oauth/device-code.d.ts.map +1 -0
- package/dist/utils/oauth/device-code.js +55 -0
- package/dist/utils/oauth/device-code.js.map +1 -0
- package/dist/utils/oauth/github-copilot.d.ts +30 -0
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
- package/dist/utils/oauth/github-copilot.js +268 -0
- package/dist/utils/oauth/github-copilot.js.map +1 -0
- package/dist/utils/oauth/index.d.ts +58 -0
- package/dist/utils/oauth/index.d.ts.map +1 -0
- package/dist/utils/oauth/index.js +122 -0
- package/dist/utils/oauth/index.js.map +1 -0
- package/dist/utils/oauth/oauth-page.d.ts +3 -0
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
- package/dist/utils/oauth/oauth-page.js +105 -0
- package/dist/utils/oauth/oauth-page.js.map +1 -0
- package/dist/utils/oauth/openai-codex.d.ts +34 -0
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
- package/dist/utils/oauth/openai-codex.js +385 -0
- package/dist/utils/oauth/openai-codex.js.map +1 -0
- package/dist/utils/oauth/pkce.d.ts +13 -0
- package/dist/utils/oauth/pkce.d.ts.map +1 -0
- package/dist/utils/oauth/pkce.js +31 -0
- package/dist/utils/oauth/pkce.js.map +1 -0
- package/dist/utils/oauth/types.d.ts +64 -0
- package/dist/utils/oauth/types.d.ts.map +1 -0
- package/dist/utils/oauth/types.js +2 -0
- package/dist/utils/oauth/types.js.map +1 -0
- package/dist/utils/overflow.d.ts +56 -0
- package/dist/utils/overflow.d.ts.map +1 -0
- package/dist/utils/overflow.js +151 -0
- package/dist/utils/overflow.js.map +1 -0
- package/dist/utils/sanitize-unicode.d.ts +22 -0
- package/dist/utils/sanitize-unicode.d.ts.map +1 -0
- package/dist/utils/sanitize-unicode.js +26 -0
- package/dist/utils/sanitize-unicode.js.map +1 -0
- package/dist/utils/typebox-helpers.d.ts +17 -0
- package/dist/utils/typebox-helpers.d.ts.map +1 -0
- package/dist/utils/typebox-helpers.js +21 -0
- package/dist/utils/typebox-helpers.js.map +1 -0
- package/dist/utils/validation.d.ts +18 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +281 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot OAuth flow
|
|
3
|
+
*/
|
|
4
|
+
import { getModels } from "../../models.js";
|
|
5
|
+
import { pollOAuthDeviceCodeFlow } from "./device-code.js";
|
|
6
|
+
const decode = (s) => atob(s);
|
|
7
|
+
const CLIENT_ID = decode("SXYxLmI1MDdhMDhjODdlY2ZlOTg=");
|
|
8
|
+
const COPILOT_HEADERS = {
|
|
9
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
10
|
+
"Editor-Version": "vscode/1.107.0",
|
|
11
|
+
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
|
12
|
+
"Copilot-Integration-Id": "vscode-chat",
|
|
13
|
+
};
|
|
14
|
+
export function normalizeDomain(input) {
|
|
15
|
+
const trimmed = input.trim();
|
|
16
|
+
if (!trimmed)
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const url = trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`);
|
|
20
|
+
return url.hostname;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getUrls(domain) {
|
|
27
|
+
return {
|
|
28
|
+
deviceCodeUrl: `https://${domain}/login/device/code`,
|
|
29
|
+
accessTokenUrl: `https://${domain}/login/oauth/access_token`,
|
|
30
|
+
copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse the proxy-ep from a Copilot token and convert to API base URL.
|
|
35
|
+
* Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...
|
|
36
|
+
* Returns API URL like https://api.individual.githubcopilot.com
|
|
37
|
+
*/
|
|
38
|
+
function getBaseUrlFromToken(token) {
|
|
39
|
+
const match = token.match(/proxy-ep=([^;]+)/);
|
|
40
|
+
if (!match)
|
|
41
|
+
return null;
|
|
42
|
+
const proxyHost = match[1];
|
|
43
|
+
// Convert proxy.xxx to api.xxx
|
|
44
|
+
const apiHost = proxyHost.replace(/^proxy\./, "api.");
|
|
45
|
+
return `https://${apiHost}`;
|
|
46
|
+
}
|
|
47
|
+
export function getGitHubCopilotBaseUrl(token, enterpriseDomain) {
|
|
48
|
+
// If we have a token, extract the base URL from proxy-ep
|
|
49
|
+
if (token) {
|
|
50
|
+
const urlFromToken = getBaseUrlFromToken(token);
|
|
51
|
+
if (urlFromToken)
|
|
52
|
+
return urlFromToken;
|
|
53
|
+
}
|
|
54
|
+
// Fallback for enterprise or if token parsing fails
|
|
55
|
+
if (enterpriseDomain)
|
|
56
|
+
return `https://copilot-api.${enterpriseDomain}`;
|
|
57
|
+
return "https://api.individual.githubcopilot.com";
|
|
58
|
+
}
|
|
59
|
+
async function fetchJson(url, init) {
|
|
60
|
+
const response = await fetch(url, init);
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const text = await response.text();
|
|
63
|
+
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
|
64
|
+
}
|
|
65
|
+
return response.json();
|
|
66
|
+
}
|
|
67
|
+
async function startDeviceFlow(domain) {
|
|
68
|
+
const urls = getUrls(domain);
|
|
69
|
+
const data = await fetchJson(urls.deviceCodeUrl, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: {
|
|
72
|
+
Accept: "application/json",
|
|
73
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
74
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
75
|
+
},
|
|
76
|
+
body: new URLSearchParams({
|
|
77
|
+
client_id: CLIENT_ID,
|
|
78
|
+
scope: "read:user",
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
if (!data || typeof data !== "object") {
|
|
82
|
+
throw new Error("Invalid device code response");
|
|
83
|
+
}
|
|
84
|
+
const deviceCode = data.device_code;
|
|
85
|
+
const userCode = data.user_code;
|
|
86
|
+
const verificationUri = data.verification_uri;
|
|
87
|
+
const interval = data.interval;
|
|
88
|
+
const expiresIn = data.expires_in;
|
|
89
|
+
if (typeof deviceCode !== "string" ||
|
|
90
|
+
typeof userCode !== "string" ||
|
|
91
|
+
typeof verificationUri !== "string" ||
|
|
92
|
+
(interval !== undefined && typeof interval !== "number") ||
|
|
93
|
+
typeof expiresIn !== "number") {
|
|
94
|
+
throw new Error("Invalid device code response fields");
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
device_code: deviceCode,
|
|
98
|
+
user_code: userCode,
|
|
99
|
+
verification_uri: verificationUri,
|
|
100
|
+
interval,
|
|
101
|
+
expires_in: expiresIn,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function pollForGitHubAccessToken(domain, device, signal) {
|
|
105
|
+
const urls = getUrls(domain);
|
|
106
|
+
return pollOAuthDeviceCodeFlow({
|
|
107
|
+
intervalSeconds: device.interval,
|
|
108
|
+
expiresInSeconds: device.expires_in,
|
|
109
|
+
signal,
|
|
110
|
+
poll: async () => {
|
|
111
|
+
const raw = await fetchJson(urls.accessTokenUrl, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: {
|
|
114
|
+
Accept: "application/json",
|
|
115
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
116
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
117
|
+
},
|
|
118
|
+
body: new URLSearchParams({
|
|
119
|
+
client_id: CLIENT_ID,
|
|
120
|
+
device_code: device.device_code,
|
|
121
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
|
|
125
|
+
return { status: "complete", accessToken: raw.access_token };
|
|
126
|
+
}
|
|
127
|
+
if (raw && typeof raw === "object" && typeof raw.error === "string") {
|
|
128
|
+
const { error, error_description: description } = raw;
|
|
129
|
+
if (error === "authorization_pending") {
|
|
130
|
+
return { status: "pending" };
|
|
131
|
+
}
|
|
132
|
+
if (error === "slow_down") {
|
|
133
|
+
return { status: "slow_down" };
|
|
134
|
+
}
|
|
135
|
+
const descriptionSuffix = description ? `: ${description}` : "";
|
|
136
|
+
return { status: "failed", message: `Device flow failed: ${error}${descriptionSuffix}` };
|
|
137
|
+
}
|
|
138
|
+
return { status: "failed", message: "Invalid device token response" };
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Refresh GitHub Copilot token
|
|
144
|
+
*/
|
|
145
|
+
export async function refreshGitHubCopilotToken(refreshToken, enterpriseDomain) {
|
|
146
|
+
const domain = enterpriseDomain || "github.com";
|
|
147
|
+
const urls = getUrls(domain);
|
|
148
|
+
const raw = await fetchJson(urls.copilotTokenUrl, {
|
|
149
|
+
headers: {
|
|
150
|
+
Accept: "application/json",
|
|
151
|
+
Authorization: `Bearer ${refreshToken}`,
|
|
152
|
+
...COPILOT_HEADERS,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
if (!raw || typeof raw !== "object") {
|
|
156
|
+
throw new Error("Invalid Copilot token response");
|
|
157
|
+
}
|
|
158
|
+
const token = raw.token;
|
|
159
|
+
const expiresAt = raw.expires_at;
|
|
160
|
+
if (typeof token !== "string" || typeof expiresAt !== "number") {
|
|
161
|
+
throw new Error("Invalid Copilot token response fields");
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
refresh: refreshToken,
|
|
165
|
+
access: token,
|
|
166
|
+
expires: expiresAt * 1000 - 5 * 60 * 1000,
|
|
167
|
+
enterpriseUrl: enterpriseDomain,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Enable a model for the user's GitHub Copilot account.
|
|
172
|
+
* This is required for some models (like Claude, Grok) before they can be used.
|
|
173
|
+
*/
|
|
174
|
+
async function enableGitHubCopilotModel(token, modelId, enterpriseDomain) {
|
|
175
|
+
const baseUrl = getGitHubCopilotBaseUrl(token, enterpriseDomain);
|
|
176
|
+
const url = `${baseUrl}/models/${modelId}/policy`;
|
|
177
|
+
try {
|
|
178
|
+
const response = await fetch(url, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: {
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
Authorization: `Bearer ${token}`,
|
|
183
|
+
...COPILOT_HEADERS,
|
|
184
|
+
"openai-intent": "chat-policy",
|
|
185
|
+
"x-interaction-type": "chat-policy",
|
|
186
|
+
},
|
|
187
|
+
body: JSON.stringify({ state: "enabled" }),
|
|
188
|
+
});
|
|
189
|
+
return response.ok;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Enable all known GitHub Copilot models that may require policy acceptance.
|
|
197
|
+
* Called after successful login to ensure all models are available.
|
|
198
|
+
*/
|
|
199
|
+
async function enableAllGitHubCopilotModels(token, enterpriseDomain, onProgress) {
|
|
200
|
+
const models = getModels("github-copilot");
|
|
201
|
+
await Promise.all(models.map(async (model) => {
|
|
202
|
+
const success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);
|
|
203
|
+
onProgress?.(model.id, success);
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Login with GitHub Copilot OAuth (device code flow)
|
|
208
|
+
*
|
|
209
|
+
* @param options.onDeviceCode - Callback with URL and user code
|
|
210
|
+
* @param options.onPrompt - Callback to prompt user for input
|
|
211
|
+
* @param options.onProgress - Optional progress callback
|
|
212
|
+
* @param options.signal - Optional AbortSignal for cancellation
|
|
213
|
+
*/
|
|
214
|
+
export async function loginGitHubCopilot(options) {
|
|
215
|
+
const input = await options.onPrompt({
|
|
216
|
+
message: "GitHub Enterprise URL/domain (blank for github.com)",
|
|
217
|
+
placeholder: "company.ghe.com",
|
|
218
|
+
allowEmpty: true,
|
|
219
|
+
});
|
|
220
|
+
if (options.signal?.aborted) {
|
|
221
|
+
throw new Error("Login cancelled");
|
|
222
|
+
}
|
|
223
|
+
const trimmed = input.trim();
|
|
224
|
+
const enterpriseDomain = normalizeDomain(input);
|
|
225
|
+
if (trimmed && !enterpriseDomain) {
|
|
226
|
+
throw new Error("Invalid GitHub Enterprise URL/domain");
|
|
227
|
+
}
|
|
228
|
+
const domain = enterpriseDomain || "github.com";
|
|
229
|
+
const device = await startDeviceFlow(domain);
|
|
230
|
+
options.onDeviceCode({
|
|
231
|
+
userCode: device.user_code,
|
|
232
|
+
verificationUri: device.verification_uri,
|
|
233
|
+
intervalSeconds: device.interval,
|
|
234
|
+
expiresInSeconds: device.expires_in,
|
|
235
|
+
});
|
|
236
|
+
const githubAccessToken = await pollForGitHubAccessToken(domain, device, options.signal);
|
|
237
|
+
const credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);
|
|
238
|
+
// Enable all models after successful login
|
|
239
|
+
options.onProgress?.("Enabling models...");
|
|
240
|
+
await enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);
|
|
241
|
+
return credentials;
|
|
242
|
+
}
|
|
243
|
+
export const githubCopilotOAuthProvider = {
|
|
244
|
+
id: "github-copilot",
|
|
245
|
+
name: "GitHub Copilot",
|
|
246
|
+
async login(callbacks) {
|
|
247
|
+
return loginGitHubCopilot({
|
|
248
|
+
onDeviceCode: callbacks.onDeviceCode,
|
|
249
|
+
onPrompt: callbacks.onPrompt,
|
|
250
|
+
onProgress: callbacks.onProgress,
|
|
251
|
+
signal: callbacks.signal,
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
async refreshToken(credentials) {
|
|
255
|
+
const creds = credentials;
|
|
256
|
+
return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);
|
|
257
|
+
},
|
|
258
|
+
getApiKey(credentials) {
|
|
259
|
+
return credentials.access;
|
|
260
|
+
},
|
|
261
|
+
modifyModels(models, credentials) {
|
|
262
|
+
const creds = credentials;
|
|
263
|
+
const domain = creds.enterpriseUrl ? (normalizeDomain(creds.enterpriseUrl) ?? undefined) : undefined;
|
|
264
|
+
const baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);
|
|
265
|
+
return models.map((m) => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
//# sourceMappingURL=github-copilot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-copilot.js","sourceRoot":"","sources":["../../../src/utils/oauth/github-copilot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot OAuth flow\n */\n\nimport { getModels } from \"../../models.ts\";\nimport type { Api, Model } from \"../../types.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport type { OAuthCredentials, OAuthDeviceCodeInfo, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\ntype CopilotCredentials = OAuthCredentials & {\n\tenterpriseUrl?: string;\n};\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"SXYxLmI1MDdhMDhjODdlY2ZlOTg=\");\n\nconst COPILOT_HEADERS = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"Copilot-Integration-Id\": \"vscode-chat\",\n} as const;\n\ntype DeviceCodeResponse = {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\tinterval?: number;\n\texpires_in: number;\n};\n\ntype DeviceTokenSuccessResponse = {\n\taccess_token: string;\n\ttoken_type?: string;\n\tscope?: string;\n};\n\ntype DeviceTokenErrorResponse = {\n\terror: string;\n\terror_description?: string;\n};\n\nexport function normalizeDomain(input: string): string | null {\n\tconst trimmed = input.trim();\n\tif (!trimmed) return null;\n\ttry {\n\t\tconst url = trimmed.includes(\"://\") ? new URL(trimmed) : new URL(`https://${trimmed}`);\n\t\treturn url.hostname;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction getUrls(domain: string): {\n\tdeviceCodeUrl: string;\n\taccessTokenUrl: string;\n\tcopilotTokenUrl: string;\n} {\n\treturn {\n\t\tdeviceCodeUrl: `https://${domain}/login/device/code`,\n\t\taccessTokenUrl: `https://${domain}/login/oauth/access_token`,\n\t\tcopilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,\n\t};\n}\n\n/**\n * Parse the proxy-ep from a Copilot token and convert to API base URL.\n * Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...\n * Returns API URL like https://api.individual.githubcopilot.com\n */\nfunction getBaseUrlFromToken(token: string): string | null {\n\tconst match = token.match(/proxy-ep=([^;]+)/);\n\tif (!match) return null;\n\tconst proxyHost = match[1];\n\t// Convert proxy.xxx to api.xxx\n\tconst apiHost = proxyHost.replace(/^proxy\\./, \"api.\");\n\treturn `https://${apiHost}`;\n}\n\nexport function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string {\n\t// If we have a token, extract the base URL from proxy-ep\n\tif (token) {\n\t\tconst urlFromToken = getBaseUrlFromToken(token);\n\t\tif (urlFromToken) return urlFromToken;\n\t}\n\t// Fallback for enterprise or if token parsing fails\n\tif (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n\treturn \"https://api.individual.githubcopilot.com\";\n}\n\nasync function fetchJson(url: string, init: RequestInit): Promise<unknown> {\n\tconst response = await fetch(url, init);\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new Error(`${response.status} ${response.statusText}: ${text}`);\n\t}\n\treturn response.json();\n}\n\nasync function startDeviceFlow(domain: string): Promise<DeviceCodeResponse> {\n\tconst urls = getUrls(domain);\n\tconst data = await fetchJson(urls.deviceCodeUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t},\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tscope: \"read:user\",\n\t\t}),\n\t});\n\n\tif (!data || typeof data !== \"object\") {\n\t\tthrow new Error(\"Invalid device code response\");\n\t}\n\n\tconst deviceCode = (data as Record<string, unknown>).device_code;\n\tconst userCode = (data as Record<string, unknown>).user_code;\n\tconst verificationUri = (data as Record<string, unknown>).verification_uri;\n\tconst interval = (data as Record<string, unknown>).interval;\n\tconst expiresIn = (data as Record<string, unknown>).expires_in;\n\n\tif (\n\t\ttypeof deviceCode !== \"string\" ||\n\t\ttypeof userCode !== \"string\" ||\n\t\ttypeof verificationUri !== \"string\" ||\n\t\t(interval !== undefined && typeof interval !== \"number\") ||\n\t\ttypeof expiresIn !== \"number\"\n\t) {\n\t\tthrow new Error(\"Invalid device code response fields\");\n\t}\n\n\treturn {\n\t\tdevice_code: deviceCode,\n\t\tuser_code: userCode,\n\t\tverification_uri: verificationUri,\n\t\tinterval,\n\t\texpires_in: expiresIn,\n\t};\n}\n\nasync function pollForGitHubAccessToken(domain: string, device: DeviceCodeResponse, signal?: AbortSignal) {\n\tconst urls = getUrls(domain);\n\treturn pollOAuthDeviceCodeFlow({\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t\tsignal,\n\t\tpoll: async () => {\n\t\t\tconst raw = await fetchJson(urls.accessTokenUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t\t},\n\t\t\t\tbody: new URLSearchParams({\n\t\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\t\tdevice_code: device.device_code,\n\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenSuccessResponse).access_token === \"string\") {\n\t\t\t\treturn { status: \"complete\", accessToken: (raw as DeviceTokenSuccessResponse).access_token };\n\t\t\t}\n\n\t\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenErrorResponse).error === \"string\") {\n\t\t\t\tconst { error, error_description: description } = raw as DeviceTokenErrorResponse;\n\t\t\t\tif (error === \"authorization_pending\") {\n\t\t\t\t\treturn { status: \"pending\" };\n\t\t\t\t}\n\n\t\t\t\tif (error === \"slow_down\") {\n\t\t\t\t\treturn { status: \"slow_down\" };\n\t\t\t\t}\n\n\t\t\t\tconst descriptionSuffix = description ? `: ${description}` : \"\";\n\t\t\t\treturn { status: \"failed\", message: `Device flow failed: ${error}${descriptionSuffix}` };\n\t\t\t}\n\n\t\t\treturn { status: \"failed\", message: \"Invalid device token response\" };\n\t\t},\n\t});\n}\n\n/**\n * Refresh GitHub Copilot token\n */\nexport async function refreshGitHubCopilotToken(\n\trefreshToken: string,\n\tenterpriseDomain?: string,\n): Promise<OAuthCredentials> {\n\tconst domain = enterpriseDomain || \"github.com\";\n\tconst urls = getUrls(domain);\n\n\tconst raw = await fetchJson(urls.copilotTokenUrl, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${refreshToken}`,\n\t\t\t...COPILOT_HEADERS,\n\t\t},\n\t});\n\n\tif (!raw || typeof raw !== \"object\") {\n\t\tthrow new Error(\"Invalid Copilot token response\");\n\t}\n\n\tconst token = (raw as Record<string, unknown>).token;\n\tconst expiresAt = (raw as Record<string, unknown>).expires_at;\n\n\tif (typeof token !== \"string\" || typeof expiresAt !== \"number\") {\n\t\tthrow new Error(\"Invalid Copilot token response fields\");\n\t}\n\n\treturn {\n\t\trefresh: refreshToken,\n\t\taccess: token,\n\t\texpires: expiresAt * 1000 - 5 * 60 * 1000,\n\t\tenterpriseUrl: enterpriseDomain,\n\t};\n}\n\n/**\n * Enable a model for the user's GitHub Copilot account.\n * This is required for some models (like Claude, Grok) before they can be used.\n */\nasync function enableGitHubCopilotModel(token: string, modelId: string, enterpriseDomain?: string): Promise<boolean> {\n\tconst baseUrl = getGitHubCopilotBaseUrl(token, enterpriseDomain);\n\tconst url = `${baseUrl}/models/${modelId}/policy`;\n\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t...COPILOT_HEADERS,\n\t\t\t\t\"openai-intent\": \"chat-policy\",\n\t\t\t\t\"x-interaction-type\": \"chat-policy\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ state: \"enabled\" }),\n\t\t});\n\t\treturn response.ok;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Enable all known GitHub Copilot models that may require policy acceptance.\n * Called after successful login to ensure all models are available.\n */\nasync function enableAllGitHubCopilotModels(\n\ttoken: string,\n\tenterpriseDomain?: string,\n\tonProgress?: (model: string, success: boolean) => void,\n): Promise<void> {\n\tconst models = getModels(\"github-copilot\");\n\tawait Promise.all(\n\t\tmodels.map(async (model) => {\n\t\t\tconst success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);\n\t\t\tonProgress?.(model.id, success);\n\t\t}),\n\t);\n}\n\n/**\n * Login with GitHub Copilot OAuth (device code flow)\n *\n * @param options.onDeviceCode - Callback with URL and user code\n * @param options.onPrompt - Callback to prompt user for input\n * @param options.onProgress - Optional progress callback\n * @param options.signal - Optional AbortSignal for cancellation\n */\nexport async function loginGitHubCopilot(options: {\n\tonDeviceCode: (info: OAuthDeviceCodeInfo) => void;\n\tonPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\tconst input = await options.onPrompt({\n\t\tmessage: \"GitHub Enterprise URL/domain (blank for github.com)\",\n\t\tplaceholder: \"company.ghe.com\",\n\t\tallowEmpty: true,\n\t});\n\n\tif (options.signal?.aborted) {\n\t\tthrow new Error(\"Login cancelled\");\n\t}\n\n\tconst trimmed = input.trim();\n\tconst enterpriseDomain = normalizeDomain(input);\n\tif (trimmed && !enterpriseDomain) {\n\t\tthrow new Error(\"Invalid GitHub Enterprise URL/domain\");\n\t}\n\tconst domain = enterpriseDomain || \"github.com\";\n\n\tconst device = await startDeviceFlow(domain);\n\toptions.onDeviceCode({\n\t\tuserCode: device.user_code,\n\t\tverificationUri: device.verification_uri,\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t});\n\n\tconst githubAccessToken = await pollForGitHubAccessToken(domain, device, options.signal);\n\tconst credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);\n\n\t// Enable all models after successful login\n\toptions.onProgress?.(\"Enabling models...\");\n\tawait enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);\n\treturn credentials;\n}\n\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n\tid: \"github-copilot\",\n\tname: \"GitHub Copilot\",\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGitHubCopilot({\n\t\t\tonDeviceCode: callbacks.onDeviceCode,\n\t\t\tonPrompt: callbacks.onPrompt,\n\t\t\tonProgress: callbacks.onProgress,\n\t\t\tsignal: callbacks.signal,\n\t\t});\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\treturn refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n\n\tmodifyModels(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[] {\n\t\tconst creds = credentials as CopilotCredentials;\n\t\tconst domain = creds.enterpriseUrl ? (normalizeDomain(creds.enterpriseUrl) ?? undefined) : undefined;\n\t\tconst baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);\n\t\treturn models.map((m) => (m.provider === \"github-copilot\" ? { ...m, baseUrl } : m));\n\t},\n};\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth credential management for AI providers.
|
|
3
|
+
*
|
|
4
|
+
* This module handles login, token refresh, and credential storage
|
|
5
|
+
* for OAuth-based providers:
|
|
6
|
+
* - Anthropic (Claude Pro/Max)
|
|
7
|
+
* - GitHub Copilot
|
|
8
|
+
*/
|
|
9
|
+
export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.ts";
|
|
10
|
+
export * from "./device-code.ts";
|
|
11
|
+
export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.ts";
|
|
12
|
+
export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from "./openai-codex.ts";
|
|
13
|
+
export * from "./types.ts";
|
|
14
|
+
import type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from "./types.ts";
|
|
15
|
+
/**
|
|
16
|
+
* Get an OAuth provider by ID
|
|
17
|
+
*/
|
|
18
|
+
export declare function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Register a custom OAuth provider
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerOAuthProvider(provider: OAuthProviderInterface): void;
|
|
23
|
+
/**
|
|
24
|
+
* Unregister an OAuth provider.
|
|
25
|
+
*
|
|
26
|
+
* If the provider is built-in, restores the built-in implementation.
|
|
27
|
+
* Custom providers are removed completely.
|
|
28
|
+
*/
|
|
29
|
+
export declare function unregisterOAuthProvider(id: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Reset OAuth providers to built-ins.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resetOAuthProviders(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get all registered OAuth providers
|
|
36
|
+
*/
|
|
37
|
+
export declare function getOAuthProviders(): OAuthProviderInterface[];
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]
|
|
40
|
+
*/
|
|
41
|
+
export declare function getOAuthProviderInfoList(): OAuthProviderInfo[];
|
|
42
|
+
/**
|
|
43
|
+
* Refresh token for any OAuth provider.
|
|
44
|
+
* @deprecated Use getOAuthProvider(id).refreshToken() instead
|
|
45
|
+
*/
|
|
46
|
+
export declare function refreshOAuthToken(providerId: OAuthProviderId, credentials: OAuthCredentials): Promise<OAuthCredentials>;
|
|
47
|
+
/**
|
|
48
|
+
* Get API key for a provider from OAuth credentials.
|
|
49
|
+
* Automatically refreshes expired tokens.
|
|
50
|
+
*
|
|
51
|
+
* @returns API key string and updated credentials, or null if no credentials
|
|
52
|
+
* @throws Error if refresh fails
|
|
53
|
+
*/
|
|
54
|
+
export declare function getOAuthApiKey(providerId: OAuthProviderId, credentials: Record<string, OAuthCredentials>): Promise<{
|
|
55
|
+
newCredentials: OAuthCredentials;
|
|
56
|
+
apiKey: string;
|
|
57
|
+
} | null>;
|
|
58
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC/F,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAExG,cAAc,YAAY,CAAC;AAS3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAY/G;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,GAAG,sBAAsB,GAAG,SAAS,CAExF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAOxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,sBAAsB,EAAE,CAE5D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,iBAAiB,EAAE,CAM9D;AAMD;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,gBAAgB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC3C,OAAO,CAAC;IAAE,cAAc,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsBtE","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.ts\";\nexport * from \"./device-code.ts\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.ts\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.ts\";\n\nexport * from \"./types.ts\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.ts\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.ts\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.ts\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.ts\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth credential management for AI providers.
|
|
3
|
+
*
|
|
4
|
+
* This module handles login, token refresh, and credential storage
|
|
5
|
+
* for OAuth-based providers:
|
|
6
|
+
* - Anthropic (Claude Pro/Max)
|
|
7
|
+
* - GitHub Copilot
|
|
8
|
+
*/
|
|
9
|
+
// Anthropic
|
|
10
|
+
export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
|
|
11
|
+
export * from "./device-code.js";
|
|
12
|
+
// GitHub Copilot
|
|
13
|
+
export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
|
|
14
|
+
// OpenAI Codex (ChatGPT OAuth)
|
|
15
|
+
export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from "./openai-codex.js";
|
|
16
|
+
export * from "./types.js";
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Provider Registry
|
|
19
|
+
// ============================================================================
|
|
20
|
+
import { anthropicOAuthProvider } from "./anthropic.js";
|
|
21
|
+
import { githubCopilotOAuthProvider } from "./github-copilot.js";
|
|
22
|
+
import { openaiCodexOAuthProvider } from "./openai-codex.js";
|
|
23
|
+
const BUILT_IN_OAUTH_PROVIDERS = [
|
|
24
|
+
anthropicOAuthProvider,
|
|
25
|
+
githubCopilotOAuthProvider,
|
|
26
|
+
openaiCodexOAuthProvider,
|
|
27
|
+
];
|
|
28
|
+
const oauthProviderRegistry = new Map(BUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]));
|
|
29
|
+
/**
|
|
30
|
+
* Get an OAuth provider by ID
|
|
31
|
+
*/
|
|
32
|
+
export function getOAuthProvider(id) {
|
|
33
|
+
return oauthProviderRegistry.get(id);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Register a custom OAuth provider
|
|
37
|
+
*/
|
|
38
|
+
export function registerOAuthProvider(provider) {
|
|
39
|
+
oauthProviderRegistry.set(provider.id, provider);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Unregister an OAuth provider.
|
|
43
|
+
*
|
|
44
|
+
* If the provider is built-in, restores the built-in implementation.
|
|
45
|
+
* Custom providers are removed completely.
|
|
46
|
+
*/
|
|
47
|
+
export function unregisterOAuthProvider(id) {
|
|
48
|
+
const builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);
|
|
49
|
+
if (builtInProvider) {
|
|
50
|
+
oauthProviderRegistry.set(id, builtInProvider);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
oauthProviderRegistry.delete(id);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reset OAuth providers to built-ins.
|
|
57
|
+
*/
|
|
58
|
+
export function resetOAuthProviders() {
|
|
59
|
+
oauthProviderRegistry.clear();
|
|
60
|
+
for (const provider of BUILT_IN_OAUTH_PROVIDERS) {
|
|
61
|
+
oauthProviderRegistry.set(provider.id, provider);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get all registered OAuth providers
|
|
66
|
+
*/
|
|
67
|
+
export function getOAuthProviders() {
|
|
68
|
+
return Array.from(oauthProviderRegistry.values());
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]
|
|
72
|
+
*/
|
|
73
|
+
export function getOAuthProviderInfoList() {
|
|
74
|
+
return getOAuthProviders().map((p) => ({
|
|
75
|
+
id: p.id,
|
|
76
|
+
name: p.name,
|
|
77
|
+
available: true,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// High-level API (uses provider registry)
|
|
82
|
+
// ============================================================================
|
|
83
|
+
/**
|
|
84
|
+
* Refresh token for any OAuth provider.
|
|
85
|
+
* @deprecated Use getOAuthProvider(id).refreshToken() instead
|
|
86
|
+
*/
|
|
87
|
+
export async function refreshOAuthToken(providerId, credentials) {
|
|
88
|
+
const provider = getOAuthProvider(providerId);
|
|
89
|
+
if (!provider) {
|
|
90
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
91
|
+
}
|
|
92
|
+
return provider.refreshToken(credentials);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get API key for a provider from OAuth credentials.
|
|
96
|
+
* Automatically refreshes expired tokens.
|
|
97
|
+
*
|
|
98
|
+
* @returns API key string and updated credentials, or null if no credentials
|
|
99
|
+
* @throws Error if refresh fails
|
|
100
|
+
*/
|
|
101
|
+
export async function getOAuthApiKey(providerId, credentials) {
|
|
102
|
+
const provider = getOAuthProvider(providerId);
|
|
103
|
+
if (!provider) {
|
|
104
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
105
|
+
}
|
|
106
|
+
let creds = credentials[providerId];
|
|
107
|
+
if (!creds) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
// Refresh if expired
|
|
111
|
+
if (Date.now() >= creds.expires) {
|
|
112
|
+
try {
|
|
113
|
+
creds = await provider.refreshToken(creds);
|
|
114
|
+
}
|
|
115
|
+
catch (_error) {
|
|
116
|
+
throw new Error(`Failed to refresh OAuth token for ${providerId}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const apiKey = provider.getApiKey(creds);
|
|
120
|
+
return { newCredentials: creds, apiKey };
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.ts\";\nexport * from \"./device-code.ts\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.ts\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.ts\";\n\nexport * from \"./types.ts\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.ts\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.ts\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.ts\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.ts\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-page.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/oauth-page.ts"],"names":[],"mappings":"AA6FA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAOxE","sourcesContent":["const LOGO_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 800 800\" aria-hidden=\"true\"><path fill=\"#fff\" fill-rule=\"evenodd\" d=\"M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z\"/><path fill=\"#fff\" d=\"M517.36 400 H634.72 V634.72 H517.36 Z\"/></svg>`;\n\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replaceAll(\"&\", \"&\")\n\t\t.replaceAll(\"<\", \"<\")\n\t\t.replaceAll(\">\", \">\")\n\t\t.replaceAll('\"', \""\")\n\t\t.replaceAll(\"'\", \"'\");\n}\n\nfunction renderPage(options: { title: string; heading: string; message: string; details?: string }): string {\n\tconst title = escapeHtml(options.title);\n\tconst heading = escapeHtml(options.heading);\n\tconst message = escapeHtml(options.message);\n\tconst details = options.details ? escapeHtml(options.details) : undefined;\n\n\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${title}</title>\n <style>\n :root {\n --text: #fafafa;\n --text-dim: #a1a1aa;\n --page-bg: #09090b;\n --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n }\n * { box-sizing: border-box; }\n html { color-scheme: dark; }\n body {\n margin: 0;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: var(--page-bg);\n color: var(--text);\n font-family: var(--font-sans);\n text-align: center;\n }\n main {\n width: 100%;\n max-width: 560px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n }\n .logo {\n width: 72px;\n height: 72px;\n display: block;\n margin-bottom: 24px;\n }\n h1 {\n margin: 0 0 10px;\n font-size: 28px;\n line-height: 1.15;\n font-weight: 650;\n color: var(--text);\n }\n p {\n margin: 0;\n line-height: 1.7;\n color: var(--text-dim);\n font-size: 15px;\n }\n .details {\n margin-top: 16px;\n font-family: var(--font-mono);\n font-size: 13px;\n color: var(--text-dim);\n white-space: pre-wrap;\n word-break: break-word;\n }\n </style>\n</head>\n<body>\n <main>\n <div class=\"logo\">${LOGO_SVG}</div>\n <h1>${heading}</h1>\n <p>${message}</p>\n ${details ? `<div class=\"details\">${details}</div>` : \"\"}\n </main>\n</body>\n</html>`;\n}\n\nexport function oauthSuccessHtml(message: string): string {\n\treturn renderPage({\n\t\ttitle: \"Authentication successful\",\n\t\theading: \"Authentication successful\",\n\t\tmessage,\n\t});\n}\n\nexport function oauthErrorHtml(message: string, details?: string): string {\n\treturn renderPage({\n\t\ttitle: \"Authentication failed\",\n\t\theading: \"Authentication failed\",\n\t\tmessage,\n\t\tdetails,\n\t});\n}\n"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;
|
|
2
|
+
function escapeHtml(value) {
|
|
3
|
+
return value
|
|
4
|
+
.replaceAll("&", "&")
|
|
5
|
+
.replaceAll("<", "<")
|
|
6
|
+
.replaceAll(">", ">")
|
|
7
|
+
.replaceAll('"', """)
|
|
8
|
+
.replaceAll("'", "'");
|
|
9
|
+
}
|
|
10
|
+
function renderPage(options) {
|
|
11
|
+
const title = escapeHtml(options.title);
|
|
12
|
+
const heading = escapeHtml(options.heading);
|
|
13
|
+
const message = escapeHtml(options.message);
|
|
14
|
+
const details = options.details ? escapeHtml(options.details) : undefined;
|
|
15
|
+
return `<!doctype html>
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<head>
|
|
18
|
+
<meta charset="utf-8" />
|
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
20
|
+
<title>${title}</title>
|
|
21
|
+
<style>
|
|
22
|
+
:root {
|
|
23
|
+
--text: #fafafa;
|
|
24
|
+
--text-dim: #a1a1aa;
|
|
25
|
+
--page-bg: #09090b;
|
|
26
|
+
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
27
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
28
|
+
}
|
|
29
|
+
* { box-sizing: border-box; }
|
|
30
|
+
html { color-scheme: dark; }
|
|
31
|
+
body {
|
|
32
|
+
margin: 0;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
padding: 24px;
|
|
38
|
+
background: var(--page-bg);
|
|
39
|
+
color: var(--text);
|
|
40
|
+
font-family: var(--font-sans);
|
|
41
|
+
text-align: center;
|
|
42
|
+
}
|
|
43
|
+
main {
|
|
44
|
+
width: 100%;
|
|
45
|
+
max-width: 560px;
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
}
|
|
51
|
+
.logo {
|
|
52
|
+
width: 72px;
|
|
53
|
+
height: 72px;
|
|
54
|
+
display: block;
|
|
55
|
+
margin-bottom: 24px;
|
|
56
|
+
}
|
|
57
|
+
h1 {
|
|
58
|
+
margin: 0 0 10px;
|
|
59
|
+
font-size: 28px;
|
|
60
|
+
line-height: 1.15;
|
|
61
|
+
font-weight: 650;
|
|
62
|
+
color: var(--text);
|
|
63
|
+
}
|
|
64
|
+
p {
|
|
65
|
+
margin: 0;
|
|
66
|
+
line-height: 1.7;
|
|
67
|
+
color: var(--text-dim);
|
|
68
|
+
font-size: 15px;
|
|
69
|
+
}
|
|
70
|
+
.details {
|
|
71
|
+
margin-top: 16px;
|
|
72
|
+
font-family: var(--font-mono);
|
|
73
|
+
font-size: 13px;
|
|
74
|
+
color: var(--text-dim);
|
|
75
|
+
white-space: pre-wrap;
|
|
76
|
+
word-break: break-word;
|
|
77
|
+
}
|
|
78
|
+
</style>
|
|
79
|
+
</head>
|
|
80
|
+
<body>
|
|
81
|
+
<main>
|
|
82
|
+
<div class="logo">${LOGO_SVG}</div>
|
|
83
|
+
<h1>${heading}</h1>
|
|
84
|
+
<p>${message}</p>
|
|
85
|
+
${details ? `<div class="details">${details}</div>` : ""}
|
|
86
|
+
</main>
|
|
87
|
+
</body>
|
|
88
|
+
</html>`;
|
|
89
|
+
}
|
|
90
|
+
export function oauthSuccessHtml(message) {
|
|
91
|
+
return renderPage({
|
|
92
|
+
title: "Authentication successful",
|
|
93
|
+
heading: "Authentication successful",
|
|
94
|
+
message,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export function oauthErrorHtml(message, details) {
|
|
98
|
+
return renderPage({
|
|
99
|
+
title: "Authentication failed",
|
|
100
|
+
heading: "Authentication failed",
|
|
101
|
+
message,
|
|
102
|
+
details,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=oauth-page.js.map
|