- add GitHub Copilot model discovery (env token fallback, headers, compat) plus fallback list and quoted provider keys in generated map - surface Copilot provider end-to-end (KnownProvider/default, env+OAuth token refresh/save, enterprise base URL swap, available only when creds/env exist) - tweak interactive OAuth UI to render instruction text and prompt placeholders gpt-5.2-high took about 35 minutes. It had a lot of trouble with `npm check` and went off on a "let's adjust every tsconfig" side quest. Device code flow works, but the ai/scripts/generate-models.ts impl is wrong as models from months ago are missing and only those deprecated are accessible in the /models picker.
138 lines
3.3 KiB
TypeScript
138 lines
3.3 KiB
TypeScript
import { loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
|
|
import { loginGitHubCopilot, refreshGitHubCopilotToken } from "./github-copilot.js";
|
|
import {
|
|
listOAuthProviders as listOAuthProvidersFromStorage,
|
|
loadOAuthCredentials,
|
|
type OAuthCredentials,
|
|
removeOAuthCredentials,
|
|
saveOAuthCredentials,
|
|
} from "./storage.js";
|
|
|
|
// Re-export for convenience
|
|
export { listOAuthProvidersFromStorage as listOAuthProviders };
|
|
|
|
export type SupportedOAuthProvider = "anthropic" | "github-copilot";
|
|
|
|
export interface OAuthProviderInfo {
|
|
id: SupportedOAuthProvider;
|
|
name: string;
|
|
available: boolean;
|
|
}
|
|
|
|
export type OAuthPrompt = {
|
|
message: string;
|
|
placeholder?: string;
|
|
allowEmpty?: boolean;
|
|
};
|
|
|
|
export type OAuthAuthInfo = {
|
|
url: string;
|
|
instructions?: string;
|
|
};
|
|
|
|
/**
|
|
* Get list of OAuth providers
|
|
*/
|
|
export function getOAuthProviders(): OAuthProviderInfo[] {
|
|
return [
|
|
{
|
|
id: "anthropic",
|
|
name: "Anthropic (Claude Pro/Max)",
|
|
available: true,
|
|
},
|
|
{
|
|
id: "github-copilot",
|
|
name: "GitHub Copilot",
|
|
available: true,
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Login with OAuth provider
|
|
*/
|
|
export async function login(
|
|
provider: SupportedOAuthProvider,
|
|
onAuth: (info: OAuthAuthInfo) => void,
|
|
onPrompt: (prompt: OAuthPrompt) => Promise<string>,
|
|
): Promise<void> {
|
|
switch (provider) {
|
|
case "anthropic":
|
|
await loginAnthropic(
|
|
(url) => onAuth({ url }),
|
|
async () => onPrompt({ message: "Paste the authorization code below:" }),
|
|
);
|
|
break;
|
|
case "github-copilot": {
|
|
const creds = await loginGitHubCopilot({
|
|
onAuth: (url, instructions) => onAuth({ url, instructions }),
|
|
onPrompt,
|
|
});
|
|
saveOAuthCredentials("github-copilot", creds);
|
|
break;
|
|
}
|
|
default:
|
|
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logout from OAuth provider
|
|
*/
|
|
export async function logout(provider: SupportedOAuthProvider): Promise<void> {
|
|
removeOAuthCredentials(provider);
|
|
}
|
|
|
|
/**
|
|
* Refresh OAuth token for provider
|
|
*/
|
|
export async function refreshToken(provider: SupportedOAuthProvider): Promise<string> {
|
|
const credentials = loadOAuthCredentials(provider);
|
|
if (!credentials) {
|
|
throw new Error(`No OAuth credentials found for ${provider}`);
|
|
}
|
|
|
|
let newCredentials: OAuthCredentials;
|
|
|
|
switch (provider) {
|
|
case "anthropic":
|
|
newCredentials = await refreshAnthropicToken(credentials.refresh);
|
|
break;
|
|
case "github-copilot":
|
|
newCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
}
|
|
|
|
// Save new credentials
|
|
saveOAuthCredentials(provider, newCredentials);
|
|
|
|
return newCredentials.access;
|
|
}
|
|
|
|
/**
|
|
* Get OAuth token for provider (auto-refreshes if expired)
|
|
*/
|
|
export async function getOAuthToken(provider: SupportedOAuthProvider): Promise<string | null> {
|
|
const credentials = loadOAuthCredentials(provider);
|
|
if (!credentials) {
|
|
return null;
|
|
}
|
|
|
|
// Check if token is expired (with 5 min buffer already applied)
|
|
if (Date.now() >= credentials.expires) {
|
|
// Token expired - refresh it
|
|
try {
|
|
return await refreshToken(provider);
|
|
} catch (error) {
|
|
console.error(`Failed to refresh OAuth token for ${provider}:`, error);
|
|
// Remove invalid credentials
|
|
removeOAuthCredentials(provider);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return credentials.access;
|
|
}
|