@bodhiapp/bodhi-js 0.0.28 → 0.0.30

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.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("@bodhiapp/bodhi-js-core");class f extends r.DirectClientBase{constructor(e,t){const s=r.createStoragePrefixWithBasePath(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),o={authClientId:e.authClientId,authServerUrl:e.authServerUrl,userRole:e.userRole,storagePrefix:s,logLevel:e.logLevel,loggerPrefix:"DirectWebClient",apiTimeoutMs:e.apiTimeoutMs};super(o,t),this.redirectUri=e.redirectUri}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;const s=e?.userRole??this.userRole,o=e?.flowType??"popup";e?.onProgress?.("requesting");const i=new r.AccessRequestBuilder(this.authClientId).requestedRole(s).flowType(o);if(e?.requested&&i.requested(e.requested),o==="redirect"){const u=e?.redirectUrl??this.redirectUri;i.redirectUrl(u)}const n=i.build(),l=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(l);e?.onProgress?.("reviewing");let h;if(o==="popup"){const u=async()=>{const E=await this.getAccessRequestStatus(c);if(E.status>=400)return null;const{status:S,access_request_scope:p}=E.body;return S==="approved"?{approved:!0,accessRequestScope:p??void 0}:["denied","failed","expired"].includes(S)?{approved:!1}:null},g=await r.openPopupReview(a,u,{intervalMs:e?.pollIntervalMs??r.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??r.DEFAULT_POLL_TIMEOUT_MS});if(!g.approved)throw r.createOperationError("auth_error","Access request was denied or expired");h=g.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,c),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${h??""}`.trim())}async performOAuthPkce(e){const t=r.generateCodeVerifier(),s=await r.generateCodeChallenge(t),o=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,o);const i=new URL(this.authEndpoints.authorize);throw i.searchParams.set("client_id",this.authClientId),i.searchParams.set("response_type","code"),i.searchParams.set("redirect_uri",this.redirectUri),i.searchParams.set("scope",e),i.searchParams.set("code_challenge",s),i.searchParams.set("code_challenge_method","S256"),i.searchParams.set("state",o),window.location.href=i.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(o.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(o),o}async handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:s,access_request_scope:o}=r.unwrapResponse(t);if(s!=="approved")throw r.createOperationError("auth_error",`Access request is not approved: ${s}`);const i=`openid profile email roles ${o??""}`.trim();return localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),this.performOAuthPkce(i)}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const s=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s})}catch(s){this.logger.warn("Token revocation failed:",s)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT);const t={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(t),t}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:this.redirectUri,client_id:this.authClientId,code_verifier:t})});if(!s.ok){const i=await s.text();throw new Error(`Token exchange failed: ${s.status} ${i}`)}const o=await s.json();if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,o.access_token),o.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,o.refresh_token),o.expires_in){const i=Date.now()+o.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,i.toString())}}async _storageGet(e){return localStorage.getItem(e)}async _storageSet(e){Object.entries(e).forEach(([t,s])=>{localStorage.setItem(t,String(s))})}async _storageRemove(e){e.forEach(t=>localStorage.removeItem(t))}_getRedirectUri(){return this.redirectUri}}const _=500,w=5e3,m=3e4;class R{constructor(e,t,s){this.state=r.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new r.Logger("WindowBodhiextClient",t.logLevel),this.authClientId=e,this.config=t,this.authEndpoints=r.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=s??r.NOOP_STATE_CALLBACK;const o=r.createStoragePrefixWithBasePath(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(o),this.apiTimeoutMs=t.apiTimeoutMs??m}setState(e){this.state=e,this.logger.info(`{state: ${JSON.stringify(e)}} - Setting client state`),this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}ensureBodhiext(){if(!this.bodhiext&&window.bodhiext&&(this.logger.info("Acquiring window.bodhiext reference"),this.bodhiext=window.bodhiext),!this.bodhiext)throw r.createOperationError("not_initialized","Client not initialized")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,s,o,i){this.ensureBodhiext();try{const n=new Promise((c,a)=>setTimeout(()=>a(new r.BodhiError("timeout_error",`[bodhi-js-sdk/web] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),l=(async()=>{let c=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)throw new r.BodhiError("auth_error","Not authenticated. Please log in first.");c={...c,Authorization:`Bearer ${a}`}}return this.bodhiext.sendApiRequest(e,t,s,c)})();return await Promise.race([l,n])}catch(n){if(n instanceof r.BodhiApiError||n instanceof r.BodhiError)throw n;if(n instanceof Error){const l=n;throw n.name==="BodhiApiError"&&typeof l.status=="number"&&l.body!=null?new r.BodhiApiError(l.status,l.body,n.message,l.headers):n.name==="BodhiError"&&typeof l.code=="string"?new r.BodhiError(l.code,n.message):new r.BodhiError("network_error",n.message)}throw new r.BodhiError("network_error",String(n))}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(e={}){if(!e.testConnection&&!e.selectedConnection)return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),r.EXTENSION_STATE_NOT_INITIALIZED;if(this.bodhiext&&!e.testConnection)return this.logger.debug("Already have bodhiext handle, skipping polling"),this.state;if(!this.bodhiext){const o=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??w,i=e.intervalMs??this.config.initParams?.extension?.intervalMs??_,n=Date.now();if(!await new Promise(c=>{const a=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-n>=o){c(!1);return}setTimeout(a,i)};a()}))return this.logger.warn("Extension discovery timed out"),this.setState(r.EXTENSION_STATE_NOT_FOUND),this.state}const t=await this.bodhiext.getExtensionId();this.logger.info(`Extension discovered: ${t}`);const s={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const o=await this.getServerState();this.setState({...s,server:o}),this.logger.info(`Server connectivity tested: ${o.status}`)}catch(o){this.logger.error("Failed to get server state:",o),this.setState({...s,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(s);return this.state}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;this.ensureBodhiext();const s=e?.userRole??this.config.userRole,o=e?.flowType??"popup";e?.onProgress?.("requesting");const i=new r.AccessRequestBuilder(this.authClientId).requestedRole(s).flowType(o);if(e?.requested&&i.requested(e.requested),o==="redirect"){const u=e?.redirectUrl??this.config.redirectUri;i.redirectUrl(u)}const n=i.build(),l=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(l);e?.onProgress?.("reviewing");let h;if(o==="popup"){const u=async()=>{const E=await this.getAccessRequestStatus(c);if(E.status>=400)return null;const{status:S,access_request_scope:p}=E.body;return S==="approved"?{approved:!0,accessRequestScope:p??void 0}:["denied","failed","expired"].includes(S)?{approved:!1}:null},g=await r.openPopupReview(a,u,{intervalMs:e?.pollIntervalMs??r.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??r.DEFAULT_POLL_TIMEOUT_MS});if(!g.approved)throw r.createOperationError("auth_error","Access request was denied or expired");h=g.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,c),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${h??""}`.trim())}async handleOAuthCallback(e,t){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(o.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(o),o}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const s=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:e,redirect_uri:this.config.redirectUri,code_verifier:t}),o=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s});if(!o.ok){const n=await o.text();throw new Error(`Token exchange failed: ${o.status} ${n}`)}const i=await o.json();if(!i.access_token)throw new Error("No access token received");if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,i.access_token),i.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,i.refresh_token),i.expires_in){const n=Date.now()+i.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,n.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const s=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s})}catch(s){this.logger.warn("Token revocation failed:",s)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const t={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{return{status:"authenticated",user:r.extractUserInfo(e),accessToken:e,error:null}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null}}}async _getAccessTokenRaw(){const e=localStorage.getItem(this.storageKeys.ACCESS_TOKEN),t=localStorage.getItem(this.storageKeys.EXPIRES_AT);if(!e)return null;if(t){const s=parseInt(t,10);if(Date.now()>=s-5*1e3){const o=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return o?this._tryRefreshToken(o):null}}return e}async _tryRefreshToken(e){if(this.refreshPromise)return this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise;this.refreshPromise=this._doRefreshToken(e);try{return await this.refreshPromise}finally{this.refreshPromise=null}}async _doRefreshToken(e){this.logger.debug("Refreshing access token");try{const t=await r.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success){this._storeRefreshedTokens(t.tokens);const s=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:s,accessToken:t.tokens.access_token,error:null}),this.logger.info("Token refreshed successfully"),t.tokens.access_token}if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),this.clearAuthStorage(),this.setAuthState({status:"unauthenticated",user:null,accessToken:null,error:null}),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),r.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}clearAuthStorage(){localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT)}_storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3;localStorage.setItem(this.storageKeys.ACCESS_TOKEN,e.access_token),localStorage.setItem(this.storageKeys.EXPIRES_AT,String(t)),e.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,e.refresh_token)}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){try{const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(e.status>=400)return r.BACKEND_SERVER_NOT_REACHABLE;const t=e.body,s=t.version||"unknown";switch(t.status){case"ready":return{status:"ready",version:s,error:null,deployment:t.deployment??null,client_id:t.client_id??null};case"setup":return r.backendServerNotReady("setup",s,void 0,t.deployment,t.client_id);case"resource_admin":return r.backendServerNotReady("resource_admin",s,void 0,t.deployment,t.client_id);case"error":return r.backendServerNotReady("error",s,t.error?{message:t.error.message,type:t.error.type}:r.SERVER_ERROR_CODES.SERVER_NOT_READY,t.deployment,t.client_id);default:return r.BACKEND_SERVER_NOT_REACHABLE}}catch{return r.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,s,o,i=!0){this.ensureBodhiext();let n=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)throw r.createOperationError("auth_error","Not authenticated. Please log in first.");n={...n,Authorization:`Bearer ${a}`}}const c=this.bodhiext.sendStreamRequest(e,t,s,n).getReader();try{for(;;){const{value:a,done:h}=await c.read();if(h||a?.done)break;yield a.body}}catch(a){if(a instanceof r.BodhiApiError||a instanceof r.BodhiError)throw a;if(a instanceof Error){const h=a;throw a.name==="BodhiApiError"&&typeof h.status=="number"&&h.body!=null?new r.BodhiApiError(h.status,h.body,a.message,h.headers):a.name==="BodhiError"&&typeof h.code=="string"?new r.BodhiError(h.code,a.message):new r.BodhiError("extension_error",a.message)}throw a}finally{c.releaseLock()}}get chat(){return this._chat??=new r.Chat(this)}get models(){return this._models??=new r.Models(this)}get embeddings(){return this._embeddings??=new r.Embeddings(this)}get toolsets(){return this._toolsets??=new r.Toolsets(this)}get mcps(){return this._mcps??=new r.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}async getAccessRequestStatus(e){return this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`,void 0,{},!1)}async pollAccessRequestStatus(e,t){return r.pollAccessRequestUntilResolved(s=>this.getAccessRequestStatus(s),e,t)}async performOAuthPkce(e){const t=r.generateCodeVerifier(),s=await r.generateCodeChallenge(t),o=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,o);const i=e.split(" ").filter(Boolean),n=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:i.join(" "),state:o,code_challenge:s,code_challenge_method:"S256"});return window.location.href=`${this.authEndpoints.authorize}?${n}`,new Promise(()=>{})}async handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:s,access_request_scope:o}=r.unwrapResponse(t);if(s!=="approved")throw r.createOperationError("auth_error",`Access request is not approved: ${s}`);const i=`openid profile email roles ${o??""}`.trim();return localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),this.performOAuthPkce(i)}serialize(){return{extensionId:this.state.type==="extension"&&this.state.extension==="ready"?this.state.extensionId:void 0}}async debug(){return{type:"WindowBodhiextClient",state:this.state,authState:await this.getAuthState(),bodhiextAvailable:this.bodhiext!==null,authClientId:this.authClientId,authServerUrl:this.config.authServerUrl,redirectUri:this.config.redirectUri,userRole:this.config.userRole}}}function T(d){if(typeof window>"u")throw new Error("redirectUri required in non-browser environment");const e=d==="/"?"":d.replace(/\/$/,"");return`${window.location.origin}${e}/callback`}class y extends r.BaseFacadeClient{constructor(e,t,s){const o=t||{},i={basePath:o.basePath||"/",redirectUri:o.redirectUri||T(o.basePath||"/"),authServerUrl:o.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userRole:o.userRole||"scope_user_user",logLevel:o.logLevel||"warn",apiTimeoutMs:o.apiTimeoutMs,initParams:o.initParams};super(e,i,s)}createLogger(e){return new r.Logger("WebUIClient",e.logLevel)}createStoragePrefix(e){return r.createStoragePrefixWithBasePath(e.basePath,r.STORAGE_PREFIXES.WEB)}createExtClient(e,t){return new R(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,userRole:e.userRole,basePath:e.basePath,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,s){return new f({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userRole:t.userRole,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},s)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}async handleAccessRequestCallback(e){return this.connectionMode==="direct"?this.directClient.handleAccessRequestCallback(e):this.extClient.handleAccessRequestCallback(e)}}const A="production";exports.WEB_BUILD_MODE=A;exports.WebUIClient=y;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("@bodhiapp/bodhi-js-core");class p extends r.DirectClientBase{constructor(e,t){const s=r.createStoragePrefixWithBasePath(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),o={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:s,logLevel:e.logLevel,loggerPrefix:"DirectWebClient",apiTimeoutMs:e.apiTimeoutMs};super(o,t),this.redirectUri=e.redirectUri}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;const s=e?.userRole??"scope_user_user",o=e?.flowType??"popup";e?.onProgress?.("requesting");const i=new r.AccessRequestBuilder(this.authClientId).requestedRole(s).flowType(o);if(e?.requested&&i.requested(e.requested),o==="redirect"){const u=e?.redirectUrl??this.redirectUri;i.redirectUrl(u)}const n=i.build(),h=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(h);e?.onProgress?.("reviewing");let l;if(o==="popup"){const u=async()=>{const S=await this.getAccessRequestStatus(c);if(S.status>=400)return null;const{status:g,access_request_scope:f}=S.body;return g==="approved"?{approved:!0,accessRequestScope:f??void 0}:["denied","failed","expired"].includes(g)?{approved:!1,status:g}:null},d=await r.openPopupReview(a,u,{intervalMs:e?.pollIntervalMs??r.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??r.DEFAULT_POLL_TIMEOUT_MS});d.approved||r.throwAccessRequestDenialError(d.status??"unknown"),l=d.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,c),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${l??""}`.trim())}async performOAuthPkce(e){const t=r.generateCodeVerifier(),s=await r.generateCodeChallenge(t),o=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,o);const i=new URL(this.authEndpoints.authorize);throw i.searchParams.set("client_id",this.authClientId),i.searchParams.set("response_type","code"),i.searchParams.set("redirect_uri",this.redirectUri),i.searchParams.set("scope",e),i.searchParams.set("code_challenge",s),i.searchParams.set("code_challenge_method","S256"),i.searchParams.set("state",o),window.location.href=i.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(o.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(o),o}async handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:s,access_request_scope:o}=r.unwrapResponse(t);localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),s!=="approved"&&r.throwAccessRequestDenialError(s);const i=`openid profile email roles ${o??""}`.trim();return this.performOAuthPkce(i)}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const s=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s})}catch(s){this.logger.warn("Token revocation failed:",s)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT);const t={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(t),t}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:this.redirectUri,client_id:this.authClientId,code_verifier:t})});if(!s.ok){const i=await s.text();throw new Error(`Token exchange failed: ${s.status} ${i}`)}const o=await s.json();if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,o.access_token),o.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,o.refresh_token),o.expires_in){const i=Date.now()+o.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,i.toString())}}async _storageGet(e){return localStorage.getItem(e)}async _storageSet(e){Object.entries(e).forEach(([t,s])=>{localStorage.setItem(t,String(s))})}async _storageRemove(e){e.forEach(t=>localStorage.removeItem(t))}_getRedirectUri(){return this.redirectUri}}const m=500,w=5e3,_=3e4;class R{constructor(e,t,s){this.state=r.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new r.Logger("WindowBodhiextClient",t.logLevel),this.authClientId=e,this.config=t,this.authEndpoints=r.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=s??r.NOOP_STATE_CALLBACK;const o=r.createStoragePrefixWithBasePath(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(o),this.apiTimeoutMs=t.apiTimeoutMs??_}setState(e){this.state=e,this.logger.info(`{state: ${JSON.stringify(e)}} - Setting client state`),this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}ensureBodhiext(){if(!this.bodhiext&&window.bodhiext&&(this.logger.info("Acquiring window.bodhiext reference"),this.bodhiext=window.bodhiext),!this.bodhiext)throw r.createOperationError("not_initialized","Client not initialized")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,s,o,i){this.ensureBodhiext();try{const n=new Promise((c,a)=>setTimeout(()=>a(new r.BodhiError("timeout_error",`[bodhi-js-sdk/web] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),h=(async()=>{let c=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)throw new r.BodhiError("auth_error","Not authenticated. Please log in first.");c={...c,Authorization:`Bearer ${a}`}}return this.bodhiext.sendApiRequest(e,t,s,c)})();return await Promise.race([h,n])}catch(n){if(n instanceof r.BodhiApiError||n instanceof r.BodhiError)throw n;if(n instanceof Error){const h=n;throw n.name==="BodhiApiError"&&typeof h.status=="number"&&h.body!=null?new r.BodhiApiError(h.status,h.body,n.message,h.headers):n.name==="BodhiError"&&typeof h.code=="string"?new r.BodhiError(h.code,n.message):new r.BodhiError("network_error",n.message)}throw new r.BodhiError("network_error",String(n))}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(e={}){if(!e.testConnection&&!e.selectedConnection)return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),r.EXTENSION_STATE_NOT_INITIALIZED;if(this.bodhiext&&!e.testConnection)return this.logger.debug("Already have bodhiext handle, skipping polling"),this.state;if(!this.bodhiext){const o=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??w,i=e.intervalMs??this.config.initParams?.extension?.intervalMs??m,n=Date.now();if(!await new Promise(c=>{const a=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-n>=o){c(!1);return}setTimeout(a,i)};a()}))return this.logger.warn("Extension discovery timed out"),this.setState(r.EXTENSION_STATE_NOT_FOUND),this.state}const t=await this.bodhiext.getExtensionId();this.logger.info(`Extension discovered: ${t}`);const s={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const o=await this.getServerState();this.setState({...s,server:o}),this.logger.info(`Server connectivity tested: ${o.status}`)}catch(o){this.logger.error("Failed to get server state:",o),this.setState({...s,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(s);return this.state}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;this.ensureBodhiext();const s=e?.userRole??"scope_user_user",o=e?.flowType??"popup";e?.onProgress?.("requesting");const i=new r.AccessRequestBuilder(this.authClientId).requestedRole(s).flowType(o);if(e?.requested&&i.requested(e.requested),o==="redirect"){const u=e?.redirectUrl??this.config.redirectUri;i.redirectUrl(u)}const n=i.build(),h=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(h);e?.onProgress?.("reviewing");let l;if(o==="popup"){const u=async()=>{const S=await this.getAccessRequestStatus(c);if(S.status>=400)return null;const{status:g,access_request_scope:f}=S.body;return g==="approved"?{approved:!0,accessRequestScope:f??void 0}:["denied","failed","expired"].includes(g)?{approved:!1,status:g}:null},d=await r.openPopupReview(a,u,{intervalMs:e?.pollIntervalMs??r.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??r.DEFAULT_POLL_TIMEOUT_MS});d.approved||r.throwAccessRequestDenialError(d.status??"unknown"),l=d.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,c),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${l??""}`.trim())}async handleOAuthCallback(e,t){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(o.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(o),o}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const s=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:e,redirect_uri:this.config.redirectUri,code_verifier:t}),o=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s});if(!o.ok){const n=await o.text();throw new Error(`Token exchange failed: ${o.status} ${n}`)}const i=await o.json();if(!i.access_token)throw new Error("No access token received");if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,i.access_token),i.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,i.refresh_token),i.expires_in){const n=Date.now()+i.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,n.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const s=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s})}catch(s){this.logger.warn("Token revocation failed:",s)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const t={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{return{status:"authenticated",user:r.extractUserInfo(e),accessToken:e,error:null}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null}}}async _getAccessTokenRaw(){const e=localStorage.getItem(this.storageKeys.ACCESS_TOKEN),t=localStorage.getItem(this.storageKeys.EXPIRES_AT);if(!e)return null;if(t){const s=parseInt(t,10);if(Date.now()>=s-5*1e3){const o=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return o?this._tryRefreshToken(o):null}}return e}async _tryRefreshToken(e){if(this.refreshPromise)return this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise;this.refreshPromise=this._doRefreshToken(e);try{return await this.refreshPromise}finally{this.refreshPromise=null}}async _doRefreshToken(e){this.logger.debug("Refreshing access token");try{const t=await r.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success){this._storeRefreshedTokens(t.tokens);const s=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:s,accessToken:t.tokens.access_token,error:null}),this.logger.info("Token refreshed successfully"),t.tokens.access_token}if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),this.clearAuthStorage(),this.setAuthState({status:"unauthenticated",user:null,accessToken:null,error:null}),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),r.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}clearAuthStorage(){localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT)}_storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3;localStorage.setItem(this.storageKeys.ACCESS_TOKEN,e.access_token),localStorage.setItem(this.storageKeys.EXPIRES_AT,String(t)),e.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,e.refresh_token)}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){try{const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(e.status>=400)return r.BACKEND_SERVER_NOT_REACHABLE;const t=e.body,s=t.version||"unknown";switch(t.status){case"ready":return{status:"ready",version:s,error:null,deployment:t.deployment??null,client_id:t.client_id??null};case"setup":return r.backendServerNotReady("setup",s,void 0,t.deployment,t.client_id);case"resource_admin":return r.backendServerNotReady("resource_admin",s,void 0,t.deployment,t.client_id);case"error":return r.backendServerNotReady("error",s,t.error?{message:t.error.message,type:t.error.type}:r.SERVER_ERROR_CODES.SERVER_NOT_READY,t.deployment,t.client_id);default:return r.BACKEND_SERVER_NOT_REACHABLE}}catch{return r.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,s,o,i=!0){this.ensureBodhiext();let n=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)throw r.createOperationError("auth_error","Not authenticated. Please log in first.");n={...n,Authorization:`Bearer ${a}`}}const c=this.bodhiext.sendStreamRequest(e,t,s,n).getReader();try{for(;;){const{value:a,done:l}=await c.read();if(l||a?.done)break;yield a.body}}catch(a){if(a instanceof r.BodhiApiError||a instanceof r.BodhiError)throw a;if(a instanceof Error){const l=a;throw a.name==="BodhiApiError"&&typeof l.status=="number"&&l.body!=null?new r.BodhiApiError(l.status,l.body,a.message,l.headers):a.name==="BodhiError"&&typeof l.code=="string"?new r.BodhiError(l.code,a.message):new r.BodhiError("extension_error",a.message)}throw a}finally{c.releaseLock()}}get chat(){return this._chat??=new r.Chat(this)}get models(){return this._models??=new r.Models(this)}get embeddings(){return this._embeddings??=new r.Embeddings(this)}get mcps(){return this._mcps??=new r.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}async getAccessRequestStatus(e){return this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`,void 0,{},!1)}async pollAccessRequestStatus(e,t){return r.pollAccessRequestUntilResolved(s=>this.getAccessRequestStatus(s),e,t)}async performOAuthPkce(e){const t=r.generateCodeVerifier(),s=await r.generateCodeChallenge(t),o=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,o);const i=e.split(" ").filter(Boolean),n=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:i.join(" "),state:o,code_challenge:s,code_challenge_method:"S256"});return window.location.href=`${this.authEndpoints.authorize}?${n}`,new Promise(()=>{})}async handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:s,access_request_scope:o}=r.unwrapResponse(t);localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),s!=="approved"&&r.throwAccessRequestDenialError(s);const i=`openid profile email roles ${o??""}`.trim();return this.performOAuthPkce(i)}serialize(){return{extensionId:this.state.type==="extension"&&this.state.extension==="ready"?this.state.extensionId:void 0}}async debug(){return{type:"WindowBodhiextClient",state:this.state,authState:await this.getAuthState(),bodhiextAvailable:this.bodhiext!==null,authClientId:this.authClientId,authServerUrl:this.config.authServerUrl,redirectUri:this.config.redirectUri}}}function y(E){if(typeof window>"u")throw new Error("redirectUri required in non-browser environment");const e=E==="/"?"":E.replace(/\/$/,"");return`${window.location.origin}${e}/callback`}class T extends r.BaseFacadeClient{constructor(e,t,s){const o=t||{},i={basePath:o.basePath||"/",redirectUri:o.redirectUri||y(o.basePath||"/"),authServerUrl:o.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:o.logLevel||"warn",apiTimeoutMs:o.apiTimeoutMs,initParams:o.initParams};super(e,i,s)}createLogger(e){return new r.Logger("WebUIClient",e.logLevel)}createStoragePrefix(e){return r.createStoragePrefixWithBasePath(e.basePath,r.STORAGE_PREFIXES.WEB)}createExtClient(e,t){return new R(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,basePath:e.basePath,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,s){return new p({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},s)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}async handleAccessRequestCallback(e){return this.connectionMode==="direct"?this.directClient.handleAccessRequestCallback(e):this.extClient.handleAccessRequestCallback(e)}}const A="production";exports.WEB_BUILD_MODE=A;exports.WebUIClient=T;
@@ -1,13 +1,12 @@
1
- import { DirectClientBase as U, createStoragePrefixWithBasePath as T, STORAGE_PREFIXES as I, AccessRequestBuilder as v, unwrapResponse as E, openPopupReview as k, DEFAULT_POLL_TIMEOUT_MS as x, DEFAULT_POLL_INTERVAL_MS as b, createOperationError as u, generateCodeVerifier as _, generateCodeChallenge as P, EXTENSION_STATE_NOT_INITIALIZED as A, Logger as O, createOAuthEndpoints as q, NOOP_STATE_CALLBACK as K, createStorageKeys as L, BodhiError as h, BodhiApiError as m, EXTENSION_STATE_NOT_FOUND as N, PENDING_EXTENSION_READY as D, BACKEND_SERVER_NOT_REACHABLE as w, extractUserInfo as C, refreshAccessToken as M, backendServerNotReady as R, SERVER_ERROR_CODES as F, Chat as B, Models as $, Embeddings as V, Toolsets as z, Mcps as H, pollAccessRequestUntilResolved as W, BaseFacadeClient as X } from "@bodhiapp/bodhi-js-core";
2
- class G extends U {
1
+ import { DirectClientBase as K, createStoragePrefixWithBasePath as I, STORAGE_PREFIXES as A, AccessRequestBuilder as k, unwrapResponse as w, openPopupReview as b, DEFAULT_POLL_TIMEOUT_MS as x, DEFAULT_POLL_INTERVAL_MS as P, throwAccessRequestDenialError as E, generateCodeVerifier as _, generateCodeChallenge as O, EXTENSION_STATE_NOT_INITIALIZED as C, Logger as U, createOAuthEndpoints as q, NOOP_STATE_CALLBACK as L, createStorageKeys as N, createOperationError as T, BodhiError as h, BodhiApiError as m, EXTENSION_STATE_NOT_FOUND as D, PENDING_EXTENSION_READY as M, BACKEND_SERVER_NOT_REACHABLE as p, extractUserInfo as v, refreshAccessToken as F, backendServerNotReady as R, SERVER_ERROR_CODES as B, Chat as $, Models as V, Embeddings as z, Mcps as H, pollAccessRequestUntilResolved as W, BaseFacadeClient as X } from "@bodhiapp/bodhi-js-core";
2
+ class G extends K {
3
3
  constructor(e, t) {
4
- const s = T(
4
+ const s = I(
5
5
  e.basePath,
6
- I.WEB_DIRECT
6
+ A.WEB_DIRECT
7
7
  ), r = {
8
8
  authClientId: e.authClientId,
9
9
  authServerUrl: e.authServerUrl,
10
- userRole: e.userRole,
11
10
  storagePrefix: s,
12
11
  logLevel: e.logLevel,
13
12
  loggerPrefix: "DirectWebClient",
@@ -22,36 +21,34 @@ class G extends U {
22
21
  const t = await this.getAuthState();
23
22
  if (t.status === "authenticated")
24
23
  return t;
25
- const s = e?.userRole ?? this.userRole, r = e?.flowType ?? "popup";
24
+ const s = e?.userRole ?? "scope_user_user", r = e?.flowType ?? "popup";
26
25
  e?.onProgress?.("requesting");
27
- const o = new v(this.authClientId).requestedRole(s).flowType(r);
26
+ const o = new k(this.authClientId).requestedRole(s).flowType(r);
28
27
  if (e?.requested && o.requested(e.requested), r === "redirect") {
29
- const g = e?.redirectUrl ?? this.redirectUri;
30
- o.redirectUrl(g);
28
+ const f = e?.redirectUrl ?? this.redirectUri;
29
+ o.redirectUrl(f);
31
30
  }
32
- const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = E(c);
31
+ const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = w(c);
33
32
  e?.onProgress?.("reviewing");
34
33
  let l;
35
34
  if (r === "popup") {
36
- const f = await k(i, async () => {
37
- const p = await this.getAccessRequestStatus(n);
38
- if (p.status >= 400) return null;
39
- const { status: S, access_request_scope: y } = p.body;
40
- return S === "approved" ? { approved: !0, accessRequestScope: y ?? void 0 } : ["denied", "failed", "expired"].includes(S) ? { approved: !1 } : null;
35
+ const u = await b(i, async () => {
36
+ const S = await this.getAccessRequestStatus(n);
37
+ if (S.status >= 400) return null;
38
+ const { status: d, access_request_scope: y } = S.body;
39
+ return d === "approved" ? { approved: !0, accessRequestScope: y ?? void 0 } : ["denied", "failed", "expired"].includes(d) ? { approved: !1, status: d } : null;
41
40
  }, {
42
- intervalMs: e?.pollIntervalMs ?? b,
41
+ intervalMs: e?.pollIntervalMs ?? P,
43
42
  timeoutMs: e?.pollTimeoutMs ?? x
44
43
  });
45
- if (!f.approved)
46
- throw u("auth_error", "Access request was denied or expired");
47
- l = f.accessRequestScope;
44
+ u.approved || E(u.status ?? "unknown"), l = u.accessRequestScope;
48
45
  } else
49
46
  return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, n), window.location.href = i, new Promise(() => {
50
47
  });
51
48
  return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${l ?? ""}`.trim());
52
49
  }
53
50
  async performOAuthPkce(e) {
54
- const t = _(), s = await P(t), r = _();
51
+ const t = _(), s = await O(t), r = _();
55
52
  localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, r);
56
53
  const o = new URL(this.authEndpoints.authorize);
57
54
  throw o.searchParams.set("client_id", this.authClientId), o.searchParams.set("response_type", "code"), o.searchParams.set("redirect_uri", this.redirectUri), o.searchParams.set("scope", e), o.searchParams.set("code_challenge", s), o.searchParams.set("code_challenge_method", "S256"), o.searchParams.set("state", r), window.location.href = o.toString(), new Error("Redirect initiated");
@@ -67,11 +64,10 @@ class G extends U {
67
64
  return this.setAuthState(r), r;
68
65
  }
69
66
  async handleAccessRequestCallback(e) {
70
- const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = E(t);
71
- if (s !== "approved")
72
- throw u("auth_error", `Access request is not approved: ${s}`);
67
+ const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = w(t);
68
+ localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), s !== "approved" && E(s);
73
69
  const o = `openid profile email roles ${r ?? ""}`.trim();
74
- return localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), this.performOAuthPkce(o);
70
+ return this.performOAuthPkce(o);
75
71
  }
76
72
  async logout() {
77
73
  const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
@@ -152,9 +148,9 @@ class G extends U {
152
148
  const j = 500, Q = 5e3, Y = 3e4;
153
149
  class J {
154
150
  constructor(e, t, s) {
155
- this.state = A, this.bodhiext = null, this.refreshPromise = null, this.logger = new O("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = q(this.config.authServerUrl), this.onStateChange = s ?? K;
156
- const r = T(t.basePath, I.WEB_EXT);
157
- this.storageKeys = L(r), this.apiTimeoutMs = t.apiTimeoutMs ?? Y;
151
+ this.state = C, this.bodhiext = null, this.refreshPromise = null, this.logger = new U("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = q(this.config.authServerUrl), this.onStateChange = s ?? L;
152
+ const r = I(t.basePath, A.WEB_EXT);
153
+ this.storageKeys = N(r), this.apiTimeoutMs = t.apiTimeoutMs ?? Y;
158
154
  }
159
155
  /**
160
156
  * Set client state and notify callback
@@ -183,7 +179,7 @@ class J {
183
179
  */
184
180
  ensureBodhiext() {
185
181
  if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
186
- throw u("not_initialized", "Client not initialized");
182
+ throw T("not_initialized", "Client not initialized");
187
183
  }
188
184
  /**
189
185
  * Send extension request via window.bodhiext.sendExtRequest
@@ -257,7 +253,7 @@ class J {
257
253
  */
258
254
  async init(e = {}) {
259
255
  if (!e.testConnection && !e.selectedConnection)
260
- return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), A;
256
+ return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), C;
261
257
  if (this.bodhiext && !e.testConnection)
262
258
  return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
263
259
  if (!this.bodhiext) {
@@ -276,7 +272,7 @@ class J {
276
272
  };
277
273
  i();
278
274
  }))
279
- return this.logger.warn("Extension discovery timed out"), this.setState(N), this.state;
275
+ return this.logger.warn("Extension discovery timed out"), this.setState(D), this.state;
280
276
  }
281
277
  const t = await this.bodhiext.getExtensionId();
282
278
  this.logger.info(`Extension discovered: ${t}`);
@@ -284,14 +280,14 @@ class J {
284
280
  type: "extension",
285
281
  extension: "ready",
286
282
  extensionId: t,
287
- server: D
283
+ server: M
288
284
  };
289
285
  if (e.testConnection)
290
286
  try {
291
287
  const r = await this.getServerState();
292
288
  this.setState({ ...s, server: r }), this.logger.info(`Server connectivity tested: ${r.status}`);
293
289
  } catch (r) {
294
- this.logger.error("Failed to get server state:", r), this.setState({ ...s, server: w });
290
+ this.logger.error("Failed to get server state:", r), this.setState({ ...s, server: p });
295
291
  }
296
292
  else
297
293
  this.setState(s);
@@ -310,29 +306,27 @@ class J {
310
306
  if (t.status === "authenticated")
311
307
  return t;
312
308
  this.ensureBodhiext();
313
- const s = e?.userRole ?? this.config.userRole, r = e?.flowType ?? "popup";
309
+ const s = e?.userRole ?? "scope_user_user", r = e?.flowType ?? "popup";
314
310
  e?.onProgress?.("requesting");
315
- const o = new v(this.authClientId).requestedRole(s).flowType(r);
311
+ const o = new k(this.authClientId).requestedRole(s).flowType(r);
316
312
  if (e?.requested && o.requested(e.requested), r === "redirect") {
317
- const g = e?.redirectUrl ?? this.config.redirectUri;
318
- o.redirectUrl(g);
313
+ const f = e?.redirectUrl ?? this.config.redirectUri;
314
+ o.redirectUrl(f);
319
315
  }
320
- const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = E(c);
316
+ const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = w(c);
321
317
  e?.onProgress?.("reviewing");
322
318
  let l;
323
319
  if (r === "popup") {
324
- const f = await k(i, async () => {
325
- const p = await this.getAccessRequestStatus(n);
326
- if (p.status >= 400) return null;
327
- const { status: S, access_request_scope: y } = p.body;
328
- return S === "approved" ? { approved: !0, accessRequestScope: y ?? void 0 } : ["denied", "failed", "expired"].includes(S) ? { approved: !1 } : null;
320
+ const u = await b(i, async () => {
321
+ const S = await this.getAccessRequestStatus(n);
322
+ if (S.status >= 400) return null;
323
+ const { status: d, access_request_scope: y } = S.body;
324
+ return d === "approved" ? { approved: !0, accessRequestScope: y ?? void 0 } : ["denied", "failed", "expired"].includes(d) ? { approved: !1, status: d } : null;
329
325
  }, {
330
- intervalMs: e?.pollIntervalMs ?? b,
326
+ intervalMs: e?.pollIntervalMs ?? P,
331
327
  timeoutMs: e?.pollTimeoutMs ?? x
332
328
  });
333
- if (!f.approved)
334
- throw u("auth_error", "Access request was denied or expired");
335
- l = f.accessRequestScope;
329
+ u.approved || E(u.status ?? "unknown"), l = u.accessRequestScope;
336
330
  } else
337
331
  return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, n), window.location.href = i, new Promise(() => {
338
332
  });
@@ -425,7 +419,7 @@ class J {
425
419
  if (!e)
426
420
  return { status: "unauthenticated", user: null, accessToken: null, error: null };
427
421
  try {
428
- return { status: "authenticated", user: C(e), accessToken: e, error: null };
422
+ return { status: "authenticated", user: v(e), accessToken: e, error: null };
429
423
  } catch (t) {
430
424
  return this.logger.error("Failed to parse token:", t), { status: "unauthenticated", user: null, accessToken: null, error: null };
431
425
  }
@@ -467,14 +461,14 @@ class J {
467
461
  async _doRefreshToken(e) {
468
462
  this.logger.debug("Refreshing access token");
469
463
  try {
470
- const t = await M(
464
+ const t = await F(
471
465
  this.authEndpoints.token,
472
466
  e,
473
467
  this.authClientId
474
468
  );
475
469
  if (t.success) {
476
470
  this._storeRefreshedTokens(t.tokens);
477
- const s = C(t.tokens.access_token);
471
+ const s = v(t.tokens.access_token);
478
472
  return this.setAuthState({
479
473
  status: "authenticated",
480
474
  user: s,
@@ -492,7 +486,7 @@ class J {
492
486
  } catch (t) {
493
487
  this.logger.warn("Token refresh failed:", t);
494
488
  }
495
- throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), u(
489
+ throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), T(
496
490
  "auth_error",
497
491
  "Access token expired and unable to refresh. Try logging out and logging in again."
498
492
  );
@@ -521,7 +515,7 @@ class J {
521
515
  try {
522
516
  const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
523
517
  if (e.status >= 400)
524
- return w;
518
+ return p;
525
519
  const t = e.body, s = t.version || "unknown";
526
520
  switch (t.status) {
527
521
  case "ready":
@@ -552,15 +546,15 @@ class J {
552
546
  return R(
553
547
  "error",
554
548
  s,
555
- t.error ? { message: t.error.message, type: t.error.type } : F.SERVER_NOT_READY,
549
+ t.error ? { message: t.error.message, type: t.error.type } : B.SERVER_NOT_READY,
556
550
  t.deployment,
557
551
  t.client_id
558
552
  );
559
553
  default:
560
- return w;
554
+ return p;
561
555
  }
562
556
  } catch {
563
- return w;
557
+ return p;
564
558
  }
565
559
  }
566
560
  /**
@@ -573,7 +567,7 @@ class J {
573
567
  if (o) {
574
568
  const i = await this._getAccessTokenRaw();
575
569
  if (!i)
576
- throw u("auth_error", "Not authenticated. Please log in first.");
570
+ throw T("auth_error", "Not authenticated. Please log in first.");
577
571
  a = {
578
572
  ...a,
579
573
  Authorization: `Bearer ${i}`
@@ -607,16 +601,13 @@ class J {
607
601
  // OpenAI-Compatible Namespaced API
608
602
  // ============================================================================
609
603
  get chat() {
610
- return this._chat ??= new B(this);
604
+ return this._chat ??= new $(this);
611
605
  }
612
606
  get models() {
613
- return this._models ??= new $(this);
607
+ return this._models ??= new V(this);
614
608
  }
615
609
  get embeddings() {
616
- return this._embeddings ??= new V(this);
617
- }
618
- get toolsets() {
619
- return this._toolsets ??= new z(this);
610
+ return this._embeddings ??= new z(this);
620
611
  }
621
612
  get mcps() {
622
613
  return this._mcps ??= new H(this);
@@ -650,7 +641,7 @@ class J {
650
641
  );
651
642
  }
652
643
  async performOAuthPkce(e) {
653
- const t = _(), s = await P(t), r = _();
644
+ const t = _(), s = await O(t), r = _();
654
645
  localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, r);
655
646
  const o = e.split(" ").filter(Boolean), a = new URLSearchParams({
656
647
  response_type: "code",
@@ -665,11 +656,10 @@ class J {
665
656
  });
666
657
  }
667
658
  async handleAccessRequestCallback(e) {
668
- const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = E(t);
669
- if (s !== "approved")
670
- throw u("auth_error", `Access request is not approved: ${s}`);
659
+ const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = w(t);
660
+ localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), s !== "approved" && E(s);
671
661
  const o = `openid profile email roles ${r ?? ""}`.trim();
672
- return localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), this.performOAuthPkce(o);
662
+ return this.performOAuthPkce(o);
673
663
  }
674
664
  /**
675
665
  * Serialize web extension client state (all transient, nothing to persist)
@@ -690,15 +680,14 @@ class J {
690
680
  bodhiextAvailable: this.bodhiext !== null,
691
681
  authClientId: this.authClientId,
692
682
  authServerUrl: this.config.authServerUrl,
693
- redirectUri: this.config.redirectUri,
694
- userRole: this.config.userRole
683
+ redirectUri: this.config.redirectUri
695
684
  };
696
685
  }
697
686
  }
698
- function Z(d) {
687
+ function Z(g) {
699
688
  if (typeof window > "u")
700
689
  throw new Error("redirectUri required in non-browser environment");
701
- const e = d === "/" ? "" : d.replace(/\/$/, "");
690
+ const e = g === "/" ? "" : g.replace(/\/$/, "");
702
691
  return `${window.location.origin}${e}/callback`;
703
692
  }
704
693
  class te extends X {
@@ -707,7 +696,6 @@ class te extends X {
707
696
  basePath: r.basePath || "/",
708
697
  redirectUri: r.redirectUri || Z(r.basePath || "/"),
709
698
  authServerUrl: r.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
710
- userRole: r.userRole || "scope_user_user",
711
699
  logLevel: r.logLevel || "warn",
712
700
  apiTimeoutMs: r.apiTimeoutMs,
713
701
  initParams: r.initParams
@@ -715,10 +703,10 @@ class te extends X {
715
703
  super(e, o, s);
716
704
  }
717
705
  createLogger(e) {
718
- return new O("WebUIClient", e.logLevel);
706
+ return new U("WebUIClient", e.logLevel);
719
707
  }
720
708
  createStoragePrefix(e) {
721
- return T(e.basePath, I.WEB);
709
+ return I(e.basePath, A.WEB);
722
710
  }
723
711
  createExtClient(e, t) {
724
712
  return new J(
@@ -726,7 +714,6 @@ class te extends X {
726
714
  {
727
715
  authServerUrl: e.authServerUrl,
728
716
  redirectUri: e.redirectUri,
729
- userRole: e.userRole,
730
717
  basePath: e.basePath,
731
718
  logLevel: e.logLevel,
732
719
  apiTimeoutMs: e.apiTimeoutMs,
@@ -741,7 +728,6 @@ class te extends X {
741
728
  authClientId: e,
742
729
  authServerUrl: t.authServerUrl,
743
730
  redirectUri: t.redirectUri,
744
- userRole: t.userRole,
745
731
  logLevel: t.logLevel,
746
732
  basePath: t.basePath,
747
733
  apiTimeoutMs: t.apiTimeoutMs
@@ -1,12 +1,10 @@
1
1
  import { DirectClientBase, AuthState, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
2
- import { UserScope } from '@bodhiapp/ts-client';
3
2
  /**
4
3
  * Configuration for DirectWebClient
5
4
  */
6
5
  export interface DirectWebClientConfig {
7
6
  authClientId: string;
8
7
  authServerUrl: string;
9
- userRole: UserScope;
10
8
  basePath: string;
11
9
  logLevel: LogLevel;
12
10
  redirectUri: string;
@@ -1,5 +1,5 @@
1
- import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, PingResponse, UserScope } from '@bodhiapp/ts-client';
2
- import { Chat, Models, Embeddings, Toolsets, Mcps, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
1
+ import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, PingResponse } from '@bodhiapp/ts-client';
2
+ import { Chat, Models, Embeddings, Mcps, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
3
3
  import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
4
4
  export type SerializedWebExtensionState = {
5
5
  extensionId?: string;
@@ -11,7 +11,6 @@ export type SerializedWebExtensionState = {
11
11
  export interface WindowBodhiextClientConfig {
12
12
  authServerUrl: string;
13
13
  redirectUri: string;
14
- userRole: UserScope;
15
14
  basePath: string;
16
15
  logLevel: LogLevel;
17
16
  apiTimeoutMs?: number;
@@ -45,7 +44,6 @@ export declare class WindowBodhiextClient implements IExtensionClient {
45
44
  private _chat;
46
45
  private _models;
47
46
  private _embeddings;
48
- private _toolsets;
49
47
  private _mcps;
50
48
  constructor(authClientId: string, config: WindowBodhiextClientConfig, onStateChange?: StateChangeCallback);
51
49
  /**
@@ -149,7 +147,6 @@ export declare class WindowBodhiextClient implements IExtensionClient {
149
147
  get chat(): Chat;
150
148
  get models(): Models;
151
149
  get embeddings(): Embeddings;
152
- get toolsets(): Toolsets;
153
150
  get mcps(): Mcps;
154
151
  requestAccess(body: CreateAccessRequest): Promise<ApiResponse<CreateAccessRequestResponse>>;
155
152
  getAccessRequestStatus(requestId: string): Promise<ApiResponse<AccessRequestStatusResponse>>;
@@ -1,4 +1,4 @@
1
- import { BaseFacadeClient, Logger, AuthState, IWebUIClient, LogLevel, StateChange, StateChangeCallback, UserScope } from '@bodhiapp/bodhi-js-core';
1
+ import { BaseFacadeClient, Logger, AuthState, IWebUIClient, LogLevel, StateChange, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
2
2
  import { DirectWebClient } from './direct-client';
3
3
  import { WindowBodhiextClient } from './ext-client';
4
4
  /**
@@ -8,7 +8,6 @@ import { WindowBodhiextClient } from './ext-client';
8
8
  export interface WebClientConfig {
9
9
  authServerUrl: string;
10
10
  redirectUri: string;
11
- userRole: UserScope;
12
11
  basePath: string;
13
12
  logLevel: LogLevel;
14
13
  apiTimeoutMs?: number;
@@ -26,7 +25,6 @@ export interface WebClientConfig {
26
25
  export interface WebUIClientParams {
27
26
  redirectUri?: string;
28
27
  authServerUrl?: string;
29
- userRole?: UserScope;
30
28
  basePath?: string;
31
29
  logLevel?: LogLevel;
32
30
  apiTimeoutMs?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "Web SDK for Bodhi Browser - window.bodhiext communication",
5
5
  "type": "module",
6
6
  "main": "dist/bodhi-web.cjs.js",
@@ -37,9 +37,9 @@
37
37
  "typecheck": "tsc --noEmit"
38
38
  },
39
39
  "dependencies": {
40
- "@bodhiapp/bodhi-browser-types": "0.0.28",
41
- "@bodhiapp/bodhi-js-core": "0.0.28",
42
- "@bodhiapp/ts-client": "0.1.23"
40
+ "@bodhiapp/bodhi-browser-types": "0.0.30",
41
+ "@bodhiapp/bodhi-js-core": "0.0.30",
42
+ "@bodhiapp/ts-client": "0.1.24"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@eslint/js": "^9.23.0",