Merge PR #3060: feat(provider): add GitLab Duo Agent provider (@jiwangyihao)

This commit is contained in:
can1357
2026-06-27 01:39:31 +02:00
34 changed files with 10243 additions and 11 deletions
@@ -28,6 +28,7 @@ import {
type OAuthCredential,
type OAuthProvider,
type OAuthProviderInfo,
PASTE_CODE_LOGIN_PROVIDERS,
PROVIDER_REGISTRY,
SqliteAuthCredentialStore,
} from "@oh-my-pi/pi-ai";
@@ -211,6 +212,15 @@ async function runLocalLogin(provider: OAuthProvider): Promise<void> {
const storage = new AuthStorage(store);
await storage.reload();
try {
// Only paste-code providers (fixed non-loopback redirect, e.g. GitLab Duo
// Agent's vscode:// URI) get the manual paste fallback. An explicit
// `onManualCodeInput` is honored for ANY provider (the storage escape hatch),
// so for loopback providers we must not pass it: it would make
// `OAuthCallbackFlow` race a readline prompt against the HTTP callback and, if
// the callback wins, leave that prompt outstanding (dirty/blocked terminal).
// `AuthStorage.login` independently refuses to synthesize the default prompt
// for non-paste-code providers, so this is defense-in-depth on the same gate.
const usesManualInput = PASTE_CODE_LOGIN_PROVIDERS.has(provider);
await storage.login(provider, {
onAuth({ url, instructions }) {
process.stdout.write(`\nOpen this URL in your browser:\n${url}\n`);
@@ -223,6 +233,13 @@ async function runLocalLogin(provider: OAuthProvider): Promise<void> {
onPrompt(p) {
return ask(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
},
...(usesManualInput
? {
onManualCodeInput() {
return ask("Paste the authorization code (or full redirect URL):");
},
}
: undefined),
});
process.stdout.write(`\nCredentials saved to ${getAgentDbPath()}\n`);
} finally {