@mariozechner/pi-coding-agent 0.24.5 → 0.25.1
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 +30 -0
- package/README.md +16 -11
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +19 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +30 -4
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +2 -0
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/oauth/index.d.ts +25 -22
- package/dist/core/oauth/index.d.ts.map +1 -1
- package/dist/core/oauth/index.js +32 -44
- package/dist/core/oauth/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +41 -5
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +7 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +7 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +33 -17
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +1 -1
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +15 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/package.json +4 -4
- package/dist/core/oauth/anthropic.d.ts +0 -10
- package/dist/core/oauth/anthropic.d.ts.map +0 -1
- package/dist/core/oauth/anthropic.js +0 -100
- package/dist/core/oauth/anthropic.js.map +0 -1
- package/dist/core/oauth/github-copilot.d.ts +0 -30
- package/dist/core/oauth/github-copilot.d.ts.map +0 -1
- package/dist/core/oauth/github-copilot.js +0 -221
- package/dist/core/oauth/github-copilot.js.map +0 -1
- package/dist/core/oauth/storage.d.ts +0 -24
- package/dist/core/oauth/storage.d.ts.map +0 -1
- package/dist/core/oauth/storage.js +0 -69
- package/dist/core/oauth/storage.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mariozechner/pi-coding-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"prepublishOnly": "npm run clean && npm run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@mariozechner/pi-agent-core": "^0.
|
|
43
|
-
"@mariozechner/pi-ai": "^0.
|
|
44
|
-
"@mariozechner/pi-tui": "^0.
|
|
42
|
+
"@mariozechner/pi-agent-core": "^0.25.1",
|
|
43
|
+
"@mariozechner/pi-ai": "^0.25.1",
|
|
44
|
+
"@mariozechner/pi-tui": "^0.25.1",
|
|
45
45
|
"chalk": "^5.5.0",
|
|
46
46
|
"cli-highlight": "^2.1.11",
|
|
47
47
|
"diff": "^8.0.2",
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type OAuthCredentials } from "./storage.js";
|
|
2
|
-
/**
|
|
3
|
-
* Login with Anthropic OAuth (device code flow)
|
|
4
|
-
*/
|
|
5
|
-
export declare function loginAnthropic(onAuthUrl: (url: string) => void, onPromptCode: () => Promise<string>): Promise<void>;
|
|
6
|
-
/**
|
|
7
|
-
* Refresh Anthropic OAuth token using refresh token
|
|
8
|
-
*/
|
|
9
|
-
export declare function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials>;
|
|
10
|
-
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/core/oauth/anthropic.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,gBAAgB,EAAwB,MAAM,cAAc,CAAC;AAiB3E;;GAEG;AACH,wBAAsB,cAAc,CACnC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAiEf;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiC3F","sourcesContent":["import { createHash, randomBytes } from \"crypto\";\nimport { type OAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\";\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://console.anthropic.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://console.anthropic.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\n/**\n * Generate PKCE code verifier and challenge\n */\nfunction generatePKCE(): { verifier: string; challenge: string } {\n\tconst verifier = randomBytes(32).toString(\"base64url\");\n\tconst challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\treturn { verifier, challenge };\n}\n\n/**\n * Login with Anthropic OAuth (device code flow)\n */\nexport async function loginAnthropic(\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tconst { verifier, challenge } = generatePKCE();\n\n\t// Build authorization URL\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\n\tconst authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;\n\n\t// Notify caller with URL to open\n\tonAuthUrl(authUrl);\n\n\t// Wait for user to paste authorization code (format: code#state)\n\tconst authCode = await onPromptCode();\n\tconst splits = authCode.split(\"#\");\n\tconst code = splits[0];\n\tconst state = splits[1];\n\n\t// Exchange code for tokens\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode: code,\n\t\t\tstate: state,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t// Save credentials\n\tconst credentials: OAuthCredentials = {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n\n\tsaveOAuthCredentials(\"anthropic\", credentials);\n}\n\n/**\n * Refresh Anthropic OAuth token using refresh token\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token refresh failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n}\n"]}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "crypto";
|
|
2
|
-
import { saveOAuthCredentials } from "./storage.js";
|
|
3
|
-
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
4
|
-
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
5
|
-
const TOKEN_URL = "https://console.anthropic.com/v1/oauth/token";
|
|
6
|
-
const REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
|
|
7
|
-
const SCOPES = "org:create_api_key user:profile user:inference";
|
|
8
|
-
/**
|
|
9
|
-
* Generate PKCE code verifier and challenge
|
|
10
|
-
*/
|
|
11
|
-
function generatePKCE() {
|
|
12
|
-
const verifier = randomBytes(32).toString("base64url");
|
|
13
|
-
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
14
|
-
return { verifier, challenge };
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Login with Anthropic OAuth (device code flow)
|
|
18
|
-
*/
|
|
19
|
-
export async function loginAnthropic(onAuthUrl, onPromptCode) {
|
|
20
|
-
const { verifier, challenge } = generatePKCE();
|
|
21
|
-
// Build authorization URL
|
|
22
|
-
const authParams = new URLSearchParams({
|
|
23
|
-
code: "true",
|
|
24
|
-
client_id: CLIENT_ID,
|
|
25
|
-
response_type: "code",
|
|
26
|
-
redirect_uri: REDIRECT_URI,
|
|
27
|
-
scope: SCOPES,
|
|
28
|
-
code_challenge: challenge,
|
|
29
|
-
code_challenge_method: "S256",
|
|
30
|
-
state: verifier,
|
|
31
|
-
});
|
|
32
|
-
const authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;
|
|
33
|
-
// Notify caller with URL to open
|
|
34
|
-
onAuthUrl(authUrl);
|
|
35
|
-
// Wait for user to paste authorization code (format: code#state)
|
|
36
|
-
const authCode = await onPromptCode();
|
|
37
|
-
const splits = authCode.split("#");
|
|
38
|
-
const code = splits[0];
|
|
39
|
-
const state = splits[1];
|
|
40
|
-
// Exchange code for tokens
|
|
41
|
-
const tokenResponse = await fetch(TOKEN_URL, {
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: {
|
|
44
|
-
"Content-Type": "application/json",
|
|
45
|
-
},
|
|
46
|
-
body: JSON.stringify({
|
|
47
|
-
grant_type: "authorization_code",
|
|
48
|
-
client_id: CLIENT_ID,
|
|
49
|
-
code: code,
|
|
50
|
-
state: state,
|
|
51
|
-
redirect_uri: REDIRECT_URI,
|
|
52
|
-
code_verifier: verifier,
|
|
53
|
-
}),
|
|
54
|
-
});
|
|
55
|
-
if (!tokenResponse.ok) {
|
|
56
|
-
const error = await tokenResponse.text();
|
|
57
|
-
throw new Error(`Token exchange failed: ${error}`);
|
|
58
|
-
}
|
|
59
|
-
const tokenData = (await tokenResponse.json());
|
|
60
|
-
// Calculate expiry time (current time + expires_in seconds - 5 min buffer)
|
|
61
|
-
const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
|
|
62
|
-
// Save credentials
|
|
63
|
-
const credentials = {
|
|
64
|
-
type: "oauth",
|
|
65
|
-
refresh: tokenData.refresh_token,
|
|
66
|
-
access: tokenData.access_token,
|
|
67
|
-
expires: expiresAt,
|
|
68
|
-
};
|
|
69
|
-
saveOAuthCredentials("anthropic", credentials);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Refresh Anthropic OAuth token using refresh token
|
|
73
|
-
*/
|
|
74
|
-
export async function refreshAnthropicToken(refreshToken) {
|
|
75
|
-
const tokenResponse = await fetch(TOKEN_URL, {
|
|
76
|
-
method: "POST",
|
|
77
|
-
headers: {
|
|
78
|
-
"Content-Type": "application/json",
|
|
79
|
-
},
|
|
80
|
-
body: JSON.stringify({
|
|
81
|
-
grant_type: "refresh_token",
|
|
82
|
-
client_id: CLIENT_ID,
|
|
83
|
-
refresh_token: refreshToken,
|
|
84
|
-
}),
|
|
85
|
-
});
|
|
86
|
-
if (!tokenResponse.ok) {
|
|
87
|
-
const error = await tokenResponse.text();
|
|
88
|
-
throw new Error(`Token refresh failed: ${error}`);
|
|
89
|
-
}
|
|
90
|
-
const tokenData = (await tokenResponse.json());
|
|
91
|
-
// Calculate expiry time (current time + expires_in seconds - 5 min buffer)
|
|
92
|
-
const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
|
|
93
|
-
return {
|
|
94
|
-
type: "oauth",
|
|
95
|
-
refresh: tokenData.refresh_token,
|
|
96
|
-
access: tokenData.access_token,
|
|
97
|
-
expires: expiresAt,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
//# sourceMappingURL=anthropic.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/core/oauth/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from \"crypto\";\nimport { type OAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\";\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://console.anthropic.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://console.anthropic.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\n/**\n * Generate PKCE code verifier and challenge\n */\nfunction generatePKCE(): { verifier: string; challenge: string } {\n\tconst verifier = randomBytes(32).toString(\"base64url\");\n\tconst challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\treturn { verifier, challenge };\n}\n\n/**\n * Login with Anthropic OAuth (device code flow)\n */\nexport async function loginAnthropic(\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tconst { verifier, challenge } = generatePKCE();\n\n\t// Build authorization URL\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\n\tconst authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;\n\n\t// Notify caller with URL to open\n\tonAuthUrl(authUrl);\n\n\t// Wait for user to paste authorization code (format: code#state)\n\tconst authCode = await onPromptCode();\n\tconst splits = authCode.split(\"#\");\n\tconst code = splits[0];\n\tconst state = splits[1];\n\n\t// Exchange code for tokens\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode: code,\n\t\t\tstate: state,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t// Save credentials\n\tconst credentials: OAuthCredentials = {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n\n\tsaveOAuthCredentials(\"anthropic\", credentials);\n}\n\n/**\n * Refresh Anthropic OAuth token using refresh token\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token refresh failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n}\n"]}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { OAuthCredentials } from "./storage.js";
|
|
2
|
-
export declare function normalizeDomain(input: string): string | null;
|
|
3
|
-
/**
|
|
4
|
-
* Parse the proxy-ep from a Copilot token and convert to API base URL.
|
|
5
|
-
* Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...
|
|
6
|
-
* Returns API URL like https://api.individual.githubcopilot.com
|
|
7
|
-
*/
|
|
8
|
-
export declare function getBaseUrlFromToken(token: string): string | null;
|
|
9
|
-
export declare function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: string): string;
|
|
10
|
-
export declare function refreshGitHubCopilotToken(refreshToken: string, enterpriseDomain?: string): Promise<OAuthCredentials>;
|
|
11
|
-
/**
|
|
12
|
-
* Enable a model for the user's GitHub Copilot account.
|
|
13
|
-
* This is required for some models (like Claude, Grok) before they can be used.
|
|
14
|
-
*/
|
|
15
|
-
export declare function enableGitHubCopilotModel(token: string, modelId: string, enterpriseDomain?: string): Promise<boolean>;
|
|
16
|
-
/**
|
|
17
|
-
* Enable all known GitHub Copilot models that may require policy acceptance.
|
|
18
|
-
* Called after successful login to ensure all models are available.
|
|
19
|
-
*/
|
|
20
|
-
export declare function enableAllGitHubCopilotModels(token: string, enterpriseDomain?: string, onProgress?: (model: string, success: boolean) => void): Promise<void>;
|
|
21
|
-
export declare function loginGitHubCopilot(options: {
|
|
22
|
-
onAuth: (url: string, instructions?: string) => void;
|
|
23
|
-
onPrompt: (prompt: {
|
|
24
|
-
message: string;
|
|
25
|
-
placeholder?: string;
|
|
26
|
-
allowEmpty?: boolean;
|
|
27
|
-
}) => Promise<string>;
|
|
28
|
-
onProgress?: (message: string) => void;
|
|
29
|
-
}): Promise<OAuthCredentials>;
|
|
30
|
-
//# sourceMappingURL=github-copilot.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../../src/core/oauth/github-copilot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA+BrD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS5D;AAcD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOhE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CASzF;AA0GD,wBAAsB,yBAAyB,CAC9C,YAAY,EAAE,MAAM,EACpB,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,gBAAgB,CAAC,CA8B3B;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CACjD,KAAK,EAAE,MAAM,EACb,gBAAgB,CAAC,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GACpD,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IACjD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvG,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8B5B","sourcesContent":["import { getModels } from \"@mariozechner/pi-ai\";\nimport type { OAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"Iv1.b507a08c87ecfe98\";\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\tinterval?: number;\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 */\nexport function 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/json\",\n\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t},\n\t\tbody: JSON.stringify({\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\ttypeof 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(\n\tdomain: string,\n\tdeviceCode: string,\n\tintervalSeconds: number,\n\texpiresIn: number,\n) {\n\tconst urls = getUrls(domain);\n\tconst deadline = Date.now() + expiresIn * 1000;\n\tlet intervalMs = Math.max(1000, Math.floor(intervalSeconds * 1000));\n\n\twhile (Date.now() < deadline) {\n\t\tconst raw = await fetchJson(urls.accessTokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t}),\n\t\t});\n\n\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenSuccessResponse).access_token === \"string\") {\n\t\t\treturn (raw as DeviceTokenSuccessResponse).access_token;\n\t\t}\n\n\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenErrorResponse).error === \"string\") {\n\t\t\tconst err = (raw as DeviceTokenErrorResponse).error;\n\t\t\tif (err === \"authorization_pending\") {\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, intervalMs));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (err === \"slow_down\") {\n\t\t\t\tintervalMs += 5000;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, intervalMs));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthrow new Error(`Device flow failed: ${err}`);\n\t\t}\n\n\t\tawait new Promise((resolve) => setTimeout(resolve, intervalMs));\n\t}\n\n\tthrow new Error(\"Device flow timed out\");\n}\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\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\tconst expires = expiresAt * 1000 - 5 * 60 * 1000;\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: refreshToken,\n\t\taccess: token,\n\t\texpires,\n\t\tenterpriseUrl: enterpriseDomain,\n\t} satisfies OAuthCredentials;\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 */\nexport async function enableGitHubCopilotModel(\n\ttoken: string,\n\tmodelId: string,\n\tenterpriseDomain?: string,\n): 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 */\nexport async 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\nexport async function loginGitHubCopilot(options: {\n\tonAuth: (url: string, instructions?: string) => void;\n\tonPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n\tonProgress?: (message: string) => void;\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\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.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);\n\n\tconst githubAccessToken = await pollForGitHubAccessToken(\n\t\tdomain,\n\t\tdevice.device_code,\n\t\tdevice.interval,\n\t\tdevice.expires_in,\n\t);\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\n\treturn credentials;\n}\n"]}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { getModels } from "@mariozechner/pi-ai";
|
|
2
|
-
const CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
|
3
|
-
const COPILOT_HEADERS = {
|
|
4
|
-
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
5
|
-
"Editor-Version": "vscode/1.107.0",
|
|
6
|
-
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
|
7
|
-
"Copilot-Integration-Id": "vscode-chat",
|
|
8
|
-
};
|
|
9
|
-
export function normalizeDomain(input) {
|
|
10
|
-
const trimmed = input.trim();
|
|
11
|
-
if (!trimmed)
|
|
12
|
-
return null;
|
|
13
|
-
try {
|
|
14
|
-
const url = trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`);
|
|
15
|
-
return url.hostname;
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function getUrls(domain) {
|
|
22
|
-
return {
|
|
23
|
-
deviceCodeUrl: `https://${domain}/login/device/code`,
|
|
24
|
-
accessTokenUrl: `https://${domain}/login/oauth/access_token`,
|
|
25
|
-
copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Parse the proxy-ep from a Copilot token and convert to API base URL.
|
|
30
|
-
* Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...
|
|
31
|
-
* Returns API URL like https://api.individual.githubcopilot.com
|
|
32
|
-
*/
|
|
33
|
-
export function getBaseUrlFromToken(token) {
|
|
34
|
-
const match = token.match(/proxy-ep=([^;]+)/);
|
|
35
|
-
if (!match)
|
|
36
|
-
return null;
|
|
37
|
-
const proxyHost = match[1];
|
|
38
|
-
// Convert proxy.xxx to api.xxx
|
|
39
|
-
const apiHost = proxyHost.replace(/^proxy\./, "api.");
|
|
40
|
-
return `https://${apiHost}`;
|
|
41
|
-
}
|
|
42
|
-
export function getGitHubCopilotBaseUrl(token, enterpriseDomain) {
|
|
43
|
-
// If we have a token, extract the base URL from proxy-ep
|
|
44
|
-
if (token) {
|
|
45
|
-
const urlFromToken = getBaseUrlFromToken(token);
|
|
46
|
-
if (urlFromToken)
|
|
47
|
-
return urlFromToken;
|
|
48
|
-
}
|
|
49
|
-
// Fallback for enterprise or if token parsing fails
|
|
50
|
-
if (enterpriseDomain)
|
|
51
|
-
return `https://copilot-api.${enterpriseDomain}`;
|
|
52
|
-
return "https://api.individual.githubcopilot.com";
|
|
53
|
-
}
|
|
54
|
-
async function fetchJson(url, init) {
|
|
55
|
-
const response = await fetch(url, init);
|
|
56
|
-
if (!response.ok) {
|
|
57
|
-
const text = await response.text();
|
|
58
|
-
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
|
59
|
-
}
|
|
60
|
-
return response.json();
|
|
61
|
-
}
|
|
62
|
-
async function startDeviceFlow(domain) {
|
|
63
|
-
const urls = getUrls(domain);
|
|
64
|
-
const data = await fetchJson(urls.deviceCodeUrl, {
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers: {
|
|
67
|
-
Accept: "application/json",
|
|
68
|
-
"Content-Type": "application/json",
|
|
69
|
-
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
70
|
-
},
|
|
71
|
-
body: JSON.stringify({
|
|
72
|
-
client_id: CLIENT_ID,
|
|
73
|
-
scope: "read:user",
|
|
74
|
-
}),
|
|
75
|
-
});
|
|
76
|
-
if (!data || typeof data !== "object") {
|
|
77
|
-
throw new Error("Invalid device code response");
|
|
78
|
-
}
|
|
79
|
-
const deviceCode = data.device_code;
|
|
80
|
-
const userCode = data.user_code;
|
|
81
|
-
const verificationUri = data.verification_uri;
|
|
82
|
-
const interval = data.interval;
|
|
83
|
-
const expiresIn = data.expires_in;
|
|
84
|
-
if (typeof deviceCode !== "string" ||
|
|
85
|
-
typeof userCode !== "string" ||
|
|
86
|
-
typeof verificationUri !== "string" ||
|
|
87
|
-
typeof interval !== "number" ||
|
|
88
|
-
typeof expiresIn !== "number") {
|
|
89
|
-
throw new Error("Invalid device code response fields");
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
device_code: deviceCode,
|
|
93
|
-
user_code: userCode,
|
|
94
|
-
verification_uri: verificationUri,
|
|
95
|
-
interval,
|
|
96
|
-
expires_in: expiresIn,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
async function pollForGitHubAccessToken(domain, deviceCode, intervalSeconds, expiresIn) {
|
|
100
|
-
const urls = getUrls(domain);
|
|
101
|
-
const deadline = Date.now() + expiresIn * 1000;
|
|
102
|
-
let intervalMs = Math.max(1000, Math.floor(intervalSeconds * 1000));
|
|
103
|
-
while (Date.now() < deadline) {
|
|
104
|
-
const raw = await fetchJson(urls.accessTokenUrl, {
|
|
105
|
-
method: "POST",
|
|
106
|
-
headers: {
|
|
107
|
-
Accept: "application/json",
|
|
108
|
-
"Content-Type": "application/json",
|
|
109
|
-
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
110
|
-
},
|
|
111
|
-
body: JSON.stringify({
|
|
112
|
-
client_id: CLIENT_ID,
|
|
113
|
-
device_code: deviceCode,
|
|
114
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
115
|
-
}),
|
|
116
|
-
});
|
|
117
|
-
if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
|
|
118
|
-
return raw.access_token;
|
|
119
|
-
}
|
|
120
|
-
if (raw && typeof raw === "object" && typeof raw.error === "string") {
|
|
121
|
-
const err = raw.error;
|
|
122
|
-
if (err === "authorization_pending") {
|
|
123
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
if (err === "slow_down") {
|
|
127
|
-
intervalMs += 5000;
|
|
128
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
throw new Error(`Device flow failed: ${err}`);
|
|
132
|
-
}
|
|
133
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
134
|
-
}
|
|
135
|
-
throw new Error("Device flow timed out");
|
|
136
|
-
}
|
|
137
|
-
export async function refreshGitHubCopilotToken(refreshToken, enterpriseDomain) {
|
|
138
|
-
const domain = enterpriseDomain || "github.com";
|
|
139
|
-
const urls = getUrls(domain);
|
|
140
|
-
const raw = await fetchJson(urls.copilotTokenUrl, {
|
|
141
|
-
headers: {
|
|
142
|
-
Accept: "application/json",
|
|
143
|
-
Authorization: `Bearer ${refreshToken}`,
|
|
144
|
-
...COPILOT_HEADERS,
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
if (!raw || typeof raw !== "object") {
|
|
148
|
-
throw new Error("Invalid Copilot token response");
|
|
149
|
-
}
|
|
150
|
-
const token = raw.token;
|
|
151
|
-
const expiresAt = raw.expires_at;
|
|
152
|
-
if (typeof token !== "string" || typeof expiresAt !== "number") {
|
|
153
|
-
throw new Error("Invalid Copilot token response fields");
|
|
154
|
-
}
|
|
155
|
-
const expires = expiresAt * 1000 - 5 * 60 * 1000;
|
|
156
|
-
return {
|
|
157
|
-
type: "oauth",
|
|
158
|
-
refresh: refreshToken,
|
|
159
|
-
access: token,
|
|
160
|
-
expires,
|
|
161
|
-
enterpriseUrl: enterpriseDomain,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Enable a model for the user's GitHub Copilot account.
|
|
166
|
-
* This is required for some models (like Claude, Grok) before they can be used.
|
|
167
|
-
*/
|
|
168
|
-
export async function enableGitHubCopilotModel(token, modelId, enterpriseDomain) {
|
|
169
|
-
const baseUrl = getGitHubCopilotBaseUrl(token, enterpriseDomain);
|
|
170
|
-
const url = `${baseUrl}/models/${modelId}/policy`;
|
|
171
|
-
try {
|
|
172
|
-
const response = await fetch(url, {
|
|
173
|
-
method: "POST",
|
|
174
|
-
headers: {
|
|
175
|
-
"Content-Type": "application/json",
|
|
176
|
-
Authorization: `Bearer ${token}`,
|
|
177
|
-
...COPILOT_HEADERS,
|
|
178
|
-
"openai-intent": "chat-policy",
|
|
179
|
-
"x-interaction-type": "chat-policy",
|
|
180
|
-
},
|
|
181
|
-
body: JSON.stringify({ state: "enabled" }),
|
|
182
|
-
});
|
|
183
|
-
return response.ok;
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Enable all known GitHub Copilot models that may require policy acceptance.
|
|
191
|
-
* Called after successful login to ensure all models are available.
|
|
192
|
-
*/
|
|
193
|
-
export async function enableAllGitHubCopilotModels(token, enterpriseDomain, onProgress) {
|
|
194
|
-
const models = getModels("github-copilot");
|
|
195
|
-
await Promise.all(models.map(async (model) => {
|
|
196
|
-
const success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);
|
|
197
|
-
onProgress?.(model.id, success);
|
|
198
|
-
}));
|
|
199
|
-
}
|
|
200
|
-
export async function loginGitHubCopilot(options) {
|
|
201
|
-
const input = await options.onPrompt({
|
|
202
|
-
message: "GitHub Enterprise URL/domain (blank for github.com)",
|
|
203
|
-
placeholder: "company.ghe.com",
|
|
204
|
-
allowEmpty: true,
|
|
205
|
-
});
|
|
206
|
-
const trimmed = input.trim();
|
|
207
|
-
const enterpriseDomain = normalizeDomain(input);
|
|
208
|
-
if (trimmed && !enterpriseDomain) {
|
|
209
|
-
throw new Error("Invalid GitHub Enterprise URL/domain");
|
|
210
|
-
}
|
|
211
|
-
const domain = enterpriseDomain || "github.com";
|
|
212
|
-
const device = await startDeviceFlow(domain);
|
|
213
|
-
options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);
|
|
214
|
-
const githubAccessToken = await pollForGitHubAccessToken(domain, device.device_code, device.interval, device.expires_in);
|
|
215
|
-
const credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);
|
|
216
|
-
// Enable all models after successful login
|
|
217
|
-
options.onProgress?.("Enabling models...");
|
|
218
|
-
await enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);
|
|
219
|
-
return credentials;
|
|
220
|
-
}
|
|
221
|
-
//# sourceMappingURL=github-copilot.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"github-copilot.js","sourceRoot":"","sources":["../../../src/core/oauth/github-copilot.ts"],"names":[],"mappings":"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","sourcesContent":["import { getModels } from \"@mariozechner/pi-ai\";\nimport type { OAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"Iv1.b507a08c87ecfe98\";\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\tinterval?: number;\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 */\nexport function 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/json\",\n\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t},\n\t\tbody: JSON.stringify({\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\ttypeof 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(\n\tdomain: string,\n\tdeviceCode: string,\n\tintervalSeconds: number,\n\texpiresIn: number,\n) {\n\tconst urls = getUrls(domain);\n\tconst deadline = Date.now() + expiresIn * 1000;\n\tlet intervalMs = Math.max(1000, Math.floor(intervalSeconds * 1000));\n\n\twhile (Date.now() < deadline) {\n\t\tconst raw = await fetchJson(urls.accessTokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t}),\n\t\t});\n\n\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenSuccessResponse).access_token === \"string\") {\n\t\t\treturn (raw as DeviceTokenSuccessResponse).access_token;\n\t\t}\n\n\t\tif (raw && typeof raw === \"object\" && typeof (raw as DeviceTokenErrorResponse).error === \"string\") {\n\t\t\tconst err = (raw as DeviceTokenErrorResponse).error;\n\t\t\tif (err === \"authorization_pending\") {\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, intervalMs));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (err === \"slow_down\") {\n\t\t\t\tintervalMs += 5000;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, intervalMs));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthrow new Error(`Device flow failed: ${err}`);\n\t\t}\n\n\t\tawait new Promise((resolve) => setTimeout(resolve, intervalMs));\n\t}\n\n\tthrow new Error(\"Device flow timed out\");\n}\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\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\tconst expires = expiresAt * 1000 - 5 * 60 * 1000;\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: refreshToken,\n\t\taccess: token,\n\t\texpires,\n\t\tenterpriseUrl: enterpriseDomain,\n\t} satisfies OAuthCredentials;\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 */\nexport async function enableGitHubCopilotModel(\n\ttoken: string,\n\tmodelId: string,\n\tenterpriseDomain?: string,\n): 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 */\nexport async 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\nexport async function loginGitHubCopilot(options: {\n\tonAuth: (url: string, instructions?: string) => void;\n\tonPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;\n\tonProgress?: (message: string) => void;\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\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.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);\n\n\tconst githubAccessToken = await pollForGitHubAccessToken(\n\t\tdomain,\n\t\tdevice.device_code,\n\t\tdevice.interval,\n\t\tdevice.expires_in,\n\t);\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\n\treturn credentials;\n}\n"]}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface OAuthCredentials {
|
|
2
|
-
type: "oauth";
|
|
3
|
-
refresh: string;
|
|
4
|
-
access: string;
|
|
5
|
-
expires: number;
|
|
6
|
-
enterpriseUrl?: string;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Load OAuth credentials for a specific provider
|
|
10
|
-
*/
|
|
11
|
-
export declare function loadOAuthCredentials(provider: string): OAuthCredentials | null;
|
|
12
|
-
/**
|
|
13
|
-
* Save OAuth credentials for a specific provider
|
|
14
|
-
*/
|
|
15
|
-
export declare function saveOAuthCredentials(provider: string, creds: OAuthCredentials): void;
|
|
16
|
-
/**
|
|
17
|
-
* Remove OAuth credentials for a specific provider
|
|
18
|
-
*/
|
|
19
|
-
export declare function removeOAuthCredentials(provider: string): void;
|
|
20
|
-
/**
|
|
21
|
-
* List all providers with OAuth credentials
|
|
22
|
-
*/
|
|
23
|
-
export declare function listOAuthProviders(): string[];
|
|
24
|
-
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/core/oauth/storage.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AA6CD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAG9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAIpF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAI7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAG7C","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { getAgentDir, getOAuthPath } from \"../../config.js\";\n\nexport interface OAuthCredentials {\n\ttype: \"oauth\";\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\tenterpriseUrl?: string;\n}\n\ninterface OAuthStorageFormat {\n\t[provider: string]: OAuthCredentials;\n}\n\n/**\n * Ensure the config directory exists\n */\nfunction ensureConfigDir(): void {\n\tconst configDir = getAgentDir();\n\tif (!existsSync(configDir)) {\n\t\tmkdirSync(configDir, { recursive: true, mode: 0o700 });\n\t}\n}\n\n/**\n * Load all OAuth credentials from oauth.json\n */\nfunction loadStorage(): OAuthStorageFormat {\n\tconst filePath = getOAuthPath();\n\tif (!existsSync(filePath)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\treturn JSON.parse(content);\n\t} catch (error) {\n\t\tconsole.error(`Warning: Failed to load OAuth credentials: ${error}`);\n\t\treturn {};\n\t}\n}\n\n/**\n * Save all OAuth credentials to oauth.json\n */\nfunction saveStorage(storage: OAuthStorageFormat): void {\n\tensureConfigDir();\n\tconst filePath = getOAuthPath();\n\twriteFileSync(filePath, JSON.stringify(storage, null, 2), \"utf-8\");\n\t// Set permissions to owner read/write only\n\tchmodSync(filePath, 0o600);\n}\n\n/**\n * Load OAuth credentials for a specific provider\n */\nexport function loadOAuthCredentials(provider: string): OAuthCredentials | null {\n\tconst storage = loadStorage();\n\treturn storage[provider] || null;\n}\n\n/**\n * Save OAuth credentials for a specific provider\n */\nexport function saveOAuthCredentials(provider: string, creds: OAuthCredentials): void {\n\tconst storage = loadStorage();\n\tstorage[provider] = creds;\n\tsaveStorage(storage);\n}\n\n/**\n * Remove OAuth credentials for a specific provider\n */\nexport function removeOAuthCredentials(provider: string): void {\n\tconst storage = loadStorage();\n\tdelete storage[provider];\n\tsaveStorage(storage);\n}\n\n/**\n * List all providers with OAuth credentials\n */\nexport function listOAuthProviders(): string[] {\n\tconst storage = loadStorage();\n\treturn Object.keys(storage);\n}\n"]}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { getAgentDir, getOAuthPath } from "../../config.js";
|
|
3
|
-
/**
|
|
4
|
-
* Ensure the config directory exists
|
|
5
|
-
*/
|
|
6
|
-
function ensureConfigDir() {
|
|
7
|
-
const configDir = getAgentDir();
|
|
8
|
-
if (!existsSync(configDir)) {
|
|
9
|
-
mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Load all OAuth credentials from oauth.json
|
|
14
|
-
*/
|
|
15
|
-
function loadStorage() {
|
|
16
|
-
const filePath = getOAuthPath();
|
|
17
|
-
if (!existsSync(filePath)) {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
const content = readFileSync(filePath, "utf-8");
|
|
22
|
-
return JSON.parse(content);
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
console.error(`Warning: Failed to load OAuth credentials: ${error}`);
|
|
26
|
-
return {};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Save all OAuth credentials to oauth.json
|
|
31
|
-
*/
|
|
32
|
-
function saveStorage(storage) {
|
|
33
|
-
ensureConfigDir();
|
|
34
|
-
const filePath = getOAuthPath();
|
|
35
|
-
writeFileSync(filePath, JSON.stringify(storage, null, 2), "utf-8");
|
|
36
|
-
// Set permissions to owner read/write only
|
|
37
|
-
chmodSync(filePath, 0o600);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Load OAuth credentials for a specific provider
|
|
41
|
-
*/
|
|
42
|
-
export function loadOAuthCredentials(provider) {
|
|
43
|
-
const storage = loadStorage();
|
|
44
|
-
return storage[provider] || null;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Save OAuth credentials for a specific provider
|
|
48
|
-
*/
|
|
49
|
-
export function saveOAuthCredentials(provider, creds) {
|
|
50
|
-
const storage = loadStorage();
|
|
51
|
-
storage[provider] = creds;
|
|
52
|
-
saveStorage(storage);
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Remove OAuth credentials for a specific provider
|
|
56
|
-
*/
|
|
57
|
-
export function removeOAuthCredentials(provider) {
|
|
58
|
-
const storage = loadStorage();
|
|
59
|
-
delete storage[provider];
|
|
60
|
-
saveStorage(storage);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* List all providers with OAuth credentials
|
|
64
|
-
*/
|
|
65
|
-
export function listOAuthProviders() {
|
|
66
|
-
const storage = loadStorage();
|
|
67
|
-
return Object.keys(storage);
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=storage.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/core/oauth/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAc5D;;GAEG;AACH,SAAS,eAAe,GAAS;IAChC,MAAM,SAAS,GAAG,WAAW,EAAE,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,CAAC;AAAA,CACD;AAED;;GAEG;AACH,SAAS,WAAW,GAAuB;IAC1C,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,8CAA8C,KAAK,EAAE,CAAC,CAAC;QACrE,OAAO,EAAE,CAAC;IACX,CAAC;AAAA,CACD;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,OAA2B,EAAQ;IACvD,eAAe,EAAE,CAAC;IAClB,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACnE,2CAA2C;IAC3C,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAAA,CAC3B;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAA2B;IAC/E,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;AAAA,CACjC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,KAAuB,EAAQ;IACrF,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;IAC1B,WAAW,CAAC,OAAO,CAAC,CAAC;AAAA,CACrB;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB,EAAQ;IAC9D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzB,WAAW,CAAC,OAAO,CAAC,CAAC;AAAA,CACrB;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,GAAa;IAC9C,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAAA,CAC5B","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { getAgentDir, getOAuthPath } from \"../../config.js\";\n\nexport interface OAuthCredentials {\n\ttype: \"oauth\";\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\tenterpriseUrl?: string;\n}\n\ninterface OAuthStorageFormat {\n\t[provider: string]: OAuthCredentials;\n}\n\n/**\n * Ensure the config directory exists\n */\nfunction ensureConfigDir(): void {\n\tconst configDir = getAgentDir();\n\tif (!existsSync(configDir)) {\n\t\tmkdirSync(configDir, { recursive: true, mode: 0o700 });\n\t}\n}\n\n/**\n * Load all OAuth credentials from oauth.json\n */\nfunction loadStorage(): OAuthStorageFormat {\n\tconst filePath = getOAuthPath();\n\tif (!existsSync(filePath)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\treturn JSON.parse(content);\n\t} catch (error) {\n\t\tconsole.error(`Warning: Failed to load OAuth credentials: ${error}`);\n\t\treturn {};\n\t}\n}\n\n/**\n * Save all OAuth credentials to oauth.json\n */\nfunction saveStorage(storage: OAuthStorageFormat): void {\n\tensureConfigDir();\n\tconst filePath = getOAuthPath();\n\twriteFileSync(filePath, JSON.stringify(storage, null, 2), \"utf-8\");\n\t// Set permissions to owner read/write only\n\tchmodSync(filePath, 0o600);\n}\n\n/**\n * Load OAuth credentials for a specific provider\n */\nexport function loadOAuthCredentials(provider: string): OAuthCredentials | null {\n\tconst storage = loadStorage();\n\treturn storage[provider] || null;\n}\n\n/**\n * Save OAuth credentials for a specific provider\n */\nexport function saveOAuthCredentials(provider: string, creds: OAuthCredentials): void {\n\tconst storage = loadStorage();\n\tstorage[provider] = creds;\n\tsaveStorage(storage);\n}\n\n/**\n * Remove OAuth credentials for a specific provider\n */\nexport function removeOAuthCredentials(provider: string): void {\n\tconst storage = loadStorage();\n\tdelete storage[provider];\n\tsaveStorage(storage);\n}\n\n/**\n * List all providers with OAuth credentials\n */\nexport function listOAuthProviders(): string[] {\n\tconst storage = loadStorage();\n\treturn Object.keys(storage);\n}\n"]}
|