mu-agent 0.2.2 → 0.2.3
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 +25 -0
- package/README.md +3 -7
- package/dist/bundle/chunks/anthropic.js +1 -1
- package/dist/bundle/chunks/chunk-FGOMFVJ2.js +2 -0
- package/dist/bundle/chunks/{chunk-BK7M6BDT.js → chunk-HPQUXAZK.js} +6 -6
- package/dist/bundle/chunks/openai-chatgpt.js +1 -1
- package/dist/bundle/chunks/openai-codex.js +2 -2
- package/dist/bundle/chunks/openrouter.js +1 -1
- package/dist/bundle/chunks/radius.js +1 -1
- package/dist/bundle/chunks/{virtual-modules-U6MRV5BC.js → virtual-modules-6BAN7L2H.js} +1 -1
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/judge/dist/auth.js +60 -15
- package/judge/dist/mude.js +624 -117
- package/judge/dist/setup.js +275 -146
- package/judge/manifest.json +14 -15
- package/judge/mu/{chunk-6XRUK3PM.js → chunk-JZ4X4HQO.js} +1 -1
- package/judge/mu/{cloud-code-XR7SZMJC.js → cloud-code-PDKLWJEG.js} +1 -1
- package/judge/mu/index.js +42 -24
- package/kyrn/bin/kyrn-doctor +0 -1
- package/kyrn/bin/kyrn-ledger +0 -2
- package/kyrn/bin/mu.mjs +50 -16
- package/package.json +2 -2
- package/dist/bundle/chunks/chunk-AN7MSYCK.js +0 -2
|
@@ -83,4 +83,4 @@ var __require=(x=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(x,{get:(
|
|
|
83
83
|
${details?`<div class="details">${details}</div>`:""}
|
|
84
84
|
</main>
|
|
85
85
|
</body>
|
|
86
|
-
</html>`}function oauthSuccessHtml(message){return renderPage({heading:"Signed in",message})}function oauthErrorHtml(message,details){return renderPage({heading:"Sign-in did not complete",message,details})}var procEnvCache=null;function getBunSandboxEnvValue(name){if(!(typeof process>"u"||!process.versions?.bun||Object.keys(process.env).length>0)){if(procEnvCache===null){procEnvCache=new Map;try{let{readFileSync}=__require("node:fs"),data=readFileSync("/proc/self/environ","utf-8");for(let entry of data.split("\0")){let idx=entry.indexOf("=");idx>0&&procEnvCache.set(entry.slice(0,idx),entry.slice(idx+1))}}catch{}}return procEnvCache.get(name)}}function getProviderEnvValue(name,env){return env?.[name]||(typeof process<"u"?process.env[name]:void 0)||getBunSandboxEnvValue(name)||void 0}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var DYNAMIC_CLIENT_ID="dynamic_agent_client",AGENT_NAME_HINT="Pi",UUID_PATTERN=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,AUTHORIZE_URL="https://auth.openai.com/api/accounts/authorize",TOKEN_URL="https://auth.openai.com/api/accounts/oauth/token",RESOURCE="https://api.openai.com/v1",CALLBACK_HOST=getProviderEnvValue("PI_OAUTH_CALLBACK_HOST")||"127.0.0.1",CALLBACK_PORT=1455,CALLBACK_PATH="/auth/callback",REDIRECT_URI=`http://127.0.0.1:${CALLBACK_PORT}${CALLBACK_PATH}`,DIRECT_TOKEN_SCOPE="chatgpt.tokens.use.direct",SCOPE=`openid profile email offline_access resource.invoke ${DIRECT_TOKEN_SCOPE}`,EXPIRY_MARGIN_MS=180*1e3;function randomValue(){return randomBytes(32).toString("base64url")}function authorizationResultFromCallback(url,expectedState){let code=url.searchParams.get("code");if(!code)throw new Error("Missing authorization code");let state=url.searchParams.get("state");if(!state)throw new Error("Missing OAuth state");if(state!==expectedState)throw new Error("OAuth state mismatch");let clientId=url.searchParams.get("client_id")?.trim();if(!clientId)throw new Error("OpenAI OAuth registration callback did not contain an issued client ID");return{code,clientId}}function authorizationResultFromManualInput(input,expectedState){let url;try{url=new URL(input.trim())}catch{throw new Error("Paste the full callback URL from the browser")}let expected=new URL(REDIRECT_URI);if(url.origin!==expected.origin||url.pathname!==expected.pathname)throw new Error(`The pasted callback URL must start with ${REDIRECT_URI}`);let error=url.searchParams.get("error");if(error)throw new Error(`ChatGPT authorization failed: ${error}`);return authorizationResultFromCallback(url,expectedState)}function sendHtml(response,status,body){response.writeHead(status,{"Content-Type":"text/html; charset=utf-8"}),response.end(body)}function startCallbackServer(expectedState){return new Promise((resolve,reject)=>{let resolveResult,rejectResult,result=new Promise((resolveAuthorization,rejectAuthorization)=>{resolveResult=resolveAuthorization,rejectResult=rejectAuthorization}),server=createServer((request,response)=>{try{let url=new URL(request.url||"",REDIRECT_URI);if(url.pathname!==CALLBACK_PATH){sendHtml(response,404,oauthErrorHtml("Callback route not found."));return}let error=url.searchParams.get("error");if(error){sendHtml(response,400,oauthErrorHtml("ChatGPT was not connected.",`Error: ${error}`)),rejectResult(new Error(`ChatGPT authorization failed: ${error}`));return}let authorizationResult;try{authorizationResult=authorizationResultFromCallback(url,expectedState)}catch(error2){let message=error2 instanceof Error?error2.message:"Invalid callback";sendHtml(response,400,oauthErrorHtml(message));return}sendHtml(response,200,oauthSuccessHtml("ChatGPT authentication completed. You can close this window.")),resolveResult(authorizationResult)}catch{sendHtml(response,500,oauthErrorHtml("Internal error while processing the callback."))}});server.once("error",reject),server.listen(CALLBACK_PORT,CALLBACK_HOST,()=>{server.removeListener("error",reject),server.on("error",rejectResult),resolve({server,result})})})}async function requestToken(body,signal){let response=await fetch(TOKEN_URL,{method:"POST",headers:{accept:"application/json","content-type":"application/x-www-form-urlencoded"},body,signal});if(!response.ok){let responseBody=await response.text().catch(()=>"");throw new Error(`OpenAI OAuth token request failed (${response.status}): ${responseBody||response.statusText}`)}let data=await response.json();if(typeof data!="object"||data===null||Array.isArray(data))throw new Error("OpenAI OAuth token response must be an object");return data}function requireTokenString(value,field){if(typeof value!="string"||value.trim().length===0)throw new Error(`OpenAI OAuth token response has invalid ${field}`);return value}function credentialFromTokenResponse(token,clientId){let access=requireTokenString(token.access_token,"access_token"),refresh=requireTokenString(token.refresh_token,"refresh_token"),scope=requireTokenString(token.scope,"scope");if(typeof token.expires_in!="number"||!Number.isFinite(token.expires_in)||token.expires_in<=0)throw new Error("OpenAI OAuth token response has invalid expires_in");let scopes=scope.trim().split(/\s+/).filter(Boolean);if(!scopes.includes(DIRECT_TOKEN_SCOPE))throw new Error(`OpenAI OAuth grant did not include ${DIRECT_TOKEN_SCOPE}`);return{type:"oauth",access,refresh,expires:Date.now()+token.expires_in*1e3-EXPIRY_MARGIN_MS,clientId,scopes}}async function exchangeAuthorizationCode(code,verifier,clientId,signal){let token=await requestToken(new URLSearchParams({grant_type:"authorization_code",client_id:clientId,code,code_verifier:verifier,redirect_uri:REDIRECT_URI,resource:RESOURCE}),signal);if(typeof token.id_token!="string"||token.id_token.trim().length===0)throw new Error("OpenAI OAuth token response did not contain an ID token");return credentialFromTokenResponse(token,clientId)}async function refreshAccessToken(credential,signal){let clientId=credential.clientId;if(typeof clientId!="string"||clientId.trim().length===0)throw new Error("Stored OpenAI OAuth credential does not contain an issued client ID; reconnect ChatGPT");let token=await requestToken(new URLSearchParams({grant_type:"refresh_token",client_id:clientId,refresh_token:credential.refresh,resource:RESOURCE}),signal);return credentialFromTokenResponse(token,clientId)}function agentHostId(deviceId){if(!deviceId||!UUID_PATTERN.test(deviceId))throw new Error("Sign in with ChatGPT requires a device ID (UUID) for this installation");return`urn:uuid:${deviceId.toLowerCase()}`}async function loginOpenAIChatGPT(interaction,options){let hostId=agentHostId(options?.getDeviceId?.()),{verifier,challenge}=await generatePKCE(),state=randomValue(),nonce=randomValue(),callback=await startCallbackServer(state).catch(error=>{throw error instanceof Error&&"code"in error&&error.code==="EADDRINUSE"?new Error(`Port ${CALLBACK_PORT} is in use, probably by an unfinished login in another pi session or by the Codex CLI. Cancel that login and try again.`):error}),authorizationUrl=new URL(AUTHORIZE_URL);authorizationUrl.search=new URLSearchParams({client_id:DYNAMIC_CLIENT_ID,agent_name_hint:options?.agentName??AGENT_NAME_HINT,ext_agent_host_id:hostId,response_type:"code",redirect_uri:REDIRECT_URI,resource:RESOURCE,scope:SCOPE,state,code_challenge:challenge,code_challenge_method:"S256",nonce}).toString(),interaction.notify({type:"auth_url",url:authorizationUrl.toString(),instructions:"Complete sign-in in your browser. If the callback does not complete, paste the final redirect URL here."});let manualAbort=new AbortController,manualCode=interaction.prompt({type:"manual_code",message:"Complete login in your browser, or paste the final redirect URL here:",placeholder:REDIRECT_URI,signal:AbortSignal.any([manualAbort.signal,interaction.signal])}).then(input=>authorizationResultFromManualInput(input,state));try{let result=await Promise.race([callback.result,manualCode]);return interaction.notify({type:"progress",message:"Exchanging authorization code for tokens..."}),await exchangeAuthorizationCode(result.code,verifier,result.clientId,interaction.signal)}catch(error){throw interaction.signal.aborted?new Error("Login cancelled"):error}finally{manualAbort.abort(),callback.server.close(),callback.server.closeAllConnections()}}var openaiChatGPTOAuth={name:"OpenAI (ChatGPT subscription)",isSubscription:!0,loginLabel:"Sign in with ChatGPT",login:loginOpenAIChatGPT,refresh:refreshAccessToken,async toAuth(credential){return{apiKey:credential.access}}};export{openaiChatGPTOAuth};
|
|
86
|
+
</html>`}function oauthSuccessHtml(message){return renderPage({heading:"Signed in",message})}function oauthErrorHtml(message,details){return renderPage({heading:"Sign-in did not complete",message,details})}var procEnvCache=null;function getBunSandboxEnvValue(name){if(!(typeof process>"u"||!process.versions?.bun||Object.keys(process.env).length>0)){if(procEnvCache===null){procEnvCache=new Map;try{let{readFileSync}=__require("node:fs"),data=readFileSync("/proc/self/environ","utf-8");for(let entry of data.split("\0")){let idx=entry.indexOf("=");idx>0&&procEnvCache.set(entry.slice(0,idx),entry.slice(idx+1))}}catch{}}return procEnvCache.get(name)}}function getProviderEnvValue(name,env){return env?.[name]||(typeof process<"u"?process.env[name]:void 0)||getBunSandboxEnvValue(name)||void 0}import{execFile}from"node:child_process";import{request}from"node:http";var LOGIN_PROBE_PATH="/pi-login",LOGIN_TAKE_OVER_PATH="/pi-login/take-over",CODEX_CANCEL_PATH="/cancel",MARKER_HEADER="x-pi-login",OLD_PI_NOT_FOUND="Callback route not found.",SETTLE_MS=2500,RETRY_MS=100,REQUEST_MS=1e3,LOOKUP_MS=2e3;function describeHolder(holder){let pid=holder?.pid?`process ${holder.pid}`:void 0;return holder?.command?pid?`${holder.command} (${pid})`:holder.command:pid??"another program"}var CallbackPortInUseError=class extends Error{code="port_in_use";port;holder;constructor(port,holder){super(holder?.pi?`Port ${port} is held by an unfinished sign-in in another pi or mu session (${describeHolder(holder)}), which did not hand it over. Finish or cancel that sign-in, then try again.`:`Port ${port} is in use by ${describeHolder(holder)}, probably for its own sign-in (the Codex CLI and the Codex app use this port). Finish or cancel that sign-in, then try again.`),this.name="CallbackPortInUseError",this.port=port,this.holder=holder}},LoginTakenOverError=class extends Error{code="login_taken_over";constructor(port,by){super(by==="codex"?`Sign-in stopped: the Codex CLI started its own sign-in on port ${port}.`:`Sign-in stopped: a newer sign-in started and took over port ${port}.`),this.name="LoginTakenOverError"}};function answerLoginControl(request2,response,takeOver){let path=new URL(request2.url??"/","http://localhost").pathname,fromBrowser=request2.headers.origin!==void 0||request2.headers["sec-fetch-site"]!==void 0;if(request2.method==="GET"&&path===LOGIN_PROBE_PATH)return response.writeHead(200,{"content-type":"application/json","cache-control":"no-store",[MARKER_HEADER]:"pi"}),response.end(JSON.stringify({pi:!0,pid:process.pid})),!0;let ours=request2.method==="POST"&&path===LOGIN_TAKE_OVER_PATH&&request2.headers[MARKER_HEADER]==="take-over"&&!fromBrowser,codex=request2.method==="GET"&&path===CODEX_CANCEL_PATH&&!fromBrowser;return!ours&&!codex?!1:(response.once("close",()=>takeOver(ours?"pi":"codex")),response.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-store"}),response.end("Login cancelled"),!0)}function closeCallbackServer(server){server.close(),server.closeAllConnections()}function localRequest(host,port,method,path,headers){return new Promise(resolve=>{let outgoing=request({host,port,method,path,headers,timeout:REQUEST_MS,agent:!1},incoming=>{let body="";incoming.setEncoding("utf8"),incoming.on("data",chunk=>{body.length<16384&&(body+=chunk)}),incoming.on("end",()=>resolve({status:incoming.statusCode??0,headers:incoming.headers,body})),incoming.on("error",()=>resolve({status:incoming.statusCode??0,headers:incoming.headers,body}))});outgoing.on("timeout",()=>outgoing.destroy(new Error("timeout"))),outgoing.on("error",error=>resolve({error:error.code??error.message})),outgoing.end()})}async function probeHolder(host,port){let answer=await localRequest(host,port,"GET",LOGIN_PROBE_PATH,{[MARKER_HEADER]:"probe"});if("error"in answer)return answer.error==="ECONNREFUSED"?{kind:"none"}:{kind:"other"};if(answer.status===200&&answer.headers[MARKER_HEADER]==="pi")try{let pid=JSON.parse(answer.body).pid;return{kind:"pi",pid:typeof pid=="number"?pid:void 0}}catch{return{kind:"pi"}}return answer.status===404&&answer.body.includes(OLD_PI_NOT_FOUND)?{kind:"old-pi"}:{kind:"other"}}function tryListen(server,port,host){return new Promise(resolve=>{let onError=error=>{server.off("listening",onListening),resolve(error)},onListening=()=>{server.off("error",onError),resolve(void 0)};server.once("error",onError),server.once("listening",onListening),server.listen(port,host)})}function cancelled(){return new Error("Login cancelled")}function pause(ms,signal){return new Promise((resolve,reject)=>{if(signal?.aborted){reject(cancelled());return}let onAbort=()=>{clearTimeout(timer),reject(cancelled())},timer=setTimeout(()=>{signal?.removeEventListener("abort",onAbort),resolve()},ms);signal?.addEventListener("abort",onAbort,{once:!0})})}async function listenOnLoginPort(server,options){let{port,host,signal}=options,settleMs=options.settleMs??SETTLE_MS,deadline=Date.now()+settleMs,askedToLetGo=!1,probe;for(;;){if(signal?.aborted)throw cancelled();let error=await tryListen(server,port,host);if(!error){if(signal?.aborted)throw closeCallbackServer(server),cancelled();return}if(error.code!=="EADDRINUSE")throw error;if(!askedToLetGo&&probe?.kind!=="other"&&probe?.kind!=="old-pi"&&(probe=await probeHolder(host,port),probe.kind==="pi"&&(await localRequest(host,port,"POST",LOGIN_TAKE_OVER_PATH,{[MARKER_HEADER]:"take-over"}),askedToLetGo=!0,deadline=Date.now()+settleMs)),Date.now()>=deadline)break;await pause(RETRY_MS,signal)}let found=await(options.findHolder??findPortHolder)(port).catch(()=>{}),holder={...found,pid:found?.pid??(probe?.kind==="pi"?probe.pid:void 0),...probe?.kind==="pi"||probe?.kind==="old-pi"?{pi:!0}:{}};throw new CallbackPortInUseError(port,holder.pid||holder.command||holder.pi?holder:void 0)}function run(command,args){return new Promise(resolve=>{execFile(command,args,{timeout:LOOKUP_MS,windowsHide:!0,encoding:"utf8"},(error,stdout)=>{resolve(error&&error.code==="ENOENT"?void 0:String(stdout??""))})})}function parseLsof(output){let holder;for(let line of output.split(/\r?\n/))if(line.startsWith("p")){if(holder)break;let pid=Number(line.slice(1));holder=Number.isInteger(pid)&&pid>0?{pid}:{}}else line.startsWith("c")&&holder&&(holder.command=line.slice(1));return holder}function parseSs(output){let match=/users:\(\("([^"]*)",pid=(\d+)/.exec(output);return match?{command:match[1],pid:Number(match[2])}:void 0}function parseNetstat(output,port){for(let line of output.split(/\r?\n/)){let columns=line.trim().split(/\s+/);if(columns.length<5||columns[0]?.toUpperCase()!=="TCP"||!columns[1]?.endsWith(`:${port}`)||columns[3]?.toUpperCase()!=="LISTENING")continue;let pid=Number(columns[4]);if(Number.isInteger(pid)&&pid>0)return pid}}function parseTasklist(output){return/^"([^"]+)"/.exec(output.trim())?.[1]?.replace(/\.exe$/i,"")}async function findPortHolder(port){if(process.platform==="win32"){let pid=parseNetstat(await run("netstat",["-ano","-p","TCP"])??"",port);if(!pid)return;let command=parseTasklist(await run("tasklist",["/FI",`PID eq ${pid}`,"/FO","CSV","/NH"])??"");return command?{pid,command}:{pid}}for(let lsof of["lsof","/usr/sbin/lsof","/usr/bin/lsof"]){let output=await run(lsof,["-nP",`-iTCP:${port}`,"-sTCP:LISTEN","-Fpc"]);if(output!==void 0)return parseLsof(output)}if(process.platform==="linux")return parseSs(await run("ss",["-Hltnp",`sport = :${port}`])??"")}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var DYNAMIC_CLIENT_ID="dynamic_agent_client",AGENT_NAME_HINT="Pi",UUID_PATTERN=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,AUTHORIZE_URL="https://auth.openai.com/api/accounts/authorize",TOKEN_URL="https://auth.openai.com/api/accounts/oauth/token",RESOURCE="https://api.openai.com/v1",CALLBACK_HOST=getProviderEnvValue("PI_OAUTH_CALLBACK_HOST")||"127.0.0.1",CALLBACK_PORT=1455,CALLBACK_PATH="/auth/callback",REDIRECT_URI=`http://127.0.0.1:${CALLBACK_PORT}${CALLBACK_PATH}`,DIRECT_TOKEN_SCOPE="chatgpt.tokens.use.direct",SCOPE=`openid profile email offline_access resource.invoke ${DIRECT_TOKEN_SCOPE}`,EXPIRY_MARGIN_MS=180*1e3,CALLBACK_TIMEOUT_MS=900*1e3;function randomValue(){return randomBytes(32).toString("base64url")}function authorizationResultFromCallback(url,expectedState){let code=url.searchParams.get("code");if(!code)throw new Error("Missing authorization code");let state=url.searchParams.get("state");if(!state)throw new Error("Missing OAuth state");if(state!==expectedState)throw new Error("OAuth state mismatch");let clientId=url.searchParams.get("client_id")?.trim();if(!clientId)throw new Error("OpenAI OAuth registration callback did not contain an issued client ID");return{code,clientId}}function authorizationResultFromManualInput(input,expectedState){let url;try{url=new URL(input.trim())}catch{throw new Error("Paste the full callback URL from the browser")}let expected=new URL(REDIRECT_URI);if(url.origin!==expected.origin||url.pathname!==expected.pathname)throw new Error(`The pasted callback URL must start with ${REDIRECT_URI}`);let error=url.searchParams.get("error");if(error)throw new Error(`ChatGPT authorization failed: ${error}`);return authorizationResultFromCallback(url,expectedState)}function sendHtml(response,status,body){response.writeHead(status,{"Content-Type":"text/html; charset=utf-8"}),response.end(body)}async function startCallbackServer(expectedState,signal){let resolveResult,rejectResult,result=new Promise((resolveAuthorization,rejectAuthorization)=>{resolveResult=resolveAuthorization,rejectResult=rejectAuthorization});result.catch(()=>{});let server=createServer((request2,response)=>{try{if(answerLoginControl(request2,response,by=>{rejectResult(new LoginTakenOverError(CALLBACK_PORT,by)),closeCallbackServer(server)}))return;let url=new URL(request2.url||"",REDIRECT_URI);if(url.pathname!==CALLBACK_PATH){sendHtml(response,404,oauthErrorHtml("Callback route not found."));return}let error=url.searchParams.get("error");if(error){sendHtml(response,400,oauthErrorHtml("ChatGPT was not connected.",`Error: ${error}`)),rejectResult(new Error(`ChatGPT authorization failed: ${error}`));return}let authorizationResult;try{authorizationResult=authorizationResultFromCallback(url,expectedState)}catch(error2){let message=error2 instanceof Error?error2.message:"Invalid callback";sendHtml(response,400,oauthErrorHtml(message));return}sendHtml(response,200,oauthSuccessHtml("ChatGPT authentication completed. You can close this window.")),resolveResult(authorizationResult)}catch{sendHtml(response,500,oauthErrorHtml("Internal error while processing the callback."))}});await listenOnLoginPort(server,{port:CALLBACK_PORT,host:CALLBACK_HOST,signal}),server.on("error",rejectResult);let timer=setTimeout(()=>{rejectResult(new Error("Sign in with ChatGPT timed out: the browser did not come back within 15 minutes")),closeCallbackServer(server)},CALLBACK_TIMEOUT_MS);return timer.unref?.(),server.once("close",()=>clearTimeout(timer)),{server,result}}async function requestToken(body,signal){let response=await fetch(TOKEN_URL,{method:"POST",headers:{accept:"application/json","content-type":"application/x-www-form-urlencoded"},body,signal});if(!response.ok){let responseBody=await response.text().catch(()=>"");throw new Error(`OpenAI OAuth token request failed (${response.status}): ${responseBody||response.statusText}`)}let data=await response.json();if(typeof data!="object"||data===null||Array.isArray(data))throw new Error("OpenAI OAuth token response must be an object");return data}function requireTokenString(value,field){if(typeof value!="string"||value.trim().length===0)throw new Error(`OpenAI OAuth token response has invalid ${field}`);return value}function credentialFromTokenResponse(token,clientId){let access=requireTokenString(token.access_token,"access_token"),refresh=requireTokenString(token.refresh_token,"refresh_token"),scope=requireTokenString(token.scope,"scope");if(typeof token.expires_in!="number"||!Number.isFinite(token.expires_in)||token.expires_in<=0)throw new Error("OpenAI OAuth token response has invalid expires_in");let scopes=scope.trim().split(/\s+/).filter(Boolean);if(!scopes.includes(DIRECT_TOKEN_SCOPE))throw new Error(`OpenAI OAuth grant did not include ${DIRECT_TOKEN_SCOPE}`);return{type:"oauth",access,refresh,expires:Date.now()+token.expires_in*1e3-EXPIRY_MARGIN_MS,clientId,scopes}}async function exchangeAuthorizationCode(code,verifier,clientId,signal){let token=await requestToken(new URLSearchParams({grant_type:"authorization_code",client_id:clientId,code,code_verifier:verifier,redirect_uri:REDIRECT_URI,resource:RESOURCE}),signal);if(typeof token.id_token!="string"||token.id_token.trim().length===0)throw new Error("OpenAI OAuth token response did not contain an ID token");return credentialFromTokenResponse(token,clientId)}async function refreshAccessToken(credential,signal){let clientId=credential.clientId;if(typeof clientId!="string"||clientId.trim().length===0)throw new Error("Stored OpenAI OAuth credential does not contain an issued client ID; reconnect ChatGPT");let token=await requestToken(new URLSearchParams({grant_type:"refresh_token",client_id:clientId,refresh_token:credential.refresh,resource:RESOURCE}),signal);return credentialFromTokenResponse(token,clientId)}function agentHostId(deviceId){if(!deviceId||!UUID_PATTERN.test(deviceId))throw new Error("Sign in with ChatGPT requires a device ID (UUID) for this installation");return`urn:uuid:${deviceId.toLowerCase()}`}async function loginOpenAIChatGPT(interaction,options){let hostId=agentHostId(options?.getDeviceId?.()),{verifier,challenge}=await generatePKCE(),state=randomValue(),nonce=randomValue(),callback=options?.pasteOnly?void 0:await startCallbackServer(state,interaction.signal),manualAbort=new AbortController,onAbort;try{let authorizationUrl=new URL(AUTHORIZE_URL);authorizationUrl.search=new URLSearchParams({client_id:DYNAMIC_CLIENT_ID,agent_name_hint:options?.agentName??AGENT_NAME_HINT,ext_agent_host_id:hostId,response_type:"code",redirect_uri:REDIRECT_URI,resource:RESOURCE,scope:SCOPE,state,code_challenge:challenge,code_challenge_method:"S256",nonce}).toString(),interaction.notify({type:"auth_url",url:authorizationUrl.toString(),instructions:callback?"Complete sign-in in your browser. If the callback does not complete, paste the final redirect URL here.":`Complete sign-in in your browser, then paste the address it ends on (it starts with ${REDIRECT_URI}; the page itself may show an error).`});let manualCode=interaction.prompt({type:"manual_code",message:callback?"Complete login in your browser, or paste the final redirect URL here:":"Paste the address your browser ends on after sign-in:",placeholder:REDIRECT_URI,signal:AbortSignal.any([manualAbort.signal,interaction.signal])}).then(input=>authorizationResultFromManualInput(input,state)),aborted=new Promise((_resolve,reject)=>{onAbort=()=>reject(new Error("Login cancelled")),interaction.signal.addEventListener("abort",onAbort,{once:!0})});if(manualCode.catch(()=>{}),aborted.catch(()=>{}),interaction.signal.aborted)throw new Error("Login cancelled");let result=await Promise.race([...callback?[callback.result]:[],manualCode,aborted]);return callback?.server.close(),interaction.notify({type:"progress",message:"Exchanging authorization code for tokens..."}),await exchangeAuthorizationCode(result.code,verifier,result.clientId,interaction.signal)}catch(error){throw interaction.signal.aborted?new Error("Login cancelled"):error}finally{onAbort&&interaction.signal.removeEventListener("abort",onAbort),manualAbort.abort(),callback&&closeCallbackServer(callback.server)}}var openaiChatGPTOAuth={name:"OpenAI (ChatGPT subscription)",isSubscription:!0,loginLabel:"Sign in with ChatGPT",login:loginOpenAIChatGPT,refresh:refreshAccessToken,async toAuth(credential){return{apiKey:credential.access}}};export{openaiChatGPTOAuth};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url);
|
|
2
|
-
var __require=(x=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(x,{get:(a,b)=>(typeof require<"u"?require:a)[b]}):x)(function(x){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});var procEnvCache=null;function getBunSandboxEnvValue(name){if(!(typeof process>"u"||!process.versions?.bun||Object.keys(process.env).length>0)){if(procEnvCache===null){procEnvCache=new Map;try{let{readFileSync}=__require("node:fs"),data=readFileSync("/proc/self/environ","utf-8");for(let entry of data.split("\0")){let idx=entry.indexOf("=");idx>0&&procEnvCache.set(entry.slice(0,idx),entry.slice(idx+1))}}catch{}}return procEnvCache.get(name)}}function getProviderEnvValue(name,env){return env?.[name]||(typeof process<"u"?process.env[name]:void 0)||getBunSandboxEnvValue(name)||void 0}import{createServer}from"node:http";var MU_MARK_SVG='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none" aria-hidden="true"><defs><linearGradient id="mu-tile" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#F3C6FA"/><stop offset="0.5" stop-color="#B79CF7"/><stop offset="1" stop-color="#8680EB"/></linearGradient></defs><rect width="100" height="100" rx="27" fill="url(#mu-tile)"/><path d="M41 32 L35 74.4 M38.9 47 C37.77 55 41 59.4 47.6 59.4 C54.8 59.4 61.05 54.5 61.9 48.5 M64.25 32 L61.35 52.5 C60.6 58 62.3 61.2 66.4 60.4" stroke="#FFFFFF" stroke-width="9.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',MU_FAVICON=`data:image/svg+xml,${encodeURIComponent(MU_MARK_SVG)}`;function escapeHtml(value){return value.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function renderPage(options){let title=escapeHtml(`${options.heading} \xB7 mu`),heading=escapeHtml(options.heading),message=escapeHtml(options.message),details=options.details?escapeHtml(options.details):void 0;return`<!doctype html>
|
|
2
|
+
var __require=(x=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(x,{get:(a,b)=>(typeof require<"u"?require:a)[b]}):x)(function(x){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});var procEnvCache=null;function getBunSandboxEnvValue(name){if(!(typeof process>"u"||!process.versions?.bun||Object.keys(process.env).length>0)){if(procEnvCache===null){procEnvCache=new Map;try{let{readFileSync}=__require("node:fs"),data=readFileSync("/proc/self/environ","utf-8");for(let entry of data.split("\0")){let idx=entry.indexOf("=");idx>0&&procEnvCache.set(entry.slice(0,idx),entry.slice(idx+1))}}catch{}}return procEnvCache.get(name)}}function getProviderEnvValue(name,env){return env?.[name]||(typeof process<"u"?process.env[name]:void 0)||getBunSandboxEnvValue(name)||void 0}import{execFile}from"node:child_process";import{request}from"node:http";var LOGIN_PROBE_PATH="/pi-login",LOGIN_TAKE_OVER_PATH="/pi-login/take-over",CODEX_CANCEL_PATH="/cancel",MARKER_HEADER="x-pi-login",OLD_PI_NOT_FOUND="Callback route not found.",SETTLE_MS=2500,RETRY_MS=100,REQUEST_MS=1e3,LOOKUP_MS=2e3;function describeHolder(holder){let pid=holder?.pid?`process ${holder.pid}`:void 0;return holder?.command?pid?`${holder.command} (${pid})`:holder.command:pid??"another program"}var CallbackPortInUseError=class extends Error{code="port_in_use";port;holder;constructor(port,holder){super(holder?.pi?`Port ${port} is held by an unfinished sign-in in another pi or mu session (${describeHolder(holder)}), which did not hand it over. Finish or cancel that sign-in, then try again.`:`Port ${port} is in use by ${describeHolder(holder)}, probably for its own sign-in (the Codex CLI and the Codex app use this port). Finish or cancel that sign-in, then try again.`),this.name="CallbackPortInUseError",this.port=port,this.holder=holder}},LoginTakenOverError=class extends Error{code="login_taken_over";constructor(port,by){super(by==="codex"?`Sign-in stopped: the Codex CLI started its own sign-in on port ${port}.`:`Sign-in stopped: a newer sign-in started and took over port ${port}.`),this.name="LoginTakenOverError"}};function answerLoginControl(request2,response,takeOver){let path=new URL(request2.url??"/","http://localhost").pathname,fromBrowser=request2.headers.origin!==void 0||request2.headers["sec-fetch-site"]!==void 0;if(request2.method==="GET"&&path===LOGIN_PROBE_PATH)return response.writeHead(200,{"content-type":"application/json","cache-control":"no-store",[MARKER_HEADER]:"pi"}),response.end(JSON.stringify({pi:!0,pid:process.pid})),!0;let ours=request2.method==="POST"&&path===LOGIN_TAKE_OVER_PATH&&request2.headers[MARKER_HEADER]==="take-over"&&!fromBrowser,codex=request2.method==="GET"&&path===CODEX_CANCEL_PATH&&!fromBrowser;return!ours&&!codex?!1:(response.once("close",()=>takeOver(ours?"pi":"codex")),response.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-store"}),response.end("Login cancelled"),!0)}function closeCallbackServer(server){server.close(),server.closeAllConnections()}function localRequest(host,port,method,path,headers){return new Promise(resolve=>{let outgoing=request({host,port,method,path,headers,timeout:REQUEST_MS,agent:!1},incoming=>{let body="";incoming.setEncoding("utf8"),incoming.on("data",chunk=>{body.length<16384&&(body+=chunk)}),incoming.on("end",()=>resolve({status:incoming.statusCode??0,headers:incoming.headers,body})),incoming.on("error",()=>resolve({status:incoming.statusCode??0,headers:incoming.headers,body}))});outgoing.on("timeout",()=>outgoing.destroy(new Error("timeout"))),outgoing.on("error",error=>resolve({error:error.code??error.message})),outgoing.end()})}async function probeHolder(host,port){let answer=await localRequest(host,port,"GET",LOGIN_PROBE_PATH,{[MARKER_HEADER]:"probe"});if("error"in answer)return answer.error==="ECONNREFUSED"?{kind:"none"}:{kind:"other"};if(answer.status===200&&answer.headers[MARKER_HEADER]==="pi")try{let pid=JSON.parse(answer.body).pid;return{kind:"pi",pid:typeof pid=="number"?pid:void 0}}catch{return{kind:"pi"}}return answer.status===404&&answer.body.includes(OLD_PI_NOT_FOUND)?{kind:"old-pi"}:{kind:"other"}}function tryListen(server,port,host){return new Promise(resolve=>{let onError=error=>{server.off("listening",onListening),resolve(error)},onListening=()=>{server.off("error",onError),resolve(void 0)};server.once("error",onError),server.once("listening",onListening),server.listen(port,host)})}function cancelled(){return new Error("Login cancelled")}function pause(ms,signal){return new Promise((resolve,reject)=>{if(signal?.aborted){reject(cancelled());return}let onAbort=()=>{clearTimeout(timer),reject(cancelled())},timer=setTimeout(()=>{signal?.removeEventListener("abort",onAbort),resolve()},ms);signal?.addEventListener("abort",onAbort,{once:!0})})}async function listenOnLoginPort(server,options){let{port,host,signal}=options,settleMs=options.settleMs??SETTLE_MS,deadline=Date.now()+settleMs,askedToLetGo=!1,probe;for(;;){if(signal?.aborted)throw cancelled();let error=await tryListen(server,port,host);if(!error){if(signal?.aborted)throw closeCallbackServer(server),cancelled();return}if(error.code!=="EADDRINUSE")throw error;if(!askedToLetGo&&probe?.kind!=="other"&&probe?.kind!=="old-pi"&&(probe=await probeHolder(host,port),probe.kind==="pi"&&(await localRequest(host,port,"POST",LOGIN_TAKE_OVER_PATH,{[MARKER_HEADER]:"take-over"}),askedToLetGo=!0,deadline=Date.now()+settleMs)),Date.now()>=deadline)break;await pause(RETRY_MS,signal)}let found=await(options.findHolder??findPortHolder)(port).catch(()=>{}),holder={...found,pid:found?.pid??(probe?.kind==="pi"?probe.pid:void 0),...probe?.kind==="pi"||probe?.kind==="old-pi"?{pi:!0}:{}};throw new CallbackPortInUseError(port,holder.pid||holder.command||holder.pi?holder:void 0)}function run(command,args){return new Promise(resolve=>{execFile(command,args,{timeout:LOOKUP_MS,windowsHide:!0,encoding:"utf8"},(error,stdout)=>{resolve(error&&error.code==="ENOENT"?void 0:String(stdout??""))})})}function parseLsof(output){let holder;for(let line of output.split(/\r?\n/))if(line.startsWith("p")){if(holder)break;let pid=Number(line.slice(1));holder=Number.isInteger(pid)&&pid>0?{pid}:{}}else line.startsWith("c")&&holder&&(holder.command=line.slice(1));return holder}function parseSs(output){let match=/users:\(\("([^"]*)",pid=(\d+)/.exec(output);return match?{command:match[1],pid:Number(match[2])}:void 0}function parseNetstat(output,port){for(let line of output.split(/\r?\n/)){let columns=line.trim().split(/\s+/);if(columns.length<5||columns[0]?.toUpperCase()!=="TCP"||!columns[1]?.endsWith(`:${port}`)||columns[3]?.toUpperCase()!=="LISTENING")continue;let pid=Number(columns[4]);if(Number.isInteger(pid)&&pid>0)return pid}}function parseTasklist(output){return/^"([^"]+)"/.exec(output.trim())?.[1]?.replace(/\.exe$/i,"")}async function findPortHolder(port){if(process.platform==="win32"){let pid=parseNetstat(await run("netstat",["-ano","-p","TCP"])??"",port);if(!pid)return;let command=parseTasklist(await run("tasklist",["/FI",`PID eq ${pid}`,"/FO","CSV","/NH"])??"");return command?{pid,command}:{pid}}for(let lsof of["lsof","/usr/sbin/lsof","/usr/bin/lsof"]){let output=await run(lsof,["-nP",`-iTCP:${port}`,"-sTCP:LISTEN","-Fpc"]);if(output!==void 0)return parseLsof(output)}if(process.platform==="linux")return parseSs(await run("ss",["-Hltnp",`sport = :${port}`])??"")}import{createServer}from"node:http";var MU_MARK_SVG='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none" aria-hidden="true"><defs><linearGradient id="mu-tile" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#F3C6FA"/><stop offset="0.5" stop-color="#B79CF7"/><stop offset="1" stop-color="#8680EB"/></linearGradient></defs><rect width="100" height="100" rx="27" fill="url(#mu-tile)"/><path d="M41 32 L35 74.4 M38.9 47 C37.77 55 41 59.4 47.6 59.4 C54.8 59.4 61.05 54.5 61.9 48.5 M64.25 32 L61.35 52.5 C60.6 58 62.3 61.2 66.4 60.4" stroke="#FFFFFF" stroke-width="9.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',MU_FAVICON=`data:image/svg+xml,${encodeURIComponent(MU_MARK_SVG)}`;function escapeHtml(value){return value.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function renderPage(options){let title=escapeHtml(`${options.heading} \xB7 mu`),heading=escapeHtml(options.heading),message=escapeHtml(options.message),details=options.details?escapeHtml(options.details):void 0;return`<!doctype html>
|
|
3
3
|
<html lang="en">
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="utf-8" />
|
|
@@ -83,4 +83,4 @@ var __require=(x=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(x,{get:(
|
|
|
83
83
|
${details?`<div class="details">${details}</div>`:""}
|
|
84
84
|
</main>
|
|
85
85
|
</body>
|
|
86
|
-
</html>`}function oauthSuccessHtml(message){return renderPage({heading:"Signed in",message})}function oauthErrorHtml(message,details){return renderPage({heading:"Sign-in did not complete",message,details})}function sendPage(response,status,html){response.writeHead(status,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),response.end(html)}async function startOAuthCallbackServer(options){let{providerName,signal}=options;if(signal?.aborted)throw new Error("Login cancelled");let resolveWait=()=>{},rejectWait=()=>{},waitPromise=new Promise((resolve,reject)=>{resolveWait=resolve,rejectWait=reject});waitPromise.catch(()=>{});let claimed=!1,settled=!1,timer,onAbort=()=>finish({error:new Error("Login cancelled")}),finish=result=>{settled||(settled=!0,timer&&clearTimeout(timer),signal?.removeEventListener("abort",onAbort),"error"in result?rejectWait(result.error):resolveWait(result.value))},server=createServer((request,response)=>{(async()=>{let url=new URL(request.url??"/","http://localhost");if(request.method!=="GET"||url.pathname!==options.path){sendPage(response,404,oauthErrorHtml("Callback route not found."));return}if(options.state!==void 0&&url.searchParams.get("state")!==options.state){sendPage(response,400,oauthErrorHtml("State mismatch."));return}if(claimed||settled){sendPage(response,409,oauthErrorHtml("This sign-in has already been handled."));return}let error=url.searchParams.get("error");if(error){let description=url.searchParams.get("error_description")??error;sendPage(response,400,oauthErrorHtml(`${providerName} authorization failed.`,description)),finish({error:new Error(`${providerName} authorization failed: ${description}`)});return}let code=url.searchParams.get("code");if(!code){sendPage(response,400,oauthErrorHtml("Missing authorization code."));return}claimed=!0;try{let value=await options.complete(code);sendPage(response,200,oauthSuccessHtml(`Signed in to ${providerName}. You may now close this page.`)),finish({value})}catch(error2){let failure=error2 instanceof Error?error2:new Error(String(error2));sendPage(response,502,oauthErrorHtml(`${providerName} sign-in failed.`,failure.message)),finish({error:failure})}})()});await new Promise((resolve,reject)=>{server.once("error",reject),server.listen(options.port,options.host,()=>{server.off("error",reject),resolve()})});let address=server.address();if(!address||typeof address=="string")throw server.close(),new Error("OAuth callback server did not bind to TCP");server.on("error",error=>finish({error})),signal?.addEventListener("abort",onAbort,{once:!0}),options.timeoutMs!==void 0&&(timer=setTimeout(()=>finish({error:new Error(`${providerName} sign-in timed out`)}),options.timeoutMs));let redirectHost=options.redirectHost??options.host;return{redirectUri:`http://${redirectHost.includes(":")?`[${redirectHost}]`:redirectHost}:${address.port}${options.path}`,wait:()=>waitPromise,cancel:()=>{claimed||finish({value:void 0})},close:()=>{finish({error:new Error("OAuth callback server closed")}),server.close()}}}async function waitForCallbackOrManualInput(interaction,callback,prompt){let manualAbort=new AbortController,manualError,manual=interaction.prompt({type:"manual_code",...prompt,signal:manualAbort.signal}).then(input=>(callback?.cancel(),input)).catch(error=>{manualError=error instanceof Error?error:new Error(String(error)),callback?.cancel()});try{let value=await callback?.wait();if(manualError)throw manualError;if(value!==void 0)return{type:"callback",value};let input=await manual;if(manualError)throw manualError;return{type:"manual",input:input??""}}finally{manualAbort.abort()}}var CANCEL_MESSAGE="Login cancelled",TIMEOUT_MESSAGE="Device flow timed out",SLOW_DOWN_TIMEOUT_MESSAGE="Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.";function abortableSleep(ms,signal,cancelMessage){return new Promise((resolve,reject)=>{if(signal.aborted){reject(new Error(cancelMessage));return}let onAbort=()=>{clearTimeout(timeout),reject(new Error(cancelMessage))},timeout=setTimeout(()=>{signal.removeEventListener("abort",onAbort),resolve()},ms);signal.addEventListener("abort",onAbort,{once:!0})})}async function pollOAuthDeviceCodeFlow(options){let deadline=typeof options.expiresInSeconds=="number"?Date.now()+options.expiresInSeconds*1e3:Number.POSITIVE_INFINITY,intervalMs=Math.max(1e3,Math.floor((options.intervalSeconds??5)*1e3)),slowDownResponses=0;if(options.waitBeforeFirstPoll){let remainingMs=deadline-Date.now();remainingMs>0&&await abortableSleep(Math.min(intervalMs,remainingMs),options.signal,CANCEL_MESSAGE)}for(;Date.now()<deadline;){if(options.signal.aborted)throw new Error(CANCEL_MESSAGE);let result=await options.poll();if(result.status==="complete")return result.value;if(result.status==="failed")throw new Error(result.message);result.status==="slow_down"&&(slowDownResponses+=1,intervalMs=typeof result.intervalSeconds=="number"&&Number.isFinite(result.intervalSeconds)&&result.intervalSeconds>0?Math.max(1e3,Math.floor(result.intervalSeconds*1e3)):Math.max(1e3,intervalMs+5e3));let remainingMs=deadline-Date.now();if(remainingMs<=0)break;await abortableSleep(Math.min(intervalMs,remainingMs),options.signal,CANCEL_MESSAGE)}throw new Error(slowDownResponses>0?SLOW_DOWN_TIMEOUT_MESSAGE:TIMEOUT_MESSAGE)}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var _randomBytes=null;typeof process<"u"&&(process.versions?.node||process.versions?.bun)&&import("node:crypto").then(m=>{_randomBytes=m.randomBytes});var CLIENT_ID="app_EMoamEEZ73f0CkXaXp7hrann",AUTH_BASE_URL="https://auth.openai.com",AUTHORIZE_URL=`${AUTH_BASE_URL}/oauth/authorize`,TOKEN_URL=`${AUTH_BASE_URL}/oauth/token`,REDIRECT_URI="http://localhost:1455/auth/callback",DEVICE_USER_CODE_URL=`${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`,DEVICE_TOKEN_URL=`${AUTH_BASE_URL}/api/accounts/deviceauth/token`,DEVICE_VERIFICATION_URI=`${AUTH_BASE_URL}/codex/device`,DEVICE_REDIRECT_URI=`${AUTH_BASE_URL}/deviceauth/callback`,DEVICE_CODE_TIMEOUT_SECONDS=900,OPENAI_CODEX_BROWSER_LOGIN_METHOD="browser",OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD="device_code",SCOPE="openid profile email offline_access",JWT_CLAIM_PATH="https://api.openai.com/auth";function getCallbackHost(){return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST")||"127.0.0.1"}function createState(){if(!_randomBytes)throw new Error("OpenAI Codex OAuth is only available in Node.js environments");return _randomBytes(16).toString("hex")}function parseAuthorizationInput(input){let value=input.trim();if(!value)return{};try{let url=new URL(value);return{code:url.searchParams.get("code")??void 0,state:url.searchParams.get("state")??void 0}}catch{}if(value.includes("#")){let[code,state]=value.split("#",2);return{code,state}}if(value.includes("code=")){let params=new URLSearchParams(value);return{code:params.get("code")??void 0,state:params.get("state")??void 0}}return{code:value}}function decodeJwt(token){try{let parts=token.split(".");if(parts.length!==3)return null;let payload=parts[1]??"",decoded=atob(payload);return JSON.parse(decoded)}catch{return null}}async function fetchWithLoginCancellation(input,init){try{return await fetch(input,init)}catch(error){throw init.signal?.aborted?new Error("Login cancelled"):error}}async function readTokenResponse(response,operation){if(!response.ok){let text=await response.text().catch(()=>"");throw new Error(`OpenAI Codex token ${operation} failed (${response.status}): ${text||response.statusText}`)}let json=await response.json();if(!json?.access_token||!json.refresh_token||typeof json.expires_in!="number")throw new Error(`OpenAI Codex token ${operation} response missing fields: ${JSON.stringify(json)}`);return{access:json.access_token,refresh:json.refresh_token,expires:Date.now()+json.expires_in*1e3}}async function exchangeAuthorizationCode(code,verifier,redirectUri,signal){let response=await fetchWithLoginCancellation(TOKEN_URL,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:CLIENT_ID,code,code_verifier:verifier,redirect_uri:redirectUri}),signal});return readTokenResponse(response,"exchange")}async function refreshAccessToken(refreshToken,signal){let response;try{response=await fetch(TOKEN_URL,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:refreshToken,client_id:CLIENT_ID}),signal})}catch(error){throw new Error(`OpenAI Codex token refresh error: ${error instanceof Error?error.message:String(error)}`)}return readTokenResponse(response,"refresh")}async function startOpenAICodexDeviceAuth(signal){let response=await fetchWithLoginCancellation(DEVICE_USER_CODE_URL,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_id:CLIENT_ID}),signal});if(!response.ok){if(response.status===404)throw new Error("OpenAI Codex device code login is not enabled for this server. Use browser login or verify the server URL.");let responseBody=await response.text().catch(()=>"");throw new Error(`OpenAI Codex device code request failed with status ${response.status}${responseBody?`: ${responseBody}`:""}`)}let json=await response.json(),intervalSeconds=typeof json?.interval=="string"?Number(json.interval.trim()):json?.interval;if(!json?.device_auth_id||!json.user_code||typeof intervalSeconds!="number"||!Number.isFinite(intervalSeconds)||intervalSeconds<0)throw new Error(`Invalid OpenAI Codex device code response: ${JSON.stringify(json)}`);return{deviceAuthId:json.device_auth_id,userCode:json.user_code,intervalSeconds}}async function pollOpenAICodexDeviceAuth(device,signal){return pollOAuthDeviceCodeFlow({intervalSeconds:device.intervalSeconds,expiresInSeconds:DEVICE_CODE_TIMEOUT_SECONDS,signal,poll:async()=>{let response=await fetchWithLoginCancellation(DEVICE_TOKEN_URL,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_auth_id:device.deviceAuthId,user_code:device.userCode}),signal});if(response.ok){let json=await response.json();return!json?.authorization_code||!json.code_verifier?{status:"failed",message:`Invalid OpenAI Codex device auth token response: ${JSON.stringify(json)}`}:{status:"complete",value:{authorizationCode:json.authorization_code,codeVerifier:json.code_verifier}}}if(response.status===403||response.status===404)return{status:"pending"};let responseBody=await response.text().catch(()=>""),errorCode;try{let error=JSON.parse(responseBody)?.error;errorCode=typeof error=="object"?error?.code:error}catch{}return errorCode==="deviceauth_authorization_pending"?{status:"pending"}:errorCode==="slow_down"?{status:"slow_down"}:{status:"failed",message:`OpenAI Codex device auth failed with status ${response.status}${responseBody?`: ${responseBody}`:""}`}}})}async function createAuthorizationFlow(originator="pi"){let{verifier,challenge}=await generatePKCE(),state=createState(),url=new URL(AUTHORIZE_URL);return url.searchParams.set("response_type","code"),url.searchParams.set("client_id",CLIENT_ID),url.searchParams.set("redirect_uri",REDIRECT_URI),url.searchParams.set("scope",SCOPE),url.searchParams.set("code_challenge",challenge),url.searchParams.set("code_challenge_method","S256"),url.searchParams.set("state",state),url.searchParams.set("id_token_add_organizations","true"),url.searchParams.set("codex_cli_simplified_flow","true"),url.searchParams.set("originator",originator),{verifier,state,url:url.toString()}}function getAccountId(accessToken){let accountId=decodeJwt(accessToken)?.[JWT_CLAIM_PATH]?.chatgpt_account_id;return typeof accountId=="string"&&accountId.length>0?accountId:null}function credentialsFromToken(token){let accountId=getAccountId(token.access);if(!accountId)throw new Error("Failed to extract accountId from token");return{type:"oauth",access:token.access,refresh:token.refresh,expires:token.expires,accountId}}async function exchangeAuthorizationCodeForCredentials(code,verifier,redirectUri,signal){return credentialsFromToken(await exchangeAuthorizationCode(code,verifier,redirectUri,signal))}async function loginOpenAICodexDeviceCode(interaction){let device=await startOpenAICodexDeviceAuth(interaction.signal);interaction.notify({type:"device_code",userCode:device.userCode,verificationUri:DEVICE_VERIFICATION_URI,intervalSeconds:device.intervalSeconds,expiresInSeconds:DEVICE_CODE_TIMEOUT_SECONDS});let code=await pollOpenAICodexDeviceAuth(device,interaction.signal);return exchangeAuthorizationCodeForCredentials(code.authorizationCode,code.codeVerifier,DEVICE_REDIRECT_URI,interaction.signal)}async function loginOpenAICodex(interaction,options){let{verifier,state,url}=await createAuthorizationFlow(options?.agentName),callback=await startOAuthCallbackServer({providerName:"OpenAI",host:getCallbackHost(),port:1455,path:"/auth/callback",state,complete:async code=>code,signal:interaction.signal}).catch(()=>{});interaction.notify({type:"auth_url",url,instructions:"A browser window should open. Complete login to finish."});try{let result=await waitForCallbackOrManualInput(interaction,callback,{message:"Complete login in your browser, or paste the authorization code / redirect URL here:",placeholder:REDIRECT_URI}),code;if(result.type==="callback")code=result.value;else{let parsed=parseAuthorizationInput(result.input);if(parsed.state&&parsed.state!==state)throw new Error("State mismatch");code=parsed.code}if(!code)throw new Error("Missing authorization code");return await exchangeAuthorizationCodeForCredentials(code,verifier,REDIRECT_URI,interaction.signal)}finally{callback?.close()}}async function refreshOpenAICodexToken(refreshToken,signal){return credentialsFromToken(await refreshAccessToken(refreshToken,signal))}var openaiCodexOAuth={name:"OpenAI (ChatGPT Plus/Pro)",isSubscription:!0,async login(interaction,options){let method=await interaction.prompt({type:"select",message:"Select OpenAI Codex login method:",options:[{id:OPENAI_CODEX_BROWSER_LOGIN_METHOD,label:"Browser login (default)"},{id:OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD,label:"Device code login (headless)"}]});if(method===OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD)return loginOpenAICodexDeviceCode(interaction);if(method!==OPENAI_CODEX_BROWSER_LOGIN_METHOD)throw new Error(`Unknown OpenAI Codex login method: ${method}`);return loginOpenAICodex(interaction,options)},refresh:(credential,signal)=>refreshOpenAICodexToken(credential.refresh,signal),async toAuth(credential){return{apiKey:credential.access}}};export{openaiCodexOAuth};
|
|
86
|
+
</html>`}function oauthSuccessHtml(message){return renderPage({heading:"Signed in",message})}function oauthErrorHtml(message,details){return renderPage({heading:"Sign-in did not complete",message,details})}function sendPage(response,status,html){response.writeHead(status,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),response.end(html)}async function startOAuthCallbackServer(options){let{providerName,signal}=options;if(signal?.aborted)throw new Error("Login cancelled");let resolveWait=()=>{},rejectWait=()=>{},waitPromise=new Promise((resolve,reject)=>{resolveWait=resolve,rejectWait=reject});waitPromise.catch(()=>{});let claimed=!1,settled=!1,timer,onAbort=()=>finish({error:new Error("Login cancelled")}),finish=result=>{settled||(settled=!0,timer&&clearTimeout(timer),signal?.removeEventListener("abort",onAbort),"error"in result?rejectWait(result.error):resolveWait(result.value))},server=createServer((request2,response)=>{(async()=>{let takeOver=by=>{finish({error:new LoginTakenOverError(options.port,by)}),closeCallbackServer(server)};if(options.sharedPort&&answerLoginControl(request2,response,takeOver))return;let url=new URL(request2.url??"/","http://localhost");if(request2.method!=="GET"||url.pathname!==options.path){sendPage(response,404,oauthErrorHtml("Callback route not found."));return}if(options.state!==void 0&&url.searchParams.get("state")!==options.state){sendPage(response,400,oauthErrorHtml("State mismatch."));return}if(claimed||settled){sendPage(response,409,oauthErrorHtml("This sign-in has already been handled."));return}let error=url.searchParams.get("error");if(error){let description=url.searchParams.get("error_description")??error;sendPage(response,400,oauthErrorHtml(`${providerName} authorization failed.`,description)),finish({error:new Error(`${providerName} authorization failed: ${description}`)});return}let code=url.searchParams.get("code");if(!code){sendPage(response,400,oauthErrorHtml("Missing authorization code."));return}claimed=!0;try{let value=await options.complete(code);sendPage(response,200,oauthSuccessHtml(`Signed in to ${providerName}. You may now close this page.`)),finish({value})}catch(error2){let failure=error2 instanceof Error?error2:new Error(String(error2));sendPage(response,502,oauthErrorHtml(`${providerName} sign-in failed.`,failure.message)),finish({error:failure})}})()});options.sharedPort?await listenOnLoginPort(server,{port:options.port,host:options.host,signal}):await new Promise((resolve,reject)=>{server.once("error",reject),server.listen(options.port,options.host,()=>{server.off("error",reject),resolve()})});let address=server.address();if(!address||typeof address=="string")throw server.close(),new Error("OAuth callback server did not bind to TCP");server.on("error",error=>finish({error})),signal?.addEventListener("abort",onAbort,{once:!0}),options.timeoutMs!==void 0&&(timer=setTimeout(()=>finish({error:new Error(`${providerName} sign-in timed out`)}),options.timeoutMs));let redirectHost=options.redirectHost??options.host;return{redirectUri:`http://${redirectHost.includes(":")?`[${redirectHost}]`:redirectHost}:${address.port}${options.path}`,wait:()=>waitPromise,cancel:()=>{claimed||finish({value:void 0})},close:()=>{finish({error:new Error("OAuth callback server closed")}),options.sharedPort?closeCallbackServer(server):server.close()}}}async function waitForCallbackOrManualInput(interaction,callback,prompt){let manualAbort=new AbortController,manualError,manual=interaction.prompt({type:"manual_code",...prompt,signal:manualAbort.signal}).then(input=>(callback?.cancel(),input)).catch(error=>{manualError=error instanceof Error?error:new Error(String(error)),callback?.cancel()});try{let value=await callback?.wait();if(manualError)throw manualError;if(value!==void 0)return{type:"callback",value};let input=await manual;if(manualError)throw manualError;return{type:"manual",input:input??""}}finally{manualAbort.abort()}}var CANCEL_MESSAGE="Login cancelled",TIMEOUT_MESSAGE="Device flow timed out",SLOW_DOWN_TIMEOUT_MESSAGE="Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.";function abortableSleep(ms,signal,cancelMessage){return new Promise((resolve,reject)=>{if(signal.aborted){reject(new Error(cancelMessage));return}let onAbort=()=>{clearTimeout(timeout),reject(new Error(cancelMessage))},timeout=setTimeout(()=>{signal.removeEventListener("abort",onAbort),resolve()},ms);signal.addEventListener("abort",onAbort,{once:!0})})}async function pollOAuthDeviceCodeFlow(options){let deadline=typeof options.expiresInSeconds=="number"?Date.now()+options.expiresInSeconds*1e3:Number.POSITIVE_INFINITY,intervalMs=Math.max(1e3,Math.floor((options.intervalSeconds??5)*1e3)),slowDownResponses=0;if(options.waitBeforeFirstPoll){let remainingMs=deadline-Date.now();remainingMs>0&&await abortableSleep(Math.min(intervalMs,remainingMs),options.signal,CANCEL_MESSAGE)}for(;Date.now()<deadline;){if(options.signal.aborted)throw new Error(CANCEL_MESSAGE);let result=await options.poll();if(result.status==="complete")return result.value;if(result.status==="failed")throw new Error(result.message);result.status==="slow_down"&&(slowDownResponses+=1,intervalMs=typeof result.intervalSeconds=="number"&&Number.isFinite(result.intervalSeconds)&&result.intervalSeconds>0?Math.max(1e3,Math.floor(result.intervalSeconds*1e3)):Math.max(1e3,intervalMs+5e3));let remainingMs=deadline-Date.now();if(remainingMs<=0)break;await abortableSleep(Math.min(intervalMs,remainingMs),options.signal,CANCEL_MESSAGE)}throw new Error(slowDownResponses>0?SLOW_DOWN_TIMEOUT_MESSAGE:TIMEOUT_MESSAGE)}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var _randomBytes=null;typeof process<"u"&&(process.versions?.node||process.versions?.bun)&&import("node:crypto").then(m=>{_randomBytes=m.randomBytes});var CLIENT_ID="app_EMoamEEZ73f0CkXaXp7hrann",AUTH_BASE_URL="https://auth.openai.com",AUTHORIZE_URL=`${AUTH_BASE_URL}/oauth/authorize`,TOKEN_URL=`${AUTH_BASE_URL}/oauth/token`,REDIRECT_URI="http://localhost:1455/auth/callback",DEVICE_USER_CODE_URL=`${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`,DEVICE_TOKEN_URL=`${AUTH_BASE_URL}/api/accounts/deviceauth/token`,DEVICE_VERIFICATION_URI=`${AUTH_BASE_URL}/codex/device`,DEVICE_REDIRECT_URI=`${AUTH_BASE_URL}/deviceauth/callback`,DEVICE_CODE_TIMEOUT_SECONDS=900,OPENAI_CODEX_BROWSER_LOGIN_METHOD="browser",OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD="device_code",SCOPE="openid profile email offline_access",JWT_CLAIM_PATH="https://api.openai.com/auth";function getCallbackHost(){return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST")||"127.0.0.1"}function createState(){if(!_randomBytes)throw new Error("OpenAI Codex OAuth is only available in Node.js environments");return _randomBytes(16).toString("hex")}function parseAuthorizationInput(input){let value=input.trim();if(!value)return{};try{let url=new URL(value);return{code:url.searchParams.get("code")??void 0,state:url.searchParams.get("state")??void 0}}catch{}if(value.includes("#")){let[code,state]=value.split("#",2);return{code,state}}if(value.includes("code=")){let params=new URLSearchParams(value);return{code:params.get("code")??void 0,state:params.get("state")??void 0}}return{code:value}}function decodeJwt(token){try{let parts=token.split(".");if(parts.length!==3)return null;let payload=parts[1]??"",decoded=atob(payload);return JSON.parse(decoded)}catch{return null}}async function fetchWithLoginCancellation(input,init){try{return await fetch(input,init)}catch(error){throw init.signal?.aborted?new Error("Login cancelled"):error}}async function readTokenResponse(response,operation){if(!response.ok){let text=await response.text().catch(()=>"");throw new Error(`OpenAI Codex token ${operation} failed (${response.status}): ${text||response.statusText}`)}let json=await response.json();if(!json?.access_token||!json.refresh_token||typeof json.expires_in!="number")throw new Error(`OpenAI Codex token ${operation} response missing fields: ${JSON.stringify(json)}`);return{access:json.access_token,refresh:json.refresh_token,expires:Date.now()+json.expires_in*1e3}}async function exchangeAuthorizationCode(code,verifier,redirectUri,signal){let response=await fetchWithLoginCancellation(TOKEN_URL,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:CLIENT_ID,code,code_verifier:verifier,redirect_uri:redirectUri}),signal});return readTokenResponse(response,"exchange")}async function refreshAccessToken(refreshToken,signal){let response;try{response=await fetch(TOKEN_URL,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:refreshToken,client_id:CLIENT_ID}),signal})}catch(error){throw new Error(`OpenAI Codex token refresh error: ${error instanceof Error?error.message:String(error)}`)}return readTokenResponse(response,"refresh")}async function startOpenAICodexDeviceAuth(signal){let response=await fetchWithLoginCancellation(DEVICE_USER_CODE_URL,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_id:CLIENT_ID}),signal});if(!response.ok){if(response.status===404)throw new Error("OpenAI Codex device code login is not enabled for this server. Use browser login or verify the server URL.");let responseBody=await response.text().catch(()=>"");throw new Error(`OpenAI Codex device code request failed with status ${response.status}${responseBody?`: ${responseBody}`:""}`)}let json=await response.json(),intervalSeconds=typeof json?.interval=="string"?Number(json.interval.trim()):json?.interval;if(!json?.device_auth_id||!json.user_code||typeof intervalSeconds!="number"||!Number.isFinite(intervalSeconds)||intervalSeconds<0)throw new Error(`Invalid OpenAI Codex device code response: ${JSON.stringify(json)}`);return{deviceAuthId:json.device_auth_id,userCode:json.user_code,intervalSeconds}}async function pollOpenAICodexDeviceAuth(device,signal){return pollOAuthDeviceCodeFlow({intervalSeconds:device.intervalSeconds,expiresInSeconds:DEVICE_CODE_TIMEOUT_SECONDS,signal,poll:async()=>{let response=await fetchWithLoginCancellation(DEVICE_TOKEN_URL,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_auth_id:device.deviceAuthId,user_code:device.userCode}),signal});if(response.ok){let json=await response.json();return!json?.authorization_code||!json.code_verifier?{status:"failed",message:`Invalid OpenAI Codex device auth token response: ${JSON.stringify(json)}`}:{status:"complete",value:{authorizationCode:json.authorization_code,codeVerifier:json.code_verifier}}}if(response.status===403||response.status===404)return{status:"pending"};let responseBody=await response.text().catch(()=>""),errorCode;try{let error=JSON.parse(responseBody)?.error;errorCode=typeof error=="object"?error?.code:error}catch{}return errorCode==="deviceauth_authorization_pending"?{status:"pending"}:errorCode==="slow_down"?{status:"slow_down"}:{status:"failed",message:`OpenAI Codex device auth failed with status ${response.status}${responseBody?`: ${responseBody}`:""}`}}})}async function createAuthorizationFlow(originator="pi"){let{verifier,challenge}=await generatePKCE(),state=createState(),url=new URL(AUTHORIZE_URL);return url.searchParams.set("response_type","code"),url.searchParams.set("client_id",CLIENT_ID),url.searchParams.set("redirect_uri",REDIRECT_URI),url.searchParams.set("scope",SCOPE),url.searchParams.set("code_challenge",challenge),url.searchParams.set("code_challenge_method","S256"),url.searchParams.set("state",state),url.searchParams.set("id_token_add_organizations","true"),url.searchParams.set("codex_cli_simplified_flow","true"),url.searchParams.set("originator",originator),{verifier,state,url:url.toString()}}function getAccountId(accessToken){let accountId=decodeJwt(accessToken)?.[JWT_CLAIM_PATH]?.chatgpt_account_id;return typeof accountId=="string"&&accountId.length>0?accountId:null}function credentialsFromToken(token){let accountId=getAccountId(token.access);if(!accountId)throw new Error("Failed to extract accountId from token");return{type:"oauth",access:token.access,refresh:token.refresh,expires:token.expires,accountId}}async function exchangeAuthorizationCodeForCredentials(code,verifier,redirectUri,signal){return credentialsFromToken(await exchangeAuthorizationCode(code,verifier,redirectUri,signal))}async function loginOpenAICodexDeviceCode(interaction){let device=await startOpenAICodexDeviceAuth(interaction.signal);interaction.notify({type:"device_code",userCode:device.userCode,verificationUri:DEVICE_VERIFICATION_URI,intervalSeconds:device.intervalSeconds,expiresInSeconds:DEVICE_CODE_TIMEOUT_SECONDS});let code=await pollOpenAICodexDeviceAuth(device,interaction.signal);return exchangeAuthorizationCodeForCredentials(code.authorizationCode,code.codeVerifier,DEVICE_REDIRECT_URI,interaction.signal)}async function loginOpenAICodex(interaction,options){let{verifier,state,url}=await createAuthorizationFlow(options?.agentName),callback=options?.pasteOnly?void 0:await startOAuthCallbackServer({providerName:"OpenAI",host:getCallbackHost(),port:1455,path:"/auth/callback",state,complete:async code=>code,signal:interaction.signal,sharedPort:!0}).catch(error=>{if(interaction.signal.aborted)throw new Error("Login cancelled");error instanceof CallbackPortInUseError&&interaction.notify({type:"info",message:`${error.message} Or paste the address your browser ends on after sign-in.`})});interaction.notify({type:"auth_url",url,instructions:"A browser window should open. Complete login to finish."});try{let result=await waitForCallbackOrManualInput(interaction,callback,{message:"Complete login in your browser, or paste the authorization code / redirect URL here:",placeholder:REDIRECT_URI}),code;if(result.type==="callback")code=result.value;else{let parsed=parseAuthorizationInput(result.input);if(parsed.state&&parsed.state!==state)throw new Error("State mismatch");code=parsed.code}if(!code)throw new Error("Missing authorization code");return await exchangeAuthorizationCodeForCredentials(code,verifier,REDIRECT_URI,interaction.signal)}finally{callback?.close()}}async function refreshOpenAICodexToken(refreshToken,signal){return credentialsFromToken(await refreshAccessToken(refreshToken,signal))}var openaiCodexOAuth={name:"OpenAI (ChatGPT Plus/Pro)",isSubscription:!0,async login(interaction,options){let method=await interaction.prompt({type:"select",message:"Select OpenAI Codex login method:",options:[{id:OPENAI_CODEX_BROWSER_LOGIN_METHOD,label:"Browser login (default)"},{id:OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD,label:"Device code login (headless)"}]});if(method===OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD)return loginOpenAICodexDeviceCode(interaction);if(method!==OPENAI_CODEX_BROWSER_LOGIN_METHOD)throw new Error(`Unknown OpenAI Codex login method: ${method}`);return loginOpenAICodex(interaction,options)},refresh:(credential,signal)=>refreshOpenAICodexToken(credential.refresh,signal),async toAuth(credential){return{apiKey:credential.access}}};export{openaiCodexOAuth};
|
|
@@ -83,4 +83,4 @@ var __require=(x=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(x,{get:(
|
|
|
83
83
|
${details?`<div class="details">${details}</div>`:""}
|
|
84
84
|
</main>
|
|
85
85
|
</body>
|
|
86
|
-
</html>`}function oauthSuccessHtml(message){return renderPage({heading:"Signed in",message})}function oauthErrorHtml(message,details){return renderPage({heading:"Sign-in did not complete",message,details})}function sendPage(response,status,html){response.writeHead(status,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),response.end(html)}async function startOAuthCallbackServer(options){let{providerName,signal}=options;if(signal?.aborted)throw new Error("Login cancelled");let resolveWait=()=>{},rejectWait=()=>{},waitPromise=new Promise((resolve,reject)=>{resolveWait=resolve,rejectWait=reject});waitPromise.catch(()=>{});let claimed=!1,settled=!1,timer,onAbort=()=>finish({error:new Error("Login cancelled")}),finish=result=>{settled||(settled=!0,timer&&clearTimeout(timer),signal?.removeEventListener("abort",onAbort),"error"in result?rejectWait(result.error):resolveWait(result.value))},server=createServer((request,response)=>{(async()=>{let url=new URL(request.url??"/","http://localhost");if(request.method!=="GET"||url.pathname!==options.path){sendPage(response,404,oauthErrorHtml("Callback route not found."));return}if(options.state!==void 0&&url.searchParams.get("state")!==options.state){sendPage(response,400,oauthErrorHtml("State mismatch."));return}if(claimed||settled){sendPage(response,409,oauthErrorHtml("This sign-in has already been handled."));return}let error=url.searchParams.get("error");if(error){let description=url.searchParams.get("error_description")??error;sendPage(response,400,oauthErrorHtml(`${providerName} authorization failed.`,description)),finish({error:new Error(`${providerName} authorization failed: ${description}`)});return}let code=url.searchParams.get("code");if(!code){sendPage(response,400,oauthErrorHtml("Missing authorization code."));return}claimed=!0;try{let value=await options.complete(code);sendPage(response,200,oauthSuccessHtml(`Signed in to ${providerName}. You may now close this page.`)),finish({value})}catch(error2){let failure=error2 instanceof Error?error2:new Error(String(error2));sendPage(response,502,oauthErrorHtml(`${providerName} sign-in failed.`,failure.message)),finish({error:failure})}})()});await new Promise((resolve,reject)=>{server.once("error",reject),server.listen(options.port,options.host,()=>{server.off("error",reject),resolve()})});let address=server.address();if(!address||typeof address=="string")throw server.close(),new Error("OAuth callback server did not bind to TCP");server.on("error",error=>finish({error})),signal?.addEventListener("abort",onAbort,{once:!0}),options.timeoutMs!==void 0&&(timer=setTimeout(()=>finish({error:new Error(`${providerName} sign-in timed out`)}),options.timeoutMs));let redirectHost=options.redirectHost??options.host;return{redirectUri:`http://${redirectHost.includes(":")?`[${redirectHost}]`:redirectHost}:${address.port}${options.path}`,wait:()=>waitPromise,cancel:()=>{claimed||finish({value:void 0})},close:()=>{finish({error:new Error("OAuth callback server closed")}),server.close()}}}async function waitForCallbackOrManualInput(interaction,callback,prompt){let manualAbort=new AbortController,manualError,manual=interaction.prompt({type:"manual_code",...prompt,signal:manualAbort.signal}).then(input=>(callback?.cancel(),input)).catch(error=>{manualError=error instanceof Error?error:new Error(String(error)),callback?.cancel()});try{let value=await callback?.wait();if(manualError)throw manualError;if(value!==void 0)return{type:"callback",value};let input=await manual;if(manualError)throw manualError;return{type:"manual",input:input??""}}finally{manualAbort.abort()}}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var AUTHORIZE_URL="https://openrouter.ai/auth",TOKEN_URL="https://openrouter.ai/api/v1/auth/keys",LOGIN_TIMEOUT_MS=300*1e3,TOKEN_EXCHANGE_TIMEOUT_MS=3e4;function getCallbackHost(){return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST")||"127.0.0.1"}function parseAuthorizationInput(input){let value=input.trim();if(value){try{return new URL(value).searchParams.get("code")??void 0}catch{}return value.includes("code=")?new URLSearchParams(value).get("code")??void 0:value}}function errorDetail(body){if(typeof body.error_description=="string")return body.error_description;if(typeof body.message=="string")return body.message;if(typeof body.error=="string")return body.error;if(body.error&&typeof body.error=="object"&&!Array.isArray(body.error)){let message=body.error.message;if(typeof message=="string")return message}}async function exchangeAuthorizationCode(code,verifier,signal){if(signal.aborted)throw new Error("Login cancelled");let controller=new AbortController,onAbort=()=>controller.abort(signal.reason);signal.addEventListener("abort",onAbort,{once:!0});let timeout=setTimeout(()=>controller.abort(new Error("OpenRouter OAuth token exchange timed out")),TOKEN_EXCHANGE_TIMEOUT_MS),response,body={};try{response=await fetch(TOKEN_URL,{method:"POST",headers:{accept:"application/json","content-type":"application/json"},body:JSON.stringify({code,code_verifier:verifier,code_challenge_method:"S256"}),signal:controller.signal});try{let parsed=await response.json();parsed&&typeof parsed=="object"&&!Array.isArray(parsed)&&(body=parsed)}catch{if(response.ok)throw new Error("OpenRouter OAuth returned invalid JSON")}}catch(error){throw signal.aborted?new Error("Login cancelled"):controller.signal.aborted?new Error("OpenRouter OAuth token exchange timed out"):error}finally{clearTimeout(timeout),signal.removeEventListener("abort",onAbort)}if(!response.ok){let detail=errorDetail(body);throw new Error(`OpenRouter OAuth key exchange failed (HTTP ${response.status})${detail?`: ${detail}`:""}`)}if(typeof body.key!="string"||body.key.length===0)throw new Error('OpenRouter OAuth response carries no "key"');return{type:"oauth",access:body.key,refresh:"",expires:Number.MAX_SAFE_INTEGER}}async function loginOpenRouter(interaction){let{verifier,challenge}=await generatePKCE(),callback=await startOAuthCallbackServer({providerName:"OpenRouter",host:getCallbackHost(),port:0,path:`/oauth/callback/${crypto.randomUUID()}`,complete:code=>exchangeAuthorizationCode(code,verifier,interaction.signal),signal:interaction.signal,timeoutMs:LOGIN_TIMEOUT_MS});try{let authorizeUrl=new URL(AUTHORIZE_URL);authorizeUrl.search=new URLSearchParams({callback_url:callback.redirectUri,code_challenge:challenge,code_challenge_method:"S256"}).toString(),interaction.notify({type:"progress",message:`Listening for OpenRouter OAuth callback on ${callback.redirectUri}`}),interaction.notify({type:"auth_url",url:authorizeUrl.toString(),instructions:"Complete sign-in in your browser. If the browser is on another machine, paste the final redirect URL here."});let result=await waitForCallbackOrManualInput(interaction,callback,{message:"Complete sign-in in your browser, or paste the authorization code / redirect URL here:",placeholder:callback.redirectUri});if(result.type==="callback")return result.value;let code=parseAuthorizationInput(result.input);if(!code)throw new Error("Missing authorization code");return interaction.notify({type:"progress",message:"Exchanging authorization code for an API key..."}),await exchangeAuthorizationCode(code,verifier,interaction.signal)}finally{callback.close()}}var openRouterOAuth={name:"OpenRouter OAuth",loginLabel:"Sign in with OpenRouter",login:loginOpenRouter,async refresh(credential,_signal){return credential},async toAuth(credential){return{apiKey:credential.access}}};export{openRouterOAuth};
|
|
86
|
+
</html>`}function oauthSuccessHtml(message){return renderPage({heading:"Signed in",message})}function oauthErrorHtml(message,details){return renderPage({heading:"Sign-in did not complete",message,details})}import{execFile}from"node:child_process";import{request}from"node:http";var LOGIN_PROBE_PATH="/pi-login",LOGIN_TAKE_OVER_PATH="/pi-login/take-over",CODEX_CANCEL_PATH="/cancel",MARKER_HEADER="x-pi-login",OLD_PI_NOT_FOUND="Callback route not found.",SETTLE_MS=2500,RETRY_MS=100,REQUEST_MS=1e3,LOOKUP_MS=2e3;function describeHolder(holder){let pid=holder?.pid?`process ${holder.pid}`:void 0;return holder?.command?pid?`${holder.command} (${pid})`:holder.command:pid??"another program"}var CallbackPortInUseError=class extends Error{code="port_in_use";port;holder;constructor(port,holder){super(holder?.pi?`Port ${port} is held by an unfinished sign-in in another pi or mu session (${describeHolder(holder)}), which did not hand it over. Finish or cancel that sign-in, then try again.`:`Port ${port} is in use by ${describeHolder(holder)}, probably for its own sign-in (the Codex CLI and the Codex app use this port). Finish or cancel that sign-in, then try again.`),this.name="CallbackPortInUseError",this.port=port,this.holder=holder}},LoginTakenOverError=class extends Error{code="login_taken_over";constructor(port,by){super(by==="codex"?`Sign-in stopped: the Codex CLI started its own sign-in on port ${port}.`:`Sign-in stopped: a newer sign-in started and took over port ${port}.`),this.name="LoginTakenOverError"}};function answerLoginControl(request2,response,takeOver){let path=new URL(request2.url??"/","http://localhost").pathname,fromBrowser=request2.headers.origin!==void 0||request2.headers["sec-fetch-site"]!==void 0;if(request2.method==="GET"&&path===LOGIN_PROBE_PATH)return response.writeHead(200,{"content-type":"application/json","cache-control":"no-store",[MARKER_HEADER]:"pi"}),response.end(JSON.stringify({pi:!0,pid:process.pid})),!0;let ours=request2.method==="POST"&&path===LOGIN_TAKE_OVER_PATH&&request2.headers[MARKER_HEADER]==="take-over"&&!fromBrowser,codex=request2.method==="GET"&&path===CODEX_CANCEL_PATH&&!fromBrowser;return!ours&&!codex?!1:(response.once("close",()=>takeOver(ours?"pi":"codex")),response.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-store"}),response.end("Login cancelled"),!0)}function closeCallbackServer(server){server.close(),server.closeAllConnections()}function localRequest(host,port,method,path,headers){return new Promise(resolve=>{let outgoing=request({host,port,method,path,headers,timeout:REQUEST_MS,agent:!1},incoming=>{let body="";incoming.setEncoding("utf8"),incoming.on("data",chunk=>{body.length<16384&&(body+=chunk)}),incoming.on("end",()=>resolve({status:incoming.statusCode??0,headers:incoming.headers,body})),incoming.on("error",()=>resolve({status:incoming.statusCode??0,headers:incoming.headers,body}))});outgoing.on("timeout",()=>outgoing.destroy(new Error("timeout"))),outgoing.on("error",error=>resolve({error:error.code??error.message})),outgoing.end()})}async function probeHolder(host,port){let answer=await localRequest(host,port,"GET",LOGIN_PROBE_PATH,{[MARKER_HEADER]:"probe"});if("error"in answer)return answer.error==="ECONNREFUSED"?{kind:"none"}:{kind:"other"};if(answer.status===200&&answer.headers[MARKER_HEADER]==="pi")try{let pid=JSON.parse(answer.body).pid;return{kind:"pi",pid:typeof pid=="number"?pid:void 0}}catch{return{kind:"pi"}}return answer.status===404&&answer.body.includes(OLD_PI_NOT_FOUND)?{kind:"old-pi"}:{kind:"other"}}function tryListen(server,port,host){return new Promise(resolve=>{let onError=error=>{server.off("listening",onListening),resolve(error)},onListening=()=>{server.off("error",onError),resolve(void 0)};server.once("error",onError),server.once("listening",onListening),server.listen(port,host)})}function cancelled(){return new Error("Login cancelled")}function pause(ms,signal){return new Promise((resolve,reject)=>{if(signal?.aborted){reject(cancelled());return}let onAbort=()=>{clearTimeout(timer),reject(cancelled())},timer=setTimeout(()=>{signal?.removeEventListener("abort",onAbort),resolve()},ms);signal?.addEventListener("abort",onAbort,{once:!0})})}async function listenOnLoginPort(server,options){let{port,host,signal}=options,settleMs=options.settleMs??SETTLE_MS,deadline=Date.now()+settleMs,askedToLetGo=!1,probe;for(;;){if(signal?.aborted)throw cancelled();let error=await tryListen(server,port,host);if(!error){if(signal?.aborted)throw closeCallbackServer(server),cancelled();return}if(error.code!=="EADDRINUSE")throw error;if(!askedToLetGo&&probe?.kind!=="other"&&probe?.kind!=="old-pi"&&(probe=await probeHolder(host,port),probe.kind==="pi"&&(await localRequest(host,port,"POST",LOGIN_TAKE_OVER_PATH,{[MARKER_HEADER]:"take-over"}),askedToLetGo=!0,deadline=Date.now()+settleMs)),Date.now()>=deadline)break;await pause(RETRY_MS,signal)}let found=await(options.findHolder??findPortHolder)(port).catch(()=>{}),holder={...found,pid:found?.pid??(probe?.kind==="pi"?probe.pid:void 0),...probe?.kind==="pi"||probe?.kind==="old-pi"?{pi:!0}:{}};throw new CallbackPortInUseError(port,holder.pid||holder.command||holder.pi?holder:void 0)}function run(command,args){return new Promise(resolve=>{execFile(command,args,{timeout:LOOKUP_MS,windowsHide:!0,encoding:"utf8"},(error,stdout)=>{resolve(error&&error.code==="ENOENT"?void 0:String(stdout??""))})})}function parseLsof(output){let holder;for(let line of output.split(/\r?\n/))if(line.startsWith("p")){if(holder)break;let pid=Number(line.slice(1));holder=Number.isInteger(pid)&&pid>0?{pid}:{}}else line.startsWith("c")&&holder&&(holder.command=line.slice(1));return holder}function parseSs(output){let match=/users:\(\("([^"]*)",pid=(\d+)/.exec(output);return match?{command:match[1],pid:Number(match[2])}:void 0}function parseNetstat(output,port){for(let line of output.split(/\r?\n/)){let columns=line.trim().split(/\s+/);if(columns.length<5||columns[0]?.toUpperCase()!=="TCP"||!columns[1]?.endsWith(`:${port}`)||columns[3]?.toUpperCase()!=="LISTENING")continue;let pid=Number(columns[4]);if(Number.isInteger(pid)&&pid>0)return pid}}function parseTasklist(output){return/^"([^"]+)"/.exec(output.trim())?.[1]?.replace(/\.exe$/i,"")}async function findPortHolder(port){if(process.platform==="win32"){let pid=parseNetstat(await run("netstat",["-ano","-p","TCP"])??"",port);if(!pid)return;let command=parseTasklist(await run("tasklist",["/FI",`PID eq ${pid}`,"/FO","CSV","/NH"])??"");return command?{pid,command}:{pid}}for(let lsof of["lsof","/usr/sbin/lsof","/usr/bin/lsof"]){let output=await run(lsof,["-nP",`-iTCP:${port}`,"-sTCP:LISTEN","-Fpc"]);if(output!==void 0)return parseLsof(output)}if(process.platform==="linux")return parseSs(await run("ss",["-Hltnp",`sport = :${port}`])??"")}function sendPage(response,status,html){response.writeHead(status,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),response.end(html)}async function startOAuthCallbackServer(options){let{providerName,signal}=options;if(signal?.aborted)throw new Error("Login cancelled");let resolveWait=()=>{},rejectWait=()=>{},waitPromise=new Promise((resolve,reject)=>{resolveWait=resolve,rejectWait=reject});waitPromise.catch(()=>{});let claimed=!1,settled=!1,timer,onAbort=()=>finish({error:new Error("Login cancelled")}),finish=result=>{settled||(settled=!0,timer&&clearTimeout(timer),signal?.removeEventListener("abort",onAbort),"error"in result?rejectWait(result.error):resolveWait(result.value))},server=createServer((request2,response)=>{(async()=>{let takeOver=by=>{finish({error:new LoginTakenOverError(options.port,by)}),closeCallbackServer(server)};if(options.sharedPort&&answerLoginControl(request2,response,takeOver))return;let url=new URL(request2.url??"/","http://localhost");if(request2.method!=="GET"||url.pathname!==options.path){sendPage(response,404,oauthErrorHtml("Callback route not found."));return}if(options.state!==void 0&&url.searchParams.get("state")!==options.state){sendPage(response,400,oauthErrorHtml("State mismatch."));return}if(claimed||settled){sendPage(response,409,oauthErrorHtml("This sign-in has already been handled."));return}let error=url.searchParams.get("error");if(error){let description=url.searchParams.get("error_description")??error;sendPage(response,400,oauthErrorHtml(`${providerName} authorization failed.`,description)),finish({error:new Error(`${providerName} authorization failed: ${description}`)});return}let code=url.searchParams.get("code");if(!code){sendPage(response,400,oauthErrorHtml("Missing authorization code."));return}claimed=!0;try{let value=await options.complete(code);sendPage(response,200,oauthSuccessHtml(`Signed in to ${providerName}. You may now close this page.`)),finish({value})}catch(error2){let failure=error2 instanceof Error?error2:new Error(String(error2));sendPage(response,502,oauthErrorHtml(`${providerName} sign-in failed.`,failure.message)),finish({error:failure})}})()});options.sharedPort?await listenOnLoginPort(server,{port:options.port,host:options.host,signal}):await new Promise((resolve,reject)=>{server.once("error",reject),server.listen(options.port,options.host,()=>{server.off("error",reject),resolve()})});let address=server.address();if(!address||typeof address=="string")throw server.close(),new Error("OAuth callback server did not bind to TCP");server.on("error",error=>finish({error})),signal?.addEventListener("abort",onAbort,{once:!0}),options.timeoutMs!==void 0&&(timer=setTimeout(()=>finish({error:new Error(`${providerName} sign-in timed out`)}),options.timeoutMs));let redirectHost=options.redirectHost??options.host;return{redirectUri:`http://${redirectHost.includes(":")?`[${redirectHost}]`:redirectHost}:${address.port}${options.path}`,wait:()=>waitPromise,cancel:()=>{claimed||finish({value:void 0})},close:()=>{finish({error:new Error("OAuth callback server closed")}),options.sharedPort?closeCallbackServer(server):server.close()}}}async function waitForCallbackOrManualInput(interaction,callback,prompt){let manualAbort=new AbortController,manualError,manual=interaction.prompt({type:"manual_code",...prompt,signal:manualAbort.signal}).then(input=>(callback?.cancel(),input)).catch(error=>{manualError=error instanceof Error?error:new Error(String(error)),callback?.cancel()});try{let value=await callback?.wait();if(manualError)throw manualError;if(value!==void 0)return{type:"callback",value};let input=await manual;if(manualError)throw manualError;return{type:"manual",input:input??""}}finally{manualAbort.abort()}}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var AUTHORIZE_URL="https://openrouter.ai/auth",TOKEN_URL="https://openrouter.ai/api/v1/auth/keys",LOGIN_TIMEOUT_MS=300*1e3,TOKEN_EXCHANGE_TIMEOUT_MS=3e4;function getCallbackHost(){return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST")||"127.0.0.1"}function parseAuthorizationInput(input){let value=input.trim();if(value){try{return new URL(value).searchParams.get("code")??void 0}catch{}return value.includes("code=")?new URLSearchParams(value).get("code")??void 0:value}}function errorDetail(body){if(typeof body.error_description=="string")return body.error_description;if(typeof body.message=="string")return body.message;if(typeof body.error=="string")return body.error;if(body.error&&typeof body.error=="object"&&!Array.isArray(body.error)){let message=body.error.message;if(typeof message=="string")return message}}async function exchangeAuthorizationCode(code,verifier,signal){if(signal.aborted)throw new Error("Login cancelled");let controller=new AbortController,onAbort=()=>controller.abort(signal.reason);signal.addEventListener("abort",onAbort,{once:!0});let timeout=setTimeout(()=>controller.abort(new Error("OpenRouter OAuth token exchange timed out")),TOKEN_EXCHANGE_TIMEOUT_MS),response,body={};try{response=await fetch(TOKEN_URL,{method:"POST",headers:{accept:"application/json","content-type":"application/json"},body:JSON.stringify({code,code_verifier:verifier,code_challenge_method:"S256"}),signal:controller.signal});try{let parsed=await response.json();parsed&&typeof parsed=="object"&&!Array.isArray(parsed)&&(body=parsed)}catch{if(response.ok)throw new Error("OpenRouter OAuth returned invalid JSON")}}catch(error){throw signal.aborted?new Error("Login cancelled"):controller.signal.aborted?new Error("OpenRouter OAuth token exchange timed out"):error}finally{clearTimeout(timeout),signal.removeEventListener("abort",onAbort)}if(!response.ok){let detail=errorDetail(body);throw new Error(`OpenRouter OAuth key exchange failed (HTTP ${response.status})${detail?`: ${detail}`:""}`)}if(typeof body.key!="string"||body.key.length===0)throw new Error('OpenRouter OAuth response carries no "key"');return{type:"oauth",access:body.key,refresh:"",expires:Number.MAX_SAFE_INTEGER}}async function loginOpenRouter(interaction){let{verifier,challenge}=await generatePKCE(),callback=await startOAuthCallbackServer({providerName:"OpenRouter",host:getCallbackHost(),port:0,path:`/oauth/callback/${crypto.randomUUID()}`,complete:code=>exchangeAuthorizationCode(code,verifier,interaction.signal),signal:interaction.signal,timeoutMs:LOGIN_TIMEOUT_MS});try{let authorizeUrl=new URL(AUTHORIZE_URL);authorizeUrl.search=new URLSearchParams({callback_url:callback.redirectUri,code_challenge:challenge,code_challenge_method:"S256"}).toString(),interaction.notify({type:"progress",message:`Listening for OpenRouter OAuth callback on ${callback.redirectUri}`}),interaction.notify({type:"auth_url",url:authorizeUrl.toString(),instructions:"Complete sign-in in your browser. If the browser is on another machine, paste the final redirect URL here."});let result=await waitForCallbackOrManualInput(interaction,callback,{message:"Complete sign-in in your browser, or paste the authorization code / redirect URL here:",placeholder:callback.redirectUri});if(result.type==="callback")return result.value;let code=parseAuthorizationInput(result.input);if(!code)throw new Error("Missing authorization code");return interaction.notify({type:"progress",message:"Exchanging authorization code for an API key..."}),await exchangeAuthorizationCode(code,verifier,interaction.signal)}finally{callback.close()}}var openRouterOAuth={name:"OpenRouter OAuth",loginLabel:"Sign in with OpenRouter",login:loginOpenRouter,async refresh(credential,_signal){return credential},async toAuth(credential){return{apiKey:credential.access}}};export{openRouterOAuth};
|
|
@@ -83,4 +83,4 @@ function normalizeRadiusGatewayUrl(value){return(/^https?:\/\//iu.test(value)?va
|
|
|
83
83
|
${details?`<div class="details">${details}</div>`:""}
|
|
84
84
|
</main>
|
|
85
85
|
</body>
|
|
86
|
-
</html>`}function oauthSuccessHtml(message){return renderPage({heading:"Signed in",message})}function oauthErrorHtml(message,details){return renderPage({heading:"Sign-in did not complete",message,details})}function sendPage(response,status,html){response.writeHead(status,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),response.end(html)}async function startOAuthCallbackServer(options){let{providerName,signal}=options;if(signal?.aborted)throw new Error("Login cancelled");let resolveWait=()=>{},rejectWait=()=>{},waitPromise=new Promise((resolve,reject)=>{resolveWait=resolve,rejectWait=reject});waitPromise.catch(()=>{});let claimed=!1,settled=!1,timer,onAbort=()=>finish({error:new Error("Login cancelled")}),finish=result=>{settled||(settled=!0,timer&&clearTimeout(timer),signal?.removeEventListener("abort",onAbort),"error"in result?rejectWait(result.error):resolveWait(result.value))},server=createServer((request,response)=>{(async()=>{let url=new URL(request.url??"/","http://localhost");if(request.method!=="GET"||url.pathname!==options.path){sendPage(response,404,oauthErrorHtml("Callback route not found."));return}if(options.state!==void 0&&url.searchParams.get("state")!==options.state){sendPage(response,400,oauthErrorHtml("State mismatch."));return}if(claimed||settled){sendPage(response,409,oauthErrorHtml("This sign-in has already been handled."));return}let error=url.searchParams.get("error");if(error){let description=url.searchParams.get("error_description")??error;sendPage(response,400,oauthErrorHtml(`${providerName} authorization failed.`,description)),finish({error:new Error(`${providerName} authorization failed: ${description}`)});return}let code=url.searchParams.get("code");if(!code){sendPage(response,400,oauthErrorHtml("Missing authorization code."));return}claimed=!0;try{let value=await options.complete(code);sendPage(response,200,oauthSuccessHtml(`Signed in to ${providerName}. You may now close this page.`)),finish({value})}catch(error2){let failure=error2 instanceof Error?error2:new Error(String(error2));sendPage(response,502,oauthErrorHtml(`${providerName} sign-in failed.`,failure.message)),finish({error:failure})}})()});await new Promise((resolve,reject)=>{server.once("error",reject),server.listen(options.port,options.host,()=>{server.off("error",reject),resolve()})});let address=server.address();if(!address||typeof address=="string")throw server.close(),new Error("OAuth callback server did not bind to TCP");server.on("error",error=>finish({error})),signal?.addEventListener("abort",onAbort,{once:!0}),options.timeoutMs!==void 0&&(timer=setTimeout(()=>finish({error:new Error(`${providerName} sign-in timed out`)}),options.timeoutMs));let redirectHost=options.redirectHost??options.host;return{redirectUri:`http://${redirectHost.includes(":")?`[${redirectHost}]`:redirectHost}:${address.port}${options.path}`,wait:()=>waitPromise,cancel:()=>{claimed||finish({value:void 0})},close:()=>{finish({error:new Error("OAuth callback server closed")}),server.close()}}}var CANCEL_MESSAGE="Login cancelled",TIMEOUT_MESSAGE="Device flow timed out",SLOW_DOWN_TIMEOUT_MESSAGE="Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.";function abortableSleep(ms,signal,cancelMessage){return new Promise((resolve,reject)=>{if(signal.aborted){reject(new Error(cancelMessage));return}let onAbort=()=>{clearTimeout(timeout),reject(new Error(cancelMessage))},timeout=setTimeout(()=>{signal.removeEventListener("abort",onAbort),resolve()},ms);signal.addEventListener("abort",onAbort,{once:!0})})}async function pollOAuthDeviceCodeFlow(options){let deadline=typeof options.expiresInSeconds=="number"?Date.now()+options.expiresInSeconds*1e3:Number.POSITIVE_INFINITY,intervalMs=Math.max(1e3,Math.floor((options.intervalSeconds??5)*1e3)),slowDownResponses=0;if(options.waitBeforeFirstPoll){let remainingMs=deadline-Date.now();remainingMs>0&&await abortableSleep(Math.min(intervalMs,remainingMs),options.signal,CANCEL_MESSAGE)}for(;Date.now()<deadline;){if(options.signal.aborted)throw new Error(CANCEL_MESSAGE);let result=await options.poll();if(result.status==="complete")return result.value;if(result.status==="failed")throw new Error(result.message);result.status==="slow_down"&&(slowDownResponses+=1,intervalMs=typeof result.intervalSeconds=="number"&&Number.isFinite(result.intervalSeconds)&&result.intervalSeconds>0?Math.max(1e3,Math.floor(result.intervalSeconds*1e3)):Math.max(1e3,intervalMs+5e3));let remainingMs=deadline-Date.now();if(remainingMs<=0)break;await abortableSleep(Math.min(intervalMs,remainingMs),options.signal,CANCEL_MESSAGE)}throw new Error(slowDownResponses>0?SLOW_DOWN_TIMEOUT_MESSAGE:TIMEOUT_MESSAGE)}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var CALLBACK_HOST="127.0.0.1",CALLBACK_PORT=1456,CALLBACK_PATH="/oauth/callback",REDIRECT_URI=`http://${CALLBACK_HOST}:${CALLBACK_PORT}${CALLBACK_PATH}`,TOKEN_EXPIRY_SKEW_MS=6e4,LOGIN_METHOD_BROWSER="browser",LOGIN_METHOD_DEVICE_CODE="device-code",OAUTH_CLIENT_ID="pi-gateway",OAUTH_SCOPE="gateway offline_access",OAUTH_DEVICE_CODE_GRANT_TYPE="urn:ietf:params:oauth:grant-type:device_code";async function loadRadiusOAuthDiscovery(gateway,signal){let response=await fetch(new URL("/v1/oauth",gateway),{headers:{accept:"application/json"},signal});if(!response.ok)throw new Error(`Could not load Radius OAuth config from ${gateway}: ${response.status} ${await response.text()}`);let discovery=await response.json();if(typeof discovery.authorizationEndpoint!="string")throw new Error(`Invalid Radius OAuth config from ${gateway}`);return{authorizationEndpoint:discovery.authorizationEndpoint}}var OAuthResponseError=class extends Error{status;oauthError;constructor(status,oauthError,description,message){let detail=oauthError?description?`${oauthError}: ${description}`:oauthError:description||String(status);super(`${message}: ${detail}`),this.status=status,this.oauthError=oauthError}};async function readOAuthResponseError(response,message){let text=await response.text().catch(()=>""),oauthError,description;if(text)try{let data=JSON.parse(text);oauthError=typeof data.error=="string"?data.error:void 0,description=typeof data.error_description=="string"?data.error_description:void 0}catch{description=text}return new OAuthResponseError(response.status,oauthError,description,message)}async function requestOAuthToken(gateway,body,signal){let response;try{response=await fetch(new URL("/v1/oauth/token",gateway),{method:"POST",headers:{accept:"application/json","content-type":"application/x-www-form-urlencoded"},body,signal})}catch(error){throw signal.aborted?new Error("Login cancelled"):error}if(!response.ok)throw await readOAuthResponseError(response,"Radius OAuth token request failed");let data=await response.json();return{type:"oauth",access:data.access_token,refresh:data.refresh_token,expires:Date.now()+data.expires_in*1e3-TOKEN_EXPIRY_SKEW_MS,scope:data.scope}}async function loginWithBrowser(gateway,authorizationEndpoint,interaction){let{verifier,challenge}=await generatePKCE(),state=crypto.randomUUID(),authorizeUrl=new URL(authorizationEndpoint);authorizeUrl.search=new URLSearchParams({response_type:"code",client_id:OAUTH_CLIENT_ID,redirect_uri:REDIRECT_URI,scope:OAUTH_SCOPE,code_challenge:challenge,code_challenge_method:"S256",handoff:"url",state}).toString();let callback=await startOAuthCallbackServer({providerName:"Radius",host:CALLBACK_HOST,port:CALLBACK_PORT,path:CALLBACK_PATH,state,complete:code=>requestOAuthToken(gateway,new URLSearchParams({grant_type:"authorization_code",client_id:OAUTH_CLIENT_ID,redirect_uri:REDIRECT_URI,code,code_verifier:verifier}),interaction.signal),signal:interaction.signal});interaction.notify({type:"progress",message:`Listening for OAuth callback on ${REDIRECT_URI}`}),interaction.notify({type:"auth_url",url:authorizeUrl.toString(),instructions:"Continue in your browser."});try{let credential=await callback.wait();if(!credential)throw new Error("OAuth callback did not complete.");return credential}finally{callback.close()}}async function requestDeviceAuthorization(gateway,signal){let response;try{response=await fetch(new URL("/v1/oauth/device",gateway),{method:"POST",headers:{accept:"application/json","content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:OAUTH_CLIENT_ID,scope:OAUTH_SCOPE}),signal})}catch(error){throw signal.aborted?new Error("Login cancelled"):error}if(!response.ok)throw await readOAuthResponseError(response,"Radius OAuth device authorization failed");let data=await response.json();if(!data.device_code||!data.user_code||!data.verification_uri||!data.expires_in)throw new Error("Radius OAuth device authorization response is missing required fields");return{device_code:data.device_code,user_code:data.user_code,verification_uri:data.verification_uri,expires_in:data.expires_in,interval:data.interval}}async function loginWithDeviceCode(gateway,interaction){let device=await requestDeviceAuthorization(gateway,interaction.signal);return interaction.notify({type:"device_code",userCode:device.user_code,verificationUri:device.verification_uri,intervalSeconds:device.interval,expiresInSeconds:device.expires_in}),pollOAuthDeviceCodeFlow({intervalSeconds:device.interval,expiresInSeconds:device.expires_in,signal:interaction.signal,poll:async()=>{try{return{status:"complete",value:await requestOAuthToken(gateway,new URLSearchParams({grant_type:OAUTH_DEVICE_CODE_GRANT_TYPE,client_id:OAUTH_CLIENT_ID,device_code:device.device_code}),interaction.signal)}}catch(error){if(!(error instanceof OAuthResponseError))throw error;switch(error.oauthError){case"authorization_pending":return{status:"pending"};case"slow_down":return{status:"slow_down"};case"expired_token":return{status:"failed",message:"Device authorization expired."};case"access_denied":return{status:"failed",message:"Device authorization was denied."};default:throw error}}}})}function createRadiusOAuth(options){let gateway=normalizeRadiusGatewayUrl(options.gateway);return{name:options.name,async login(interaction){let loginMethod=await interaction.prompt({type:"select",message:`Sign in to ${options.name}:`,options:[{id:LOGIN_METHOD_BROWSER,label:"Sign in with browser (recommended)"},{id:LOGIN_METHOD_DEVICE_CODE,label:"Sign in with device code (when signing in from another device)"}]});if(loginMethod===LOGIN_METHOD_DEVICE_CODE)return loginWithDeviceCode(gateway,interaction);if(loginMethod===LOGIN_METHOD_BROWSER){let discovery=await loadRadiusOAuthDiscovery(gateway,interaction.signal);return loginWithBrowser(gateway,discovery.authorizationEndpoint,interaction)}throw new Error(`Unknown ${options.name} sign-in method: ${loginMethod}`)},async refresh(credential,signal){return await requestOAuthToken(gateway,new URLSearchParams({grant_type:"refresh_token",client_id:OAUTH_CLIENT_ID,refresh_token:credential.refresh}),signal)},async toAuth(credential){return{apiKey:credential.access}}}}export{createRadiusOAuth};
|
|
86
|
+
</html>`}function oauthSuccessHtml(message){return renderPage({heading:"Signed in",message})}function oauthErrorHtml(message,details){return renderPage({heading:"Sign-in did not complete",message,details})}import{execFile}from"node:child_process";import{request}from"node:http";var LOGIN_PROBE_PATH="/pi-login",LOGIN_TAKE_OVER_PATH="/pi-login/take-over",CODEX_CANCEL_PATH="/cancel",MARKER_HEADER="x-pi-login",OLD_PI_NOT_FOUND="Callback route not found.",SETTLE_MS=2500,RETRY_MS=100,REQUEST_MS=1e3,LOOKUP_MS=2e3;function describeHolder(holder){let pid=holder?.pid?`process ${holder.pid}`:void 0;return holder?.command?pid?`${holder.command} (${pid})`:holder.command:pid??"another program"}var CallbackPortInUseError=class extends Error{code="port_in_use";port;holder;constructor(port,holder){super(holder?.pi?`Port ${port} is held by an unfinished sign-in in another pi or mu session (${describeHolder(holder)}), which did not hand it over. Finish or cancel that sign-in, then try again.`:`Port ${port} is in use by ${describeHolder(holder)}, probably for its own sign-in (the Codex CLI and the Codex app use this port). Finish or cancel that sign-in, then try again.`),this.name="CallbackPortInUseError",this.port=port,this.holder=holder}},LoginTakenOverError=class extends Error{code="login_taken_over";constructor(port,by){super(by==="codex"?`Sign-in stopped: the Codex CLI started its own sign-in on port ${port}.`:`Sign-in stopped: a newer sign-in started and took over port ${port}.`),this.name="LoginTakenOverError"}};function answerLoginControl(request2,response,takeOver){let path=new URL(request2.url??"/","http://localhost").pathname,fromBrowser=request2.headers.origin!==void 0||request2.headers["sec-fetch-site"]!==void 0;if(request2.method==="GET"&&path===LOGIN_PROBE_PATH)return response.writeHead(200,{"content-type":"application/json","cache-control":"no-store",[MARKER_HEADER]:"pi"}),response.end(JSON.stringify({pi:!0,pid:process.pid})),!0;let ours=request2.method==="POST"&&path===LOGIN_TAKE_OVER_PATH&&request2.headers[MARKER_HEADER]==="take-over"&&!fromBrowser,codex=request2.method==="GET"&&path===CODEX_CANCEL_PATH&&!fromBrowser;return!ours&&!codex?!1:(response.once("close",()=>takeOver(ours?"pi":"codex")),response.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-store"}),response.end("Login cancelled"),!0)}function closeCallbackServer(server){server.close(),server.closeAllConnections()}function localRequest(host,port,method,path,headers){return new Promise(resolve=>{let outgoing=request({host,port,method,path,headers,timeout:REQUEST_MS,agent:!1},incoming=>{let body="";incoming.setEncoding("utf8"),incoming.on("data",chunk=>{body.length<16384&&(body+=chunk)}),incoming.on("end",()=>resolve({status:incoming.statusCode??0,headers:incoming.headers,body})),incoming.on("error",()=>resolve({status:incoming.statusCode??0,headers:incoming.headers,body}))});outgoing.on("timeout",()=>outgoing.destroy(new Error("timeout"))),outgoing.on("error",error=>resolve({error:error.code??error.message})),outgoing.end()})}async function probeHolder(host,port){let answer=await localRequest(host,port,"GET",LOGIN_PROBE_PATH,{[MARKER_HEADER]:"probe"});if("error"in answer)return answer.error==="ECONNREFUSED"?{kind:"none"}:{kind:"other"};if(answer.status===200&&answer.headers[MARKER_HEADER]==="pi")try{let pid=JSON.parse(answer.body).pid;return{kind:"pi",pid:typeof pid=="number"?pid:void 0}}catch{return{kind:"pi"}}return answer.status===404&&answer.body.includes(OLD_PI_NOT_FOUND)?{kind:"old-pi"}:{kind:"other"}}function tryListen(server,port,host){return new Promise(resolve=>{let onError=error=>{server.off("listening",onListening),resolve(error)},onListening=()=>{server.off("error",onError),resolve(void 0)};server.once("error",onError),server.once("listening",onListening),server.listen(port,host)})}function cancelled(){return new Error("Login cancelled")}function pause(ms,signal){return new Promise((resolve,reject)=>{if(signal?.aborted){reject(cancelled());return}let onAbort=()=>{clearTimeout(timer),reject(cancelled())},timer=setTimeout(()=>{signal?.removeEventListener("abort",onAbort),resolve()},ms);signal?.addEventListener("abort",onAbort,{once:!0})})}async function listenOnLoginPort(server,options){let{port,host,signal}=options,settleMs=options.settleMs??SETTLE_MS,deadline=Date.now()+settleMs,askedToLetGo=!1,probe;for(;;){if(signal?.aborted)throw cancelled();let error=await tryListen(server,port,host);if(!error){if(signal?.aborted)throw closeCallbackServer(server),cancelled();return}if(error.code!=="EADDRINUSE")throw error;if(!askedToLetGo&&probe?.kind!=="other"&&probe?.kind!=="old-pi"&&(probe=await probeHolder(host,port),probe.kind==="pi"&&(await localRequest(host,port,"POST",LOGIN_TAKE_OVER_PATH,{[MARKER_HEADER]:"take-over"}),askedToLetGo=!0,deadline=Date.now()+settleMs)),Date.now()>=deadline)break;await pause(RETRY_MS,signal)}let found=await(options.findHolder??findPortHolder)(port).catch(()=>{}),holder={...found,pid:found?.pid??(probe?.kind==="pi"?probe.pid:void 0),...probe?.kind==="pi"||probe?.kind==="old-pi"?{pi:!0}:{}};throw new CallbackPortInUseError(port,holder.pid||holder.command||holder.pi?holder:void 0)}function run(command,args){return new Promise(resolve=>{execFile(command,args,{timeout:LOOKUP_MS,windowsHide:!0,encoding:"utf8"},(error,stdout)=>{resolve(error&&error.code==="ENOENT"?void 0:String(stdout??""))})})}function parseLsof(output){let holder;for(let line of output.split(/\r?\n/))if(line.startsWith("p")){if(holder)break;let pid=Number(line.slice(1));holder=Number.isInteger(pid)&&pid>0?{pid}:{}}else line.startsWith("c")&&holder&&(holder.command=line.slice(1));return holder}function parseSs(output){let match=/users:\(\("([^"]*)",pid=(\d+)/.exec(output);return match?{command:match[1],pid:Number(match[2])}:void 0}function parseNetstat(output,port){for(let line of output.split(/\r?\n/)){let columns=line.trim().split(/\s+/);if(columns.length<5||columns[0]?.toUpperCase()!=="TCP"||!columns[1]?.endsWith(`:${port}`)||columns[3]?.toUpperCase()!=="LISTENING")continue;let pid=Number(columns[4]);if(Number.isInteger(pid)&&pid>0)return pid}}function parseTasklist(output){return/^"([^"]+)"/.exec(output.trim())?.[1]?.replace(/\.exe$/i,"")}async function findPortHolder(port){if(process.platform==="win32"){let pid=parseNetstat(await run("netstat",["-ano","-p","TCP"])??"",port);if(!pid)return;let command=parseTasklist(await run("tasklist",["/FI",`PID eq ${pid}`,"/FO","CSV","/NH"])??"");return command?{pid,command}:{pid}}for(let lsof of["lsof","/usr/sbin/lsof","/usr/bin/lsof"]){let output=await run(lsof,["-nP",`-iTCP:${port}`,"-sTCP:LISTEN","-Fpc"]);if(output!==void 0)return parseLsof(output)}if(process.platform==="linux")return parseSs(await run("ss",["-Hltnp",`sport = :${port}`])??"")}function sendPage(response,status,html){response.writeHead(status,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),response.end(html)}async function startOAuthCallbackServer(options){let{providerName,signal}=options;if(signal?.aborted)throw new Error("Login cancelled");let resolveWait=()=>{},rejectWait=()=>{},waitPromise=new Promise((resolve,reject)=>{resolveWait=resolve,rejectWait=reject});waitPromise.catch(()=>{});let claimed=!1,settled=!1,timer,onAbort=()=>finish({error:new Error("Login cancelled")}),finish=result=>{settled||(settled=!0,timer&&clearTimeout(timer),signal?.removeEventListener("abort",onAbort),"error"in result?rejectWait(result.error):resolveWait(result.value))},server=createServer((request2,response)=>{(async()=>{let takeOver=by=>{finish({error:new LoginTakenOverError(options.port,by)}),closeCallbackServer(server)};if(options.sharedPort&&answerLoginControl(request2,response,takeOver))return;let url=new URL(request2.url??"/","http://localhost");if(request2.method!=="GET"||url.pathname!==options.path){sendPage(response,404,oauthErrorHtml("Callback route not found."));return}if(options.state!==void 0&&url.searchParams.get("state")!==options.state){sendPage(response,400,oauthErrorHtml("State mismatch."));return}if(claimed||settled){sendPage(response,409,oauthErrorHtml("This sign-in has already been handled."));return}let error=url.searchParams.get("error");if(error){let description=url.searchParams.get("error_description")??error;sendPage(response,400,oauthErrorHtml(`${providerName} authorization failed.`,description)),finish({error:new Error(`${providerName} authorization failed: ${description}`)});return}let code=url.searchParams.get("code");if(!code){sendPage(response,400,oauthErrorHtml("Missing authorization code."));return}claimed=!0;try{let value=await options.complete(code);sendPage(response,200,oauthSuccessHtml(`Signed in to ${providerName}. You may now close this page.`)),finish({value})}catch(error2){let failure=error2 instanceof Error?error2:new Error(String(error2));sendPage(response,502,oauthErrorHtml(`${providerName} sign-in failed.`,failure.message)),finish({error:failure})}})()});options.sharedPort?await listenOnLoginPort(server,{port:options.port,host:options.host,signal}):await new Promise((resolve,reject)=>{server.once("error",reject),server.listen(options.port,options.host,()=>{server.off("error",reject),resolve()})});let address=server.address();if(!address||typeof address=="string")throw server.close(),new Error("OAuth callback server did not bind to TCP");server.on("error",error=>finish({error})),signal?.addEventListener("abort",onAbort,{once:!0}),options.timeoutMs!==void 0&&(timer=setTimeout(()=>finish({error:new Error(`${providerName} sign-in timed out`)}),options.timeoutMs));let redirectHost=options.redirectHost??options.host;return{redirectUri:`http://${redirectHost.includes(":")?`[${redirectHost}]`:redirectHost}:${address.port}${options.path}`,wait:()=>waitPromise,cancel:()=>{claimed||finish({value:void 0})},close:()=>{finish({error:new Error("OAuth callback server closed")}),options.sharedPort?closeCallbackServer(server):server.close()}}}var CANCEL_MESSAGE="Login cancelled",TIMEOUT_MESSAGE="Device flow timed out",SLOW_DOWN_TIMEOUT_MESSAGE="Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.";function abortableSleep(ms,signal,cancelMessage){return new Promise((resolve,reject)=>{if(signal.aborted){reject(new Error(cancelMessage));return}let onAbort=()=>{clearTimeout(timeout),reject(new Error(cancelMessage))},timeout=setTimeout(()=>{signal.removeEventListener("abort",onAbort),resolve()},ms);signal.addEventListener("abort",onAbort,{once:!0})})}async function pollOAuthDeviceCodeFlow(options){let deadline=typeof options.expiresInSeconds=="number"?Date.now()+options.expiresInSeconds*1e3:Number.POSITIVE_INFINITY,intervalMs=Math.max(1e3,Math.floor((options.intervalSeconds??5)*1e3)),slowDownResponses=0;if(options.waitBeforeFirstPoll){let remainingMs=deadline-Date.now();remainingMs>0&&await abortableSleep(Math.min(intervalMs,remainingMs),options.signal,CANCEL_MESSAGE)}for(;Date.now()<deadline;){if(options.signal.aborted)throw new Error(CANCEL_MESSAGE);let result=await options.poll();if(result.status==="complete")return result.value;if(result.status==="failed")throw new Error(result.message);result.status==="slow_down"&&(slowDownResponses+=1,intervalMs=typeof result.intervalSeconds=="number"&&Number.isFinite(result.intervalSeconds)&&result.intervalSeconds>0?Math.max(1e3,Math.floor(result.intervalSeconds*1e3)):Math.max(1e3,intervalMs+5e3));let remainingMs=deadline-Date.now();if(remainingMs<=0)break;await abortableSleep(Math.min(intervalMs,remainingMs),options.signal,CANCEL_MESSAGE)}throw new Error(slowDownResponses>0?SLOW_DOWN_TIMEOUT_MESSAGE:TIMEOUT_MESSAGE)}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var CALLBACK_HOST="127.0.0.1",CALLBACK_PORT=1456,CALLBACK_PATH="/oauth/callback",REDIRECT_URI=`http://${CALLBACK_HOST}:${CALLBACK_PORT}${CALLBACK_PATH}`,TOKEN_EXPIRY_SKEW_MS=6e4,LOGIN_METHOD_BROWSER="browser",LOGIN_METHOD_DEVICE_CODE="device-code",OAUTH_CLIENT_ID="pi-gateway",OAUTH_SCOPE="gateway offline_access",OAUTH_DEVICE_CODE_GRANT_TYPE="urn:ietf:params:oauth:grant-type:device_code";async function loadRadiusOAuthDiscovery(gateway,signal){let response=await fetch(new URL("/v1/oauth",gateway),{headers:{accept:"application/json"},signal});if(!response.ok)throw new Error(`Could not load Radius OAuth config from ${gateway}: ${response.status} ${await response.text()}`);let discovery=await response.json();if(typeof discovery.authorizationEndpoint!="string")throw new Error(`Invalid Radius OAuth config from ${gateway}`);return{authorizationEndpoint:discovery.authorizationEndpoint}}var OAuthResponseError=class extends Error{status;oauthError;constructor(status,oauthError,description,message){let detail=oauthError?description?`${oauthError}: ${description}`:oauthError:description||String(status);super(`${message}: ${detail}`),this.status=status,this.oauthError=oauthError}};async function readOAuthResponseError(response,message){let text=await response.text().catch(()=>""),oauthError,description;if(text)try{let data=JSON.parse(text);oauthError=typeof data.error=="string"?data.error:void 0,description=typeof data.error_description=="string"?data.error_description:void 0}catch{description=text}return new OAuthResponseError(response.status,oauthError,description,message)}async function requestOAuthToken(gateway,body,signal){let response;try{response=await fetch(new URL("/v1/oauth/token",gateway),{method:"POST",headers:{accept:"application/json","content-type":"application/x-www-form-urlencoded"},body,signal})}catch(error){throw signal.aborted?new Error("Login cancelled"):error}if(!response.ok)throw await readOAuthResponseError(response,"Radius OAuth token request failed");let data=await response.json();return{type:"oauth",access:data.access_token,refresh:data.refresh_token,expires:Date.now()+data.expires_in*1e3-TOKEN_EXPIRY_SKEW_MS,scope:data.scope}}async function loginWithBrowser(gateway,authorizationEndpoint,interaction){let{verifier,challenge}=await generatePKCE(),state=crypto.randomUUID(),authorizeUrl=new URL(authorizationEndpoint);authorizeUrl.search=new URLSearchParams({response_type:"code",client_id:OAUTH_CLIENT_ID,redirect_uri:REDIRECT_URI,scope:OAUTH_SCOPE,code_challenge:challenge,code_challenge_method:"S256",handoff:"url",state}).toString();let callback=await startOAuthCallbackServer({providerName:"Radius",host:CALLBACK_HOST,port:CALLBACK_PORT,path:CALLBACK_PATH,state,complete:code=>requestOAuthToken(gateway,new URLSearchParams({grant_type:"authorization_code",client_id:OAUTH_CLIENT_ID,redirect_uri:REDIRECT_URI,code,code_verifier:verifier}),interaction.signal),signal:interaction.signal});interaction.notify({type:"progress",message:`Listening for OAuth callback on ${REDIRECT_URI}`}),interaction.notify({type:"auth_url",url:authorizeUrl.toString(),instructions:"Continue in your browser."});try{let credential=await callback.wait();if(!credential)throw new Error("OAuth callback did not complete.");return credential}finally{callback.close()}}async function requestDeviceAuthorization(gateway,signal){let response;try{response=await fetch(new URL("/v1/oauth/device",gateway),{method:"POST",headers:{accept:"application/json","content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:OAUTH_CLIENT_ID,scope:OAUTH_SCOPE}),signal})}catch(error){throw signal.aborted?new Error("Login cancelled"):error}if(!response.ok)throw await readOAuthResponseError(response,"Radius OAuth device authorization failed");let data=await response.json();if(!data.device_code||!data.user_code||!data.verification_uri||!data.expires_in)throw new Error("Radius OAuth device authorization response is missing required fields");return{device_code:data.device_code,user_code:data.user_code,verification_uri:data.verification_uri,expires_in:data.expires_in,interval:data.interval}}async function loginWithDeviceCode(gateway,interaction){let device=await requestDeviceAuthorization(gateway,interaction.signal);return interaction.notify({type:"device_code",userCode:device.user_code,verificationUri:device.verification_uri,intervalSeconds:device.interval,expiresInSeconds:device.expires_in}),pollOAuthDeviceCodeFlow({intervalSeconds:device.interval,expiresInSeconds:device.expires_in,signal:interaction.signal,poll:async()=>{try{return{status:"complete",value:await requestOAuthToken(gateway,new URLSearchParams({grant_type:OAUTH_DEVICE_CODE_GRANT_TYPE,client_id:OAUTH_CLIENT_ID,device_code:device.device_code}),interaction.signal)}}catch(error){if(!(error instanceof OAuthResponseError))throw error;switch(error.oauthError){case"authorization_pending":return{status:"pending"};case"slow_down":return{status:"slow_down"};case"expired_token":return{status:"failed",message:"Device authorization expired."};case"access_denied":return{status:"failed",message:"Device authorization was denied."};default:throw error}}}})}function createRadiusOAuth(options){let gateway=normalizeRadiusGatewayUrl(options.gateway);return{name:options.name,async login(interaction){let loginMethod=await interaction.prompt({type:"select",message:`Sign in to ${options.name}:`,options:[{id:LOGIN_METHOD_BROWSER,label:"Sign in with browser (recommended)"},{id:LOGIN_METHOD_DEVICE_CODE,label:"Sign in with device code (when signing in from another device)"}]});if(loginMethod===LOGIN_METHOD_DEVICE_CODE)return loginWithDeviceCode(gateway,interaction);if(loginMethod===LOGIN_METHOD_BROWSER){let discovery=await loadRadiusOAuthDiscovery(gateway,interaction.signal);return loginWithBrowser(gateway,discovery.authorizationEndpoint,interaction)}throw new Error(`Unknown ${options.name} sign-in method: ${loginMethod}`)},async refresh(credential,signal){return await requestOAuthToken(gateway,new URLSearchParams({grant_type:"refresh_token",client_id:OAUTH_CLIENT_ID,refresh_token:credential.refresh}),signal)},async toAuth(credential){return{apiKey:credential.access}}}}export{createRadiusOAuth};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url);
|
|
2
|
-
import{dist_exports as dist_exports3}from"./chunk-
|
|
2
|
+
import{dist_exports as dist_exports3}from"./chunk-FGOMFVJ2.js";import{all_exports,compat_exports,compile_exports,dist_exports as dist_exports2,value_exports}from"./chunk-HPQUXAZK.js";import{build_exports}from"./chunk-DYTENXHX.js";import"./chunk-FLHKNMLQ.js";import{dist_exports}from"./chunk-LT2C56HV.js";import"./chunk-6Z2347ML.js";import"./chunk-MF3VB62R.js";import"./chunk-URBBMV3M.js";import"./chunk-6QGV3IWD.js";import"./chunk-LHRPMXVI.js";import"./chunk-USJZMAIR.js";import"./chunk-5AGIVGSV.js";import"./chunk-UKVNB6C4.js";import"./chunk-TMTOQ7H7.js";import"./chunk-7DDKXS2Q.js";import"./chunk-3V45WARH.js";import"./chunk-WCGU75WO.js";import"./chunk-LPXNMA2W.js";import"./chunk-JXWJOIH4.js";var oauth_exports={};var VIRTUAL_MODULES={typebox:build_exports,"typebox/compile":compile_exports,"typebox/value":value_exports,"@sinclair/typebox":build_exports,"@sinclair/typebox/compile":compile_exports,"@sinclair/typebox/value":value_exports,"@earendil-works/pi-agent-core":dist_exports2,"@earendil-works/pi-tui":dist_exports,"@earendil-works/pi-ai":compat_exports,"@earendil-works/pi-ai/compat":compat_exports,"@earendil-works/pi-ai/oauth":oauth_exports,"@earendil-works/pi-ai/providers/all":all_exports,"@earendil-works/pi-coding-agent":dist_exports3,"@mariozechner/pi-agent-core":dist_exports2,"@mariozechner/pi-tui":dist_exports,"@mariozechner/pi-ai":compat_exports,"@mariozechner/pi-ai/compat":compat_exports,"@mariozechner/pi-ai/oauth":oauth_exports,"@mariozechner/pi-ai/providers/all":all_exports,"@mariozechner/pi-coding-agent":dist_exports3};export{VIRTUAL_MODULES};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url);
|
|
3
|
-
import{main,setupCli}from"./chunks/chunk-
|
|
3
|
+
import{main,setupCli}from"./chunks/chunk-HPQUXAZK.js";import"./chunks/chunk-DYTENXHX.js";import"./chunks/chunk-FLHKNMLQ.js";import"./chunks/chunk-LT2C56HV.js";import"./chunks/chunk-6Z2347ML.js";import"./chunks/chunk-MF3VB62R.js";import"./chunks/chunk-URBBMV3M.js";import"./chunks/chunk-6QGV3IWD.js";import"./chunks/chunk-LHRPMXVI.js";import"./chunks/chunk-USJZMAIR.js";import"./chunks/chunk-5AGIVGSV.js";import"./chunks/chunk-UKVNB6C4.js";import"./chunks/chunk-TMTOQ7H7.js";import"./chunks/chunk-7DDKXS2Q.js";import"./chunks/chunk-3V45WARH.js";import"./chunks/chunk-WCGU75WO.js";import"./chunks/chunk-LPXNMA2W.js";import"./chunks/chunk-JXWJOIH4.js";setupCli();main(process.argv.slice(2));
|
package/dist/bundle/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url);
|
|
2
|
-
import{ShowImagesSelectorComponent,ThemeSelectorComponent}from"./chunks/chunk-
|
|
2
|
+
import{ShowImagesSelectorComponent,ThemeSelectorComponent}from"./chunks/chunk-FGOMFVJ2.js";import{AgentSession,AgentSessionRuntime,AssistantMessageComponent,BashExecutionComponent,BorderedLoader,BranchSummaryMessageComponent,CURRENT_SESSION_VERSION,CompactionSummaryMessageComponent,CredentialSynchronizationError,CustomEditor,CustomMessageComponent,DEFAULT_COMPACTION_SETTINGS,DefaultPackageManager,DefaultResourceLoader,DynamicBorder,ExtensionEditorComponent,ExtensionInputComponent,ExtensionRunner,ExtensionSelectorComponent,FileModelsStore,FooterComponent,InteractiveMode,LoginDialogComponent,MCP_CONNECTION_EVENT,ModelRegistry,ModelRuntime,ModelSelectorComponent,OAuthSelectorComponent,RpcClient,SessionManager,SessionSelectorComponent,SettingsManager,SettingsSelectorComponent,SkillInvocationMessageComponent,ThinkingSelectorComponent,ToolExecutionComponent,TreeSelectorComponent,UserMessageComponent,UserMessageSelectorComponent,VIRTUAL_MODEL_STATE_ENTRY,applyHttpProxySettings,buildContextEntries,buildSessionContext,buildSessionProjection,calculateContextTokens,collectEntriesForBranchSummary,compact,configureHttpDispatcher,convertToLlm,convertToPng,copyToClipboard,createAgentSession,createAgentSessionFromServices,createAgentSessionRuntime,createAgentSessionServices,createBashTool,createBashToolDefinition,createCodemodeExtension,createCodingTools,createEditTool,createEditToolDefinition,createEventBus,createExtensionRuntime,createFindTool,createFindToolDefinition,createGrepTool,createGrepToolDefinition,createLocalBashOperations,createLocalPowerShellOperations,createLsTool,createLsToolDefinition,createMcpExtension,createPowerShellTool,createPowerShellToolDefinition,createReadOnlyTools,createReadTool,createReadToolDefinition,createSyntheticSourceInfo,createToolSearchExtension,createWriteTool,createWriteToolDefinition,defaultModelPerProvider,defineTool,detectSupportedImageMimeTypeFromFile,discoverAndLoadExtensions,estimateTokens,findCutPoint,findTurnStartIndex,formatDimensionNote,formatSkillsForPrompt,generateBranchSummary,generateDiffString,generateSummary,generateSummaryWithUsage,generateUnifiedPatch,getLastAssistantUsage,getLatestCompactionEntry,isBashToolResult,isEditToolResult,isFindToolResult,isGrepToolResult,isLsToolResult,isPowerShellToolResult,isReadToolResult,isToolCallEventType,isWriteToolResult,loadProjectContextFiles,loadSkills,loadSkillsFromDir,main,migrateSessionEntries,parseArgs,parseFrontmatter,parseSessionEntries,parseSkillBlock,prepareBranchEntries,renderDiff,resizeImage,resolveCliModel,resolveModelScopeWithDiagnostics,runPrintMode,runRpcMode,serializeConversation,sessionEntryToContextMessages,setupCli,shouldCompact,stdioRpcTransport,stripFrontmatter,withFileMutationQueue,wrapRegisteredTool,wrapRegisteredTools}from"./chunks/chunk-HPQUXAZK.js";import{DEFAULT_MAX_BYTES,DEFAULT_MAX_LINES,formatReadLineRange,formatSize,truncateHead,truncateLine,truncateTail,truncateToVisualLines}from"./chunks/chunk-DYTENXHX.js";import{ArminComponent}from"./chunks/chunk-FLHKNMLQ.js";import{Theme,getLanguageFromPath,getMarkdownTheme,getSelectListTheme,getSettingsListTheme,highlightCode,initTheme,keyHint,keyText,rawKeyHint}from"./chunks/chunk-LT2C56HV.js";import{ProjectTrustStore,hasTrustRequiringProjectResources,loadMcpConfig}from"./chunks/chunk-6Z2347ML.js";import{readStoredCredential}from"./chunks/chunk-MF3VB62R.js";import{getPowerShellConfig,getShellConfig}from"./chunks/chunk-URBBMV3M.js";import"./chunks/chunk-6QGV3IWD.js";import{CONFIG_DIR_NAME,VERSION,getAgentDir,getDocsPath,getExamplesPath,getPackageDir,getReadmePath}from"./chunks/chunk-LHRPMXVI.js";import"./chunks/chunk-USJZMAIR.js";import"./chunks/chunk-5AGIVGSV.js";import"./chunks/chunk-UKVNB6C4.js";import"./chunks/chunk-TMTOQ7H7.js";import"./chunks/chunk-7DDKXS2Q.js";import"./chunks/chunk-3V45WARH.js";import"./chunks/chunk-WCGU75WO.js";import"./chunks/chunk-LPXNMA2W.js";import"./chunks/chunk-JXWJOIH4.js";export{AgentSession,AgentSessionRuntime,ArminComponent,AssistantMessageComponent,BashExecutionComponent,BorderedLoader,BranchSummaryMessageComponent,CONFIG_DIR_NAME,CURRENT_SESSION_VERSION,CompactionSummaryMessageComponent,CredentialSynchronizationError,CustomEditor,CustomMessageComponent,DEFAULT_COMPACTION_SETTINGS,DEFAULT_MAX_BYTES,DEFAULT_MAX_LINES,DefaultPackageManager,DefaultResourceLoader,DynamicBorder,ExtensionEditorComponent,ExtensionInputComponent,ExtensionRunner,ExtensionSelectorComponent,FileModelsStore,FooterComponent,InteractiveMode,LoginDialogComponent,MCP_CONNECTION_EVENT,ModelRegistry,ModelRuntime,ModelSelectorComponent,OAuthSelectorComponent,ProjectTrustStore,RpcClient,SessionManager,SessionSelectorComponent,SettingsManager,SettingsSelectorComponent,ShowImagesSelectorComponent,SkillInvocationMessageComponent,Theme,ThemeSelectorComponent,ThinkingSelectorComponent,ToolExecutionComponent,TreeSelectorComponent,UserMessageComponent,UserMessageSelectorComponent,VERSION,VIRTUAL_MODEL_STATE_ENTRY,applyHttpProxySettings,buildContextEntries,buildSessionContext,buildSessionProjection,calculateContextTokens,collectEntriesForBranchSummary,compact,configureHttpDispatcher,convertToLlm,convertToPng,copyToClipboard,createAgentSession,createAgentSessionFromServices,createAgentSessionRuntime,createAgentSessionServices,createBashTool,createBashToolDefinition,createCodemodeExtension,createCodingTools,createEditTool,createEditToolDefinition,createEventBus,createExtensionRuntime,createFindTool,createFindToolDefinition,createGrepTool,createGrepToolDefinition,createLocalBashOperations,createLocalPowerShellOperations,createLsTool,createLsToolDefinition,createMcpExtension,createPowerShellTool,createPowerShellToolDefinition,createReadOnlyTools,createReadTool,createReadToolDefinition,createSyntheticSourceInfo,createToolSearchExtension,createWriteTool,createWriteToolDefinition,defaultModelPerProvider,defineTool,detectSupportedImageMimeTypeFromFile,discoverAndLoadExtensions,estimateTokens,findCutPoint,findTurnStartIndex,formatDimensionNote,formatReadLineRange,formatSize,formatSkillsForPrompt,generateBranchSummary,generateDiffString,generateSummary,generateSummaryWithUsage,generateUnifiedPatch,getAgentDir,getDocsPath,getExamplesPath,getLanguageFromPath,getLastAssistantUsage,getLatestCompactionEntry,getMarkdownTheme,getPackageDir,getPowerShellConfig,getReadmePath,getSelectListTheme,getSettingsListTheme,getShellConfig,hasTrustRequiringProjectResources,highlightCode,initTheme,isBashToolResult,isEditToolResult,isFindToolResult,isGrepToolResult,isLsToolResult,isPowerShellToolResult,isReadToolResult,isToolCallEventType,isWriteToolResult,keyHint,keyText,loadMcpConfig,loadProjectContextFiles,loadSkills,loadSkillsFromDir,main,migrateSessionEntries,parseArgs,parseFrontmatter,parseSessionEntries,parseSkillBlock,prepareBranchEntries,rawKeyHint,readStoredCredential,renderDiff,resizeImage,resolveCliModel,resolveModelScopeWithDiagnostics,runPrintMode,runRpcMode,serializeConversation,sessionEntryToContextMessages,setupCli,shouldCompact,stdioRpcTransport,stripFrontmatter,truncateHead,truncateLine,truncateTail,truncateToVisualLines,withFileMutationQueue,wrapRegisteredTool,wrapRegisteredTools};
|
package/dist/bundle/rpc-entry.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url);
|
|
3
|
-
import{configureHttpDispatcher,main,nameProcess}from"./chunks/chunk-
|
|
3
|
+
import{configureHttpDispatcher,main,nameProcess}from"./chunks/chunk-HPQUXAZK.js";import"./chunks/chunk-DYTENXHX.js";import"./chunks/chunk-FLHKNMLQ.js";import"./chunks/chunk-LT2C56HV.js";import"./chunks/chunk-6Z2347ML.js";import"./chunks/chunk-MF3VB62R.js";import"./chunks/chunk-URBBMV3M.js";import"./chunks/chunk-6QGV3IWD.js";import{APP_NAME}from"./chunks/chunk-LHRPMXVI.js";import"./chunks/chunk-USJZMAIR.js";import"./chunks/chunk-5AGIVGSV.js";import"./chunks/chunk-UKVNB6C4.js";import"./chunks/chunk-TMTOQ7H7.js";import"./chunks/chunk-7DDKXS2Q.js";import"./chunks/chunk-3V45WARH.js";import"./chunks/chunk-WCGU75WO.js";import"./chunks/chunk-LPXNMA2W.js";import"./chunks/chunk-JXWJOIH4.js";nameProcess(`${APP_NAME}-rpc`);process.env.PI_CODING_AGENT="true";process.env.AI_AGENT="pi";process.emitWarning=(()=>{});configureHttpDispatcher();main(["--mode","rpc",...process.argv.slice(2)]);
|
package/judge/dist/auth.js
CHANGED
|
@@ -21,7 +21,12 @@ var init_cloud_code = __esm({
|
|
|
21
21
|
import { constants } from "node:os";
|
|
22
22
|
import { join as join3 } from "node:path";
|
|
23
23
|
import { createInterface } from "node:readline";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
defaultModelPerProvider,
|
|
26
|
+
FileModelsStore,
|
|
27
|
+
ModelRuntime,
|
|
28
|
+
SettingsManager
|
|
29
|
+
} from "../../dist/bundle/index.js";
|
|
25
30
|
|
|
26
31
|
// packages/kyrn-judge/src/config.ts
|
|
27
32
|
import { readFileSync } from "node:fs";
|
|
@@ -57,8 +62,9 @@ var DEFAULT_CONFIG = {
|
|
|
57
62
|
"capability.disclosure": "off",
|
|
58
63
|
// Its rules deliver what matters; in single-prompt runs a "next_turn" verdict never reached anyone.
|
|
59
64
|
"notify.routing": "shadow",
|
|
60
|
-
//
|
|
61
|
-
|
|
65
|
+
// input.interjection follows the default: mu, not the key that sent it, decides when a message sent mid-run
|
|
66
|
+
// reaches the work. A message waits for its verdict for at most 1.5 s (`features.interjection.waitMs`), and goes
|
|
67
|
+
// as sent without one. A built-in "active" here would keep it acting under a default of shadow.
|
|
62
68
|
// Its steers were mostly wrong where it flagged anything at all, so it only records until it is reworked.
|
|
63
69
|
"turn.drift": "shadow"
|
|
64
70
|
},
|
|
@@ -1284,7 +1290,8 @@ var LEGACY_PROVIDERS = ["openai-codex", "anthropic"];
|
|
|
1284
1290
|
var LISTED_BY_ACCOUNT = /* @__PURE__ */ new Set(["google-antigravity"]);
|
|
1285
1291
|
var MODELS_READ_MS = 2e4;
|
|
1286
1292
|
var BROWSER_METHOD = "browser";
|
|
1287
|
-
var USAGE = "usage: mu auth status | mu auth login <provider> | mu auth logout <provider>";
|
|
1293
|
+
var USAGE = "usage: mu auth status | mu auth login <provider> [--paste] | mu auth logout <provider>";
|
|
1294
|
+
var ERROR_CODES = ["port_in_use", "login_taken_over"];
|
|
1288
1295
|
var AUTH_COMMANDS = ["status", "login", "logout"];
|
|
1289
1296
|
function modelsOf(runtime, provider, preferred) {
|
|
1290
1297
|
const models = runtime.getModels(provider).map((model) => ({ id: model.id, name: model.name || model.id }));
|
|
@@ -1300,16 +1307,42 @@ function shown(prompt) {
|
|
|
1300
1307
|
options: prompt.type === "select" ? prompt.options : void 0
|
|
1301
1308
|
};
|
|
1302
1309
|
}
|
|
1310
|
+
function errorLine(error) {
|
|
1311
|
+
const line = {
|
|
1312
|
+
type: "error",
|
|
1313
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1314
|
+
};
|
|
1315
|
+
if (!(error instanceof Error)) return line;
|
|
1316
|
+
const { code, port, holder } = error;
|
|
1317
|
+
if (typeof code !== "string" || !ERROR_CODES.includes(code)) return line;
|
|
1318
|
+
line.code = code;
|
|
1319
|
+
if (code !== "port_in_use") return line;
|
|
1320
|
+
if (typeof port === "number") line.port = port;
|
|
1321
|
+
if (holder && typeof holder === "object") {
|
|
1322
|
+
const { pid, command, pi } = holder;
|
|
1323
|
+
line.holder = {
|
|
1324
|
+
...typeof pid === "number" ? { pid } : {},
|
|
1325
|
+
...typeof command === "string" ? { command } : {},
|
|
1326
|
+
...pi === true ? { pi } : {}
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
return line;
|
|
1330
|
+
}
|
|
1303
1331
|
async function runAuth(argv, options) {
|
|
1304
1332
|
const { runtime, preferred, io } = options;
|
|
1305
1333
|
try {
|
|
1306
|
-
const
|
|
1334
|
+
const flags = argv.filter((arg) => arg.startsWith("--"));
|
|
1335
|
+
const [mode, provider, ...extra] = argv.filter((arg) => !arg.startsWith("--"));
|
|
1307
1336
|
if (!AUTH_COMMANDS.includes(mode ?? "")) throw new Error(USAGE);
|
|
1308
|
-
|
|
1337
|
+
const paste = flags.includes("--paste");
|
|
1338
|
+
if (extra.length > 0 || flags.some((flag) => flag !== "--paste") || paste && mode !== "login") {
|
|
1339
|
+
throw new Error(USAGE);
|
|
1340
|
+
}
|
|
1341
|
+
for (const extra2 of options.extra) runtime.registerNativeProvider(extra2);
|
|
1309
1342
|
if (options.extra.length > 0) {
|
|
1310
|
-
await runtime.refresh({ allowNetwork: false, providers: options.extra.map((
|
|
1343
|
+
await runtime.refresh({ allowNetwork: false, providers: options.extra.map((extra2) => extra2.id) });
|
|
1311
1344
|
}
|
|
1312
|
-
const offered = [...BUILT_IN_PROVIDERS, ...options.extra.map((
|
|
1345
|
+
const offered = [...BUILT_IN_PROVIDERS, ...options.extra.map((extra2) => extra2.id)];
|
|
1313
1346
|
const known = [...offered, ...LEGACY_PROVIDERS];
|
|
1314
1347
|
const status = async () => ({
|
|
1315
1348
|
type: "status",
|
|
@@ -1329,19 +1362,24 @@ async function runAuth(argv, options) {
|
|
|
1329
1362
|
}
|
|
1330
1363
|
if (mode === "logout") {
|
|
1331
1364
|
const stored = (await runtime.listCredentials()).find((credential) => credential.providerId === provider);
|
|
1332
|
-
if (stored?.type === "oauth")
|
|
1365
|
+
if (stored?.type === "oauth") {
|
|
1366
|
+
await runtime.logout(provider);
|
|
1367
|
+
if (LISTED_BY_ACCOUNT.has(provider)) await options.forgetModels?.(provider);
|
|
1368
|
+
}
|
|
1333
1369
|
io.say(await status());
|
|
1334
1370
|
return 0;
|
|
1335
1371
|
}
|
|
1336
|
-
await signIn(provider, options);
|
|
1372
|
+
await signIn(provider, paste ? { ...options, login: { ...options.login, pasteOnly: true } } : options);
|
|
1337
1373
|
return 0;
|
|
1338
1374
|
} catch (error) {
|
|
1339
|
-
io.say(
|
|
1375
|
+
io.say(errorLine(error));
|
|
1340
1376
|
return 1;
|
|
1341
1377
|
}
|
|
1342
1378
|
}
|
|
1343
|
-
async function signIn(provider, { runtime, preferred, io, modelsReadMs, login: login2 }) {
|
|
1379
|
+
async function signIn(provider, { runtime, preferred, io, modelsReadMs, login: login2, signal: ending2 }) {
|
|
1344
1380
|
const controller = new AbortController();
|
|
1381
|
+
ending2?.addEventListener("abort", () => controller.abort(), { once: true });
|
|
1382
|
+
if (ending2?.aborted) controller.abort();
|
|
1345
1383
|
let pending;
|
|
1346
1384
|
io.listen(
|
|
1347
1385
|
(line) => {
|
|
@@ -1402,6 +1440,7 @@ var authPath = join3(agentDir, "auth.json");
|
|
|
1402
1440
|
var modelsStorePath = join3(agentDir, "models-store.json");
|
|
1403
1441
|
var say = (message) => process.stdout.write(`${JSON.stringify(message)}
|
|
1404
1442
|
`);
|
|
1443
|
+
var ending = new AbortController();
|
|
1405
1444
|
function googleProviders() {
|
|
1406
1445
|
try {
|
|
1407
1446
|
const loaded = loadConfig({ dir: agentDir, env: process.env });
|
|
@@ -1421,16 +1460,18 @@ function googleProviders() {
|
|
|
1421
1460
|
}
|
|
1422
1461
|
}
|
|
1423
1462
|
async function main() {
|
|
1463
|
+
const modelsStore = new FileModelsStore(modelsStorePath);
|
|
1424
1464
|
const runtime = await ModelRuntime.create({
|
|
1425
1465
|
authPath,
|
|
1426
1466
|
modelsPath: join3(agentDir, "models.json"),
|
|
1427
|
-
|
|
1467
|
+
modelsStore,
|
|
1428
1468
|
refreshOnCreate: false
|
|
1429
1469
|
});
|
|
1430
1470
|
return runAuth(process.argv.slice(2), {
|
|
1431
1471
|
runtime,
|
|
1432
1472
|
preferred: defaultModelPerProvider,
|
|
1433
1473
|
extra: googleProviders(),
|
|
1474
|
+
forgetModels: (providerId) => modelsStore.delete(providerId),
|
|
1434
1475
|
login: {
|
|
1435
1476
|
agentName: "mu",
|
|
1436
1477
|
// The id pi keeps in settings.json for this installation: the same one a /login in the terminal sends.
|
|
@@ -1443,7 +1484,8 @@ async function main() {
|
|
|
1443
1484
|
lines.on("line", line);
|
|
1444
1485
|
lines.on("close", end);
|
|
1445
1486
|
}
|
|
1446
|
-
}
|
|
1487
|
+
},
|
|
1488
|
+
signal: ending.signal
|
|
1447
1489
|
});
|
|
1448
1490
|
}
|
|
1449
1491
|
var leaving = false;
|
|
@@ -1453,7 +1495,10 @@ var leave = (code) => {
|
|
|
1453
1495
|
void exitWhenUnlocked(code, [authPath, modelsStorePath], (exitCode) => process.exit(exitCode));
|
|
1454
1496
|
};
|
|
1455
1497
|
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"])
|
|
1456
|
-
process.on(signal, () =>
|
|
1498
|
+
process.on(signal, () => {
|
|
1499
|
+
ending.abort();
|
|
1500
|
+
leave(128 + constants.signals[signal]);
|
|
1501
|
+
});
|
|
1457
1502
|
main().then(leave, (error) => {
|
|
1458
1503
|
say({ type: "error", message: error instanceof Error ? error.message : String(error) });
|
|
1459
1504
|
leave(1);
|