Clean up OAuth device-code callbacks
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
- No fluff or cheerful filler text (e.g., "Thanks @user" not "Thanks so much @user!")
|
- No fluff or cheerful filler text (e.g., "Thanks @user" not "Thanks so much @user!")
|
||||||
- Technical prose only, be direct
|
- Technical prose only, be direct
|
||||||
- When the user asks a question, answer it first before making edits or running implementation commands.
|
- When the user asks a question, answer it first before making edits or running implementation commands.
|
||||||
|
- When responding to user feedback or an analysis, explicitly say whether you agree or disagree before saying what you changed.
|
||||||
|
|
||||||
## Code Quality
|
## Code Quality
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- Changed `OAuthLoginCallbacks` to require `onDeviceCode` and `onSelect`, so OAuth providers can rely on pi supplying device-code and selection UI callbacks.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed GitHub Copilot OAuth login to rely on the required device-code callback without a runtime callback availability guard.
|
||||||
- Fixed Amazon Bedrock Claude requests to send the model output token cap by default, matching Anthropic requests and avoiding Bedrock's 4096-token default truncation ([#4848](https://github.com/earendil-works/pi/issues/4848)).
|
- Fixed Amazon Bedrock Claude requests to send the model output token cap by default, matching Anthropic requests and avoiding Bedrock's 4096-token default truncation ([#4848](https://github.com/earendil-works/pi/issues/4848)).
|
||||||
|
|
||||||
## [0.75.4] - 2026-05-20
|
## [0.75.4] - 2026-05-20
|
||||||
|
|||||||
@@ -50,6 +50,15 @@ async function login(providerId: OAuthProviderId): Promise<void> {
|
|||||||
onPrompt: async (p) => {
|
onPrompt: async (p) => {
|
||||||
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
||||||
},
|
},
|
||||||
|
onSelect: async (p) => {
|
||||||
|
console.log(`\n${p.message}`);
|
||||||
|
for (let i = 0; i < p.options.length; i++) {
|
||||||
|
console.log(` ${i + 1}. ${p.options[i].label}`);
|
||||||
|
}
|
||||||
|
const choice = await promptFn(`Enter number (1-${p.options.length}):`);
|
||||||
|
const index = parseInt(choice, 10) - 1;
|
||||||
|
return p.options[index]?.id;
|
||||||
|
},
|
||||||
onProgress: (msg) => console.log(msg),
|
onProgress: (msg) => console.log(msg),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -319,10 +319,6 @@ export const githubCopilotOAuthProvider: OAuthProviderInterface = {
|
|||||||
name: "GitHub Copilot",
|
name: "GitHub Copilot",
|
||||||
|
|
||||||
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
||||||
if (!callbacks.onDeviceCode) {
|
|
||||||
throw new Error("GitHub Copilot OAuth requires a device code callback");
|
|
||||||
}
|
|
||||||
|
|
||||||
return loginGitHubCopilot({
|
return loginGitHubCopilot({
|
||||||
onDeviceCode: callbacks.onDeviceCode,
|
onDeviceCode: callbacks.onDeviceCode,
|
||||||
onPrompt: callbacks.onPrompt,
|
onPrompt: callbacks.onPrompt,
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ export type OAuthSelectPrompt = {
|
|||||||
|
|
||||||
export interface OAuthLoginCallbacks {
|
export interface OAuthLoginCallbacks {
|
||||||
onAuth: (info: OAuthAuthInfo) => void;
|
onAuth: (info: OAuthAuthInfo) => void;
|
||||||
onDeviceCode?: (info: OAuthDeviceCodeInfo) => void;
|
onDeviceCode: (info: OAuthDeviceCodeInfo) => void;
|
||||||
onPrompt: (prompt: OAuthPrompt) => Promise<string>;
|
onPrompt: (prompt: OAuthPrompt) => Promise<string>;
|
||||||
onProgress?: (message: string) => void;
|
onProgress?: (message: string) => void;
|
||||||
onManualCodeInput?: () => Promise<string>;
|
onManualCodeInput?: () => Promise<string>;
|
||||||
/** Show an interactive selector and return the selected option id, or undefined on cancel. */
|
/** Show an interactive selector and return the selected option id, or undefined on cancel. */
|
||||||
onSelect?: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;
|
onSelect: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed exported session HTML to escape quote characters in attribute values ([#4832](https://github.com/earendil-works/pi/issues/4832)).
|
- Fixed exported session HTML to escape quote characters in attribute values ([#4832](https://github.com/earendil-works/pi/issues/4832)).
|
||||||
|
- Fixed GitHub Copilot device-code login to keep opening the verification URL in browser-capable environments while ignoring browser launch failures for headless use.
|
||||||
- Fixed git package installs to reconcile existing checkouts to the requested ref and update package settings without losing filters ([#4870](https://github.com/earendil-works/pi/issues/4870)).
|
- Fixed git package installs to reconcile existing checkouts to the requested ref and update package settings without losing filters ([#4870](https://github.com/earendil-works/pi/issues/4870)).
|
||||||
- Published a 0.74.2 rescue release that tells Node 20 users to upgrade Node before updating to newer Pi versions ([#4876](https://github.com/earendil-works/pi/issues/4876)).
|
- Published a 0.74.2 rescue release that tells Node 20 users to upgrade Node before updating to newer Pi versions ([#4876](https://github.com/earendil-works/pi/issues/4876)).
|
||||||
- Fixed final bash tool cards to avoid rendering duplicate full-output truncation paths ([#4819](https://github.com/earendil-works/pi/issues/4819)).
|
- Fixed final bash tool cards to avoid rendering duplicate full-output truncation paths ([#4819](https://github.com/earendil-works/pi/issues/4819)).
|
||||||
|
|||||||
@@ -263,17 +263,28 @@ pi.registerProvider("corporate-ai", {
|
|||||||
name: "Corporate AI (SSO)",
|
name: "Corporate AI (SSO)",
|
||||||
|
|
||||||
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
||||||
// Option 1: Browser-based OAuth
|
const method = await callbacks.onSelect({
|
||||||
callbacks.onAuth({ url: "https://sso.corp.com/authorize?..." });
|
message: "Select login method:",
|
||||||
|
options: [
|
||||||
// Option 2: Device code flow
|
{ id: "browser", label: "Browser OAuth" },
|
||||||
callbacks.onDeviceCode({
|
{ id: "device", label: "Device code" }
|
||||||
userCode: "ABCD-1234",
|
]
|
||||||
verificationUri: "https://sso.corp.com/device"
|
|
||||||
});
|
});
|
||||||
|
if (!method) throw new Error("Login cancelled");
|
||||||
|
|
||||||
// Option 3: Prompt for token/code
|
let code: string;
|
||||||
const code = await callbacks.onPrompt({ message: "Enter SSO code:" });
|
if (method === "device") {
|
||||||
|
callbacks.onDeviceCode({
|
||||||
|
userCode: "ABCD-1234",
|
||||||
|
verificationUri: "https://sso.corp.com/device",
|
||||||
|
intervalSeconds: 5,
|
||||||
|
expiresInSeconds: 900
|
||||||
|
});
|
||||||
|
code = await pollDeviceCodeUntilComplete();
|
||||||
|
} else {
|
||||||
|
callbacks.onAuth({ url: "https://sso.corp.com/authorize?..." });
|
||||||
|
code = await callbacks.onPrompt({ message: "Enter SSO code:" });
|
||||||
|
}
|
||||||
|
|
||||||
// Exchange for tokens (your implementation)
|
// Exchange for tokens (your implementation)
|
||||||
const tokens = await exchangeCodeForTokens(code);
|
const tokens = await exchangeCodeForTokens(code);
|
||||||
@@ -322,10 +333,21 @@ interface OAuthLoginCallbacks {
|
|||||||
onAuth(params: { url: string }): void;
|
onAuth(params: { url: string }): void;
|
||||||
|
|
||||||
// Show device code (for device authorization flow)
|
// Show device code (for device authorization flow)
|
||||||
onDeviceCode(params: { userCode: string; verificationUri: string }): void;
|
onDeviceCode(params: {
|
||||||
|
userCode: string;
|
||||||
|
verificationUri: string;
|
||||||
|
intervalSeconds?: number;
|
||||||
|
expiresInSeconds?: number;
|
||||||
|
}): void;
|
||||||
|
|
||||||
// Prompt user for input (for manual token entry)
|
// Prompt user for input (for manual token entry)
|
||||||
onPrompt(params: { message: string }): Promise<string>;
|
onPrompt(params: { message: string }): Promise<string>;
|
||||||
|
|
||||||
|
// Show an interactive selector, e.g. to choose browser OAuth vs device code
|
||||||
|
onSelect(params: {
|
||||||
|
message: string;
|
||||||
|
options: { id: string; label: string }[];
|
||||||
|
}): Promise<string | undefined>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -122,13 +122,17 @@ export class LoginDialogComponent extends Container implements Focusable {
|
|||||||
this.contentContainer.addChild(new Spacer(1));
|
this.contentContainer.addChild(new Spacer(1));
|
||||||
this.contentContainer.addChild(new Text(theme.fg("warning", `Enter code: ${info.userCode}`), 1, 0));
|
this.contentContainer.addChild(new Text(theme.fg("warning", `Enter code: ${info.userCode}`), 1, 0));
|
||||||
|
|
||||||
// Do not open device-code URLs automatically. These flows need to work in headless environments.
|
this.openUrl(info.verificationUri);
|
||||||
this.tui.requestRender();
|
this.tui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
private openUrl(url: string): void {
|
private openUrl(url: string): void {
|
||||||
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
||||||
exec(`${openCmd} "${url}"`);
|
try {
|
||||||
|
exec(`${openCmd} "${url}"`, () => {});
|
||||||
|
} catch {
|
||||||
|
// Ignore browser launch failures. The URL remains visible for manual opening/copying.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user