UNPKG

eve

Version:

Filesystem-first framework for durable backend AI agents that run anywhere.

1 lines • 4.5 kB
import{CHATGPT_LOGIN_HINT,ChatGptSignInRequiredError,requestChatGptTokens}from"./oauth.js";import{ChatGptSignedOutError,isChatGptTokenFresh}from"./token.js";import{randomUUID}from"node:crypto";import{mkdir,rm,stat}from"node:fs/promises";import{isErrnoCode,isObject}from"#shared/guards.js";import{dirname,join}from"node:path";import{homedir}from"node:os";import{setTimeout}from"node:timers/promises";import{secrets}from"#compiled/just-secrets/index.js";const SECRET_ID={service:`eve`,name:`chatgpt`},MAX_SECRET_BYTES=2560;var ChatGptInvalidStoredSessionError=class extends Error{constructor(){super(`The ChatGPT session in the OS secret store is invalid. Sign in again from /login to replace it.`)}};function createChatGptCredentialStore(e=join(homedir(),`.eve`,`auth`,`chatgpt.json`),t=secrets){let n;async function removeLegacyFile(){try{await rm(e,{force:!0})}catch{throw Error(`ChatGPT credentials are stored securely, but the old plaintext session could not be removed. Stop older eve processes, remove ~/.eve/auth/chatgpt.json, and retry.`)}}async function load(){let e;try{e=await t.get(SECRET_ID)}catch{throw secretStoreError()}if(e===null){n=void 0;return}let r;try{if(Buffer.byteLength(e)>MAX_SECRET_BYTES)throw Error();r=JSON.parse(e)}catch{throw new ChatGptInvalidStoredSessionError}if(!isObject(r)||typeof r.sessionId!=`string`||!r.sessionId||typeof r.refreshToken!=`string`||!r.refreshToken)throw new ChatGptInvalidStoredSessionError;let i={refreshToken:r.refreshToken,...typeof r.accountId==`string`&&{accountId:r.accountId},...typeof r.accountLabel==`string`&&{accountLabel:r.accountLabel}},a=n?.sessionId===r.sessionId?{...i,accessToken:n.credentials.accessToken,expiresAt:n.credentials.expiresAt}:i;return await removeLegacyFile(),{sessionId:r.sessionId,credentials:a}}let r={async read(){return(await load())?.credentials},async resolveToken(e){let t=await r.read();if(!t)throw e.forceRefresh?new ChatGptSignInRequiredError:new ChatGptSignedOutError(`ChatGPT subscription is not signed in. ${CHATGPT_LOGIN_HINT}`);let n=tokenFromCredentials(t)?.token;(e.forceRefresh||!isChatGptTokenFresh(tokenFromCredentials(t),e.now()))&&(t=await r.update(async t=>{if(!t)throw new ChatGptSignInRequiredError;return`accessToken`in t&&isChatGptTokenFresh(tokenFromCredentials(t),e.now())&&(!e.forceRefresh||t.accessToken!==n)?t:requestChatGptTokens({grant_type:`refresh_token`,refresh_token:t.refreshToken},{fetch:e.fetch,previous:t,now:e.now})}));let i=tokenFromCredentials(t);if(!i)throw Error(`ChatGPT access token is unavailable. ${CHATGPT_LOGIN_HINT}`);return i},async update(r,i){await mkdir(dirname(e),{recursive:!0,mode:448});let a=`${e}.lock`,o=Date.now()+35e3;for(;;)try{await mkdir(a,{mode:448});break}catch(e){if(!isErrnoCode(e,`EEXIST`))throw e;let t=await stat(a).catch(e=>{if(!isErrnoCode(e,`ENOENT`))throw e});if(t&&Date.now()-t.mtimeMs>6e5){await rm(a,{recursive:!0,force:!0});continue}if(Date.now()>=o)throw Error(`Another eve process is updating the ChatGPT session. Retry shortly.`);await setTimeout(100)}try{let e=i?.replace?void 0:await load(),a=await r(e?.credentials),o=e?.sessionId??randomUUID(),s=JSON.stringify({sessionId:o,refreshToken:a.refreshToken,...a.accountId&&{accountId:a.accountId},...a.accountLabel&&{accountLabel:a.accountLabel}});if(Buffer.byteLength(s)>MAX_SECRET_BYTES)throw Error(`The ChatGPT refresh session exceeds the OS secret store's 2,560-byte limit. Use an API-key model instead.`);try{await t.set({...SECRET_ID,value:s})}catch{throw secretStoreError()}return n={sessionId:o,credentials:a},await removeLegacyFile(),a}finally{await rm(a,{recursive:!0,force:!0})}}};return r}let defaultStore;function getDefaultChatGptCredentialStore(){return defaultStore??=createChatGptCredentialStore()}function tokenFromCredentials(e){if(`accessToken`in e)return{token:e.accessToken,expiresAt:e.expiresAt,...e.accountId&&{accountId:e.accountId},...e.accountLabel&&{accountLabel:e.accountLabel}}}function secretStoreError(){let e=process.platform===`linux`?`Install libsecret-tools and start or unlock a Secret Service keyring with a session D-Bus. Headless sessions may need an API-key model.`:process.platform===`win32`?`Allow Windows PowerShell and Credential Manager access in your user session.`:`Unlock your login keychain and allow credential access.`;return Error(`Could not access ChatGPT credentials in the OS secret store. ${e} Retry from /login.`)}export{ChatGptInvalidStoredSessionError,createChatGptCredentialStore,getDefaultChatGptCredentialStore};