@bodhiapp/bodhi-js 0.0.26 → 0.0.28

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 _ 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(),c=await this.requestAccess(n);if(r.isApiResultOperationError(c))throw r.createOperationError(c.error.message,c.error.type);if(!r.isApiResultSuccess(c))throw r.createOperationError(`Access request failed: HTTP ${c.status}`,"auth_error");const{id:l,review_url:a}=c.body;e?.onProgress?.("reviewing");let h;if(o==="popup"){const u=async()=>{const p=await this.getAccessRequestStatus(l);if(!r.isApiResultSuccess(p))return null;const{status:E,access_request_scope:S}=p.body;return E==="approved"?{approved:!0,accessRequestScope:S??void 0}:["denied","failed","expired"].includes(E)?{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("Access request was denied or expired","auth_error");h=g.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,l),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);if(!r.isApiResultSuccess(t))throw r.createOperationError("Failed to get access request status","auth_error");const{status:s,access_request_scope:o}=t.body;if(s!=="approved")throw r.createOperationError(`Access request is not approved: ${s}`,"auth_error");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 f=500,m=5e3,R=3e4;class w{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??R}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("Client not initialized","extension_error")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,s,o,i){try{this.ensureBodhiext()}catch(n){return{error:{message:n instanceof Error?n.message:String(n),type:"extension_error"}}}try{const n=new Promise((l,a)=>setTimeout(()=>a(new Error(`[bodhi-js-sdk/web] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),c=(async()=>{let l=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)return{error:{message:"Not authenticated. Please log in first.",type:"extension_error"}};l={...l,Authorization:`Bearer ${a}`}}return this.bodhiext.sendApiRequest(e,t,s,l)})();return await Promise.race([c,n])}catch(n){const c=n?.error,l=c?.message??(n instanceof Error?n.message:String(n)),a=c?.type||"network_error";return{error:{message:l,type:a}}}}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??m,i=e.intervalMs??this.config.initParams?.extension?.intervalMs??f,n=Date.now();if(!await new Promise(l=>{const a=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,l(!0);return}if(Date.now()-n>=o){l(!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(),c=await this.requestAccess(n);if(r.isApiResultOperationError(c))throw r.createOperationError(c.error.message,c.error.type);if(!r.isApiResultSuccess(c))throw r.createOperationError(`Access request failed: HTTP ${c.status}`,"auth_error");const{id:l,review_url:a}=c.body;e?.onProgress?.("reviewing");let h;if(o==="popup"){const u=async()=>{const p=await this.getAccessRequestStatus(l);if(!r.isApiResultSuccess(p))return null;const{status:E,access_request_scope:S}=p.body;return E==="approved"?{approved:!0,accessRequestScope:S??void 0}:["denied","failed","expired"].includes(E)?{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("Access request was denied or expired","auth_error");h=g.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,l),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("Access token expired and unable to refresh. Try logging out and logging in again.","token_refresh_failed")}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(){const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(r.isApiResultOperationError(e)||!r.isApiResultSuccess(e))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"tenant_selection":return{status:"tenant_selection",version:s,error:null,deployment:t.deployment??null,client_id:t.client_id??null};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}}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("Not authenticated. Please log in first.","auth_error");n={...n,Authorization:`Bearer ${a}`}}const l=this.bodhiext.sendStreamRequest(e,t,s,n).getReader();try{for(;;){const{value:a,done:h}=await l.read();if(h||a?.done)break;yield a.body}}catch(a){if(a instanceof Error){if("response"in a){const h=a;throw r.createApiError(a.message,h.response.status,h.response.body)}throw"error"in a,r.createOperationError(a.message,"extension_error")}throw a}finally{l.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);if(!r.isApiResultSuccess(t))throw r.createOperationError("Failed to get access request status","auth_error");const{status:s,access_request_scope:o}=t.body;if(s!=="approved")throw r.createOperationError(`Access request is not approved: ${s}`,"auth_error");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 y(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 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",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 w(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 _({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=T;
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,9 +1,9 @@
1
- import { DirectClientBase as O, createStoragePrefixWithBasePath as R, STORAGE_PREFIXES as T, AccessRequestBuilder as C, isApiResultOperationError as y, createOperationError as l, isApiResultSuccess as u, openPopupReview as v, DEFAULT_POLL_TIMEOUT_MS as k, DEFAULT_POLL_INTERVAL_MS as x, generateCodeVerifier as _, generateCodeChallenge as b, EXTENSION_STATE_NOT_INITIALIZED as I, Logger as P, createOAuthEndpoints as q, NOOP_STATE_CALLBACK as U, createStorageKeys as K, EXTENSION_STATE_NOT_FOUND as L, PENDING_EXTENSION_READY as N, BACKEND_SERVER_NOT_REACHABLE as m, extractUserInfo as A, refreshAccessToken as D, backendServerNotReady as w, SERVER_ERROR_CODES as M, createApiError as F, Chat as B, Models as $, Embeddings as V, Toolsets as H, Mcps as z, pollAccessRequestUntilResolved as W, BaseFacadeClient as X } from "@bodhiapp/bodhi-js-core";
2
- class j extends O {
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 {
3
3
  constructor(e, t) {
4
- const s = R(
4
+ const s = T(
5
5
  e.basePath,
6
- T.WEB_DIRECT
6
+ I.WEB_DIRECT
7
7
  ), r = {
8
8
  authClientId: e.authClientId,
9
9
  authServerUrl: e.authServerUrl,
@@ -24,42 +24,34 @@ class j extends O {
24
24
  return t;
25
25
  const s = e?.userRole ?? this.userRole, r = e?.flowType ?? "popup";
26
26
  e?.onProgress?.("requesting");
27
- const o = new C(this.authClientId).requestedRole(s).flowType(r);
27
+ const o = new v(this.authClientId).requestedRole(s).flowType(r);
28
28
  if (e?.requested && o.requested(e.requested), r === "redirect") {
29
29
  const g = e?.redirectUrl ?? this.redirectUri;
30
30
  o.redirectUrl(g);
31
31
  }
32
- const a = o.build(), n = await this.requestAccess(a);
33
- if (y(n))
34
- throw l(n.error.message, n.error.type);
35
- if (!u(n))
36
- throw l(
37
- `Access request failed: HTTP ${n.status}`,
38
- "auth_error"
39
- );
40
- const { id: c, review_url: i } = n.body;
32
+ const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = E(c);
41
33
  e?.onProgress?.("reviewing");
42
- let h;
34
+ let l;
43
35
  if (r === "popup") {
44
- const p = await v(i, async () => {
45
- const S = await this.getAccessRequestStatus(c);
46
- if (!u(S)) return null;
47
- const { status: f, access_request_scope: E } = S.body;
48
- return f === "approved" ? { approved: !0, accessRequestScope: E ?? void 0 } : ["denied", "failed", "expired"].includes(f) ? { approved: !1 } : null;
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;
49
41
  }, {
50
- intervalMs: e?.pollIntervalMs ?? x,
51
- timeoutMs: e?.pollTimeoutMs ?? k
42
+ intervalMs: e?.pollIntervalMs ?? b,
43
+ timeoutMs: e?.pollTimeoutMs ?? x
52
44
  });
53
- if (!p.approved)
54
- throw l("Access request was denied or expired", "auth_error");
55
- h = p.accessRequestScope;
45
+ if (!f.approved)
46
+ throw u("auth_error", "Access request was denied or expired");
47
+ l = f.accessRequestScope;
56
48
  } else
57
- return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, c), window.location.href = i, new Promise(() => {
49
+ return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, n), window.location.href = i, new Promise(() => {
58
50
  });
59
- return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${h ?? ""}`.trim());
51
+ return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${l ?? ""}`.trim());
60
52
  }
61
53
  async performOAuthPkce(e) {
62
- const t = _(), s = await b(t), r = _();
54
+ const t = _(), s = await P(t), r = _();
63
55
  localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, r);
64
56
  const o = new URL(this.authEndpoints.authorize);
65
57
  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");
@@ -75,12 +67,9 @@ class j extends O {
75
67
  return this.setAuthState(r), r;
76
68
  }
77
69
  async handleAccessRequestCallback(e) {
78
- const t = await this.getAccessRequestStatus(e);
79
- if (!u(t))
80
- throw l("Failed to get access request status", "auth_error");
81
- const { status: s, access_request_scope: r } = t.body;
70
+ const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = E(t);
82
71
  if (s !== "approved")
83
- throw l(`Access request is not approved: ${s}`, "auth_error");
72
+ throw u("auth_error", `Access request is not approved: ${s}`);
84
73
  const o = `openid profile email roles ${r ?? ""}`.trim();
85
74
  return localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), this.performOAuthPkce(o);
86
75
  }
@@ -160,12 +149,12 @@ class j extends O {
160
149
  return this.redirectUri;
161
150
  }
162
151
  }
163
- const G = 500, Q = 5e3, Y = 3e4;
152
+ const j = 500, Q = 5e3, Y = 3e4;
164
153
  class J {
165
154
  constructor(e, t, s) {
166
- this.state = I, this.bodhiext = null, this.refreshPromise = null, this.logger = new P("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = q(this.config.authServerUrl), this.onStateChange = s ?? U;
167
- const r = R(t.basePath, T.WEB_EXT);
168
- this.storageKeys = K(r), this.apiTimeoutMs = t.apiTimeoutMs ?? Y;
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;
169
158
  }
170
159
  /**
171
160
  * Set client state and notify callback
@@ -190,11 +179,11 @@ class J {
190
179
  // ============================================================================
191
180
  /**
192
181
  * Ensure bodhiext is available, attempting to acquire it if not already set
193
- * @throws OperationError if client not initialized
182
+ * @throws BodhiError if client not initialized
194
183
  */
195
184
  ensureBodhiext() {
196
185
  if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
197
- throw l("Client not initialized", "extension_error");
186
+ throw u("not_initialized", "Client not initialized");
198
187
  }
199
188
  /**
200
189
  * Send extension request via window.bodhiext.sendExtRequest
@@ -204,56 +193,47 @@ class J {
204
193
  }
205
194
  /**
206
195
  * Send API message via window.bodhiext.sendApiRequest
207
- * Converts ApiResponse to ApiResponseResult
196
+ * @throws BodhiError on operational errors (extension not ready, auth, network, timeout)
208
197
  */
209
198
  async sendApiRequest(e, t, s, r, o) {
210
- try {
211
- this.ensureBodhiext();
212
- } catch (a) {
213
- return {
214
- error: {
215
- message: a instanceof Error ? a.message : String(a),
216
- type: "extension_error"
217
- }
218
- };
219
- }
199
+ this.ensureBodhiext();
220
200
  try {
221
201
  const a = new Promise(
222
- (c, i) => setTimeout(
202
+ (n, i) => setTimeout(
223
203
  () => i(
224
- new Error(
204
+ new h(
205
+ "timeout_error",
225
206
  `[bodhi-js-sdk/web] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`
226
207
  )
227
208
  ),
228
209
  this.apiTimeoutMs
229
210
  )
230
- ), n = (async () => {
231
- let c = r || {};
211
+ ), c = (async () => {
212
+ let n = r || {};
232
213
  if (o) {
233
214
  const i = await this._getAccessTokenRaw();
234
215
  if (!i)
235
- return {
236
- error: {
237
- message: "Not authenticated. Please log in first.",
238
- type: "extension_error"
239
- }
240
- };
241
- c = {
242
- ...c,
216
+ throw new h("auth_error", "Not authenticated. Please log in first.");
217
+ n = {
218
+ ...n,
243
219
  Authorization: `Bearer ${i}`
244
220
  };
245
221
  }
246
- return this.bodhiext.sendApiRequest(e, t, s, c);
222
+ return this.bodhiext.sendApiRequest(e, t, s, n);
247
223
  })();
248
- return await Promise.race([n, a]);
224
+ return await Promise.race([c, a]);
249
225
  } catch (a) {
250
- const n = a?.error, c = n?.message ?? (a instanceof Error ? a.message : String(a)), i = n?.type || "network_error";
251
- return {
252
- error: {
253
- message: c,
254
- type: i
255
- }
256
- };
226
+ if (a instanceof m || a instanceof h) throw a;
227
+ if (a instanceof Error) {
228
+ const c = a;
229
+ throw a.name === "BodhiApiError" && typeof c.status == "number" && c.body != null ? new m(
230
+ c.status,
231
+ c.body,
232
+ a.message,
233
+ c.headers
234
+ ) : a.name === "BodhiError" && typeof c.code == "string" ? new h(c.code, a.message) : new h("network_error", a.message);
235
+ }
236
+ throw new h("network_error", String(a));
257
237
  }
258
238
  }
259
239
  /**
@@ -277,26 +257,26 @@ class J {
277
257
  */
278
258
  async init(e = {}) {
279
259
  if (!e.testConnection && !e.selectedConnection)
280
- return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), I;
260
+ return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), A;
281
261
  if (this.bodhiext && !e.testConnection)
282
262
  return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
283
263
  if (!this.bodhiext) {
284
- const r = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? Q, o = e.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? G, a = Date.now();
285
- if (!await new Promise((c) => {
264
+ const r = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? Q, o = e.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? j, a = Date.now();
265
+ if (!await new Promise((n) => {
286
266
  const i = () => {
287
267
  if (window.bodhiext) {
288
- this.bodhiext = window.bodhiext, c(!0);
268
+ this.bodhiext = window.bodhiext, n(!0);
289
269
  return;
290
270
  }
291
271
  if (Date.now() - a >= r) {
292
- c(!1);
272
+ n(!1);
293
273
  return;
294
274
  }
295
275
  setTimeout(i, o);
296
276
  };
297
277
  i();
298
278
  }))
299
- return this.logger.warn("Extension discovery timed out"), this.setState(L), this.state;
279
+ return this.logger.warn("Extension discovery timed out"), this.setState(N), this.state;
300
280
  }
301
281
  const t = await this.bodhiext.getExtensionId();
302
282
  this.logger.info(`Extension discovered: ${t}`);
@@ -304,14 +284,14 @@ class J {
304
284
  type: "extension",
305
285
  extension: "ready",
306
286
  extensionId: t,
307
- server: N
287
+ server: D
308
288
  };
309
289
  if (e.testConnection)
310
290
  try {
311
291
  const r = await this.getServerState();
312
292
  this.setState({ ...s, server: r }), this.logger.info(`Server connectivity tested: ${r.status}`);
313
293
  } catch (r) {
314
- this.logger.error("Failed to get server state:", r), this.setState({ ...s, server: m });
294
+ this.logger.error("Failed to get server state:", r), this.setState({ ...s, server: w });
315
295
  }
316
296
  else
317
297
  this.setState(s);
@@ -332,39 +312,31 @@ class J {
332
312
  this.ensureBodhiext();
333
313
  const s = e?.userRole ?? this.config.userRole, r = e?.flowType ?? "popup";
334
314
  e?.onProgress?.("requesting");
335
- const o = new C(this.authClientId).requestedRole(s).flowType(r);
315
+ const o = new v(this.authClientId).requestedRole(s).flowType(r);
336
316
  if (e?.requested && o.requested(e.requested), r === "redirect") {
337
317
  const g = e?.redirectUrl ?? this.config.redirectUri;
338
318
  o.redirectUrl(g);
339
319
  }
340
- const a = o.build(), n = await this.requestAccess(a);
341
- if (y(n))
342
- throw l(n.error.message, n.error.type);
343
- if (!u(n))
344
- throw l(
345
- `Access request failed: HTTP ${n.status}`,
346
- "auth_error"
347
- );
348
- const { id: c, review_url: i } = n.body;
320
+ const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = E(c);
349
321
  e?.onProgress?.("reviewing");
350
- let h;
322
+ let l;
351
323
  if (r === "popup") {
352
- const p = await v(i, async () => {
353
- const S = await this.getAccessRequestStatus(c);
354
- if (!u(S)) return null;
355
- const { status: f, access_request_scope: E } = S.body;
356
- return f === "approved" ? { approved: !0, accessRequestScope: E ?? void 0 } : ["denied", "failed", "expired"].includes(f) ? { approved: !1 } : null;
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;
357
329
  }, {
358
- intervalMs: e?.pollIntervalMs ?? x,
359
- timeoutMs: e?.pollTimeoutMs ?? k
330
+ intervalMs: e?.pollIntervalMs ?? b,
331
+ timeoutMs: e?.pollTimeoutMs ?? x
360
332
  });
361
- if (!p.approved)
362
- throw l("Access request was denied or expired", "auth_error");
363
- h = p.accessRequestScope;
333
+ if (!f.approved)
334
+ throw u("auth_error", "Access request was denied or expired");
335
+ l = f.accessRequestScope;
364
336
  } else
365
- return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, c), window.location.href = i, new Promise(() => {
337
+ return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, n), window.location.href = i, new Promise(() => {
366
338
  });
367
- return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${h ?? ""}`.trim());
339
+ return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${l ?? ""}`.trim());
368
340
  }
369
341
  /**
370
342
  * Handle OAuth callback with authorization code
@@ -453,7 +425,7 @@ class J {
453
425
  if (!e)
454
426
  return { status: "unauthenticated", user: null, accessToken: null, error: null };
455
427
  try {
456
- return { status: "authenticated", user: A(e), accessToken: e, error: null };
428
+ return { status: "authenticated", user: C(e), accessToken: e, error: null };
457
429
  } catch (t) {
458
430
  return this.logger.error("Failed to parse token:", t), { status: "unauthenticated", user: null, accessToken: null, error: null };
459
431
  }
@@ -495,14 +467,14 @@ class J {
495
467
  async _doRefreshToken(e) {
496
468
  this.logger.debug("Refreshing access token");
497
469
  try {
498
- const t = await D(
470
+ const t = await M(
499
471
  this.authEndpoints.token,
500
472
  e,
501
473
  this.authClientId
502
474
  );
503
475
  if (t.success) {
504
476
  this._storeRefreshedTokens(t.tokens);
505
- const s = A(t.tokens.access_token);
477
+ const s = C(t.tokens.access_token);
506
478
  return this.setAuthState({
507
479
  status: "authenticated",
508
480
  user: s,
@@ -520,9 +492,9 @@ class J {
520
492
  } catch (t) {
521
493
  this.logger.warn("Token refresh failed:", t);
522
494
  }
523
- throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), l(
524
- "Access token expired and unable to refresh. Try logging out and logging in again.",
525
- "token_refresh_failed"
495
+ throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), u(
496
+ "auth_error",
497
+ "Access token expired and unable to refresh. Try logging out and logging in again."
526
498
  );
527
499
  }
528
500
  clearAuthStorage() {
@@ -546,49 +518,49 @@ class J {
546
518
  * Calls /bodhi/v1/info and returns structured server state
547
519
  */
548
520
  async getServerState() {
549
- const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
550
- if (y(e))
551
- return m;
552
- if (!u(e))
553
- return m;
554
- const t = e.body, s = t.version || "unknown";
555
- switch (t.status) {
556
- case "ready":
557
- return {
558
- status: "ready",
559
- version: s,
560
- error: null,
561
- deployment: t.deployment ?? null,
562
- client_id: t.client_id ?? null
563
- };
564
- case "setup":
565
- return w("setup", s, void 0, t.deployment, t.client_id);
566
- case "resource_admin":
567
- return w(
568
- "resource_admin",
569
- s,
570
- void 0,
571
- t.deployment,
572
- t.client_id
573
- );
574
- case "tenant_selection":
575
- return {
576
- status: "tenant_selection",
577
- version: s,
578
- error: null,
579
- deployment: t.deployment ?? null,
580
- client_id: t.client_id ?? null
581
- };
582
- case "error":
583
- return w(
584
- "error",
585
- s,
586
- t.error ? { message: t.error.message, type: t.error.type } : M.SERVER_NOT_READY,
587
- t.deployment,
588
- t.client_id
589
- );
590
- default:
591
- return m;
521
+ try {
522
+ const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
523
+ if (e.status >= 400)
524
+ return w;
525
+ const t = e.body, s = t.version || "unknown";
526
+ switch (t.status) {
527
+ case "ready":
528
+ return {
529
+ status: "ready",
530
+ version: s,
531
+ error: null,
532
+ deployment: t.deployment ?? null,
533
+ client_id: t.client_id ?? null
534
+ };
535
+ case "setup":
536
+ return R(
537
+ "setup",
538
+ s,
539
+ void 0,
540
+ t.deployment,
541
+ t.client_id
542
+ );
543
+ case "resource_admin":
544
+ return R(
545
+ "resource_admin",
546
+ s,
547
+ void 0,
548
+ t.deployment,
549
+ t.client_id
550
+ );
551
+ case "error":
552
+ return R(
553
+ "error",
554
+ s,
555
+ t.error ? { message: t.error.message, type: t.error.type } : F.SERVER_NOT_READY,
556
+ t.deployment,
557
+ t.client_id
558
+ );
559
+ default:
560
+ return w;
561
+ }
562
+ } catch {
563
+ return w;
592
564
  }
593
565
  }
594
566
  /**
@@ -601,31 +573,34 @@ class J {
601
573
  if (o) {
602
574
  const i = await this._getAccessTokenRaw();
603
575
  if (!i)
604
- throw l("Not authenticated. Please log in first.", "auth_error");
576
+ throw u("auth_error", "Not authenticated. Please log in first.");
605
577
  a = {
606
578
  ...a,
607
579
  Authorization: `Bearer ${i}`
608
580
  };
609
581
  }
610
- const c = this.bodhiext.sendStreamRequest(e, t, s, a).getReader();
582
+ const n = this.bodhiext.sendStreamRequest(e, t, s, a).getReader();
611
583
  try {
612
584
  for (; ; ) {
613
- const { value: i, done: h } = await c.read();
614
- if (h || i?.done)
585
+ const { value: i, done: l } = await n.read();
586
+ if (l || i?.done)
615
587
  break;
616
588
  yield i.body;
617
589
  }
618
590
  } catch (i) {
591
+ if (i instanceof m || i instanceof h) throw i;
619
592
  if (i instanceof Error) {
620
- if ("response" in i) {
621
- const h = i;
622
- throw F(i.message, h.response.status, h.response.body);
623
- }
624
- throw "error" in i ? l(i.message, "extension_error") : l(i.message, "extension_error");
593
+ const l = i;
594
+ throw i.name === "BodhiApiError" && typeof l.status == "number" && l.body != null ? new m(
595
+ l.status,
596
+ l.body,
597
+ i.message,
598
+ l.headers
599
+ ) : i.name === "BodhiError" && typeof l.code == "string" ? new h(l.code, i.message) : new h("extension_error", i.message);
625
600
  }
626
601
  throw i;
627
602
  } finally {
628
- c.releaseLock();
603
+ n.releaseLock();
629
604
  }
630
605
  }
631
606
  // ============================================================================
@@ -641,10 +616,10 @@ class J {
641
616
  return this._embeddings ??= new V(this);
642
617
  }
643
618
  get toolsets() {
644
- return this._toolsets ??= new H(this);
619
+ return this._toolsets ??= new z(this);
645
620
  }
646
621
  get mcps() {
647
- return this._mcps ??= new z(this);
622
+ return this._mcps ??= new H(this);
648
623
  }
649
624
  // ============================================================================
650
625
  // Access Request Methods
@@ -675,7 +650,7 @@ class J {
675
650
  );
676
651
  }
677
652
  async performOAuthPkce(e) {
678
- const t = _(), s = await b(t), r = _();
653
+ const t = _(), s = await P(t), r = _();
679
654
  localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, r);
680
655
  const o = e.split(" ").filter(Boolean), a = new URLSearchParams({
681
656
  response_type: "code",
@@ -690,12 +665,9 @@ class J {
690
665
  });
691
666
  }
692
667
  async handleAccessRequestCallback(e) {
693
- const t = await this.getAccessRequestStatus(e);
694
- if (!u(t))
695
- throw l("Failed to get access request status", "auth_error");
696
- const { status: s, access_request_scope: r } = t.body;
668
+ const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = E(t);
697
669
  if (s !== "approved")
698
- throw l(`Access request is not approved: ${s}`, "auth_error");
670
+ throw u("auth_error", `Access request is not approved: ${s}`);
699
671
  const o = `openid profile email roles ${r ?? ""}`.trim();
700
672
  return localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), this.performOAuthPkce(o);
701
673
  }
@@ -743,10 +715,10 @@ class te extends X {
743
715
  super(e, o, s);
744
716
  }
745
717
  createLogger(e) {
746
- return new P("WebUIClient", e.logLevel);
718
+ return new O("WebUIClient", e.logLevel);
747
719
  }
748
720
  createStoragePrefix(e) {
749
- return R(e.basePath, T.WEB);
721
+ return T(e.basePath, I.WEB);
750
722
  }
751
723
  createExtClient(e, t) {
752
724
  return new J(
@@ -764,7 +736,7 @@ class te extends X {
764
736
  );
765
737
  }
766
738
  createDirectClient(e, t, s) {
767
- return new j(
739
+ return new G(
768
740
  {
769
741
  authClientId: e,
770
742
  authServerUrl: t.authServerUrl,
@@ -1,5 +1,6 @@
1
- import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, UserScope } from '@bodhiapp/ts-client';
2
- import { Chat, Models, Embeddings, Toolsets, Mcps, ApiResponseResult, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
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';
3
+ import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
3
4
  export type SerializedWebExtensionState = {
4
5
  extensionId?: string;
5
6
  };
@@ -61,7 +62,7 @@ export declare class WindowBodhiextClient implements IExtensionClient {
61
62
  setStateCallback(callback: StateChangeCallback): void;
62
63
  /**
63
64
  * Ensure bodhiext is available, attempting to acquire it if not already set
64
- * @throws OperationError if client not initialized
65
+ * @throws BodhiError if client not initialized
65
66
  */
66
67
  private ensureBodhiext;
67
68
  /**
@@ -70,9 +71,9 @@ export declare class WindowBodhiextClient implements IExtensionClient {
70
71
  sendExtRequest<TParams = void, TRes = unknown>(action: string, params?: TParams): Promise<TRes>;
71
72
  /**
72
73
  * Send API message via window.bodhiext.sendApiRequest
73
- * Converts ApiResponse to ApiResponseResult
74
+ * @throws BodhiError on operational errors (extension not ready, auth, network, timeout)
74
75
  */
75
- sendApiRequest<TReq = void, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): Promise<ApiResponseResult<TRes>>;
76
+ sendApiRequest<TReq = void, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): Promise<ApiResponse<TRes>>;
76
77
  /**
77
78
  * Get current client state
78
79
  */
@@ -134,9 +135,7 @@ export declare class WindowBodhiextClient implements IExtensionClient {
134
135
  /**
135
136
  * Ping API
136
137
  */
137
- pingApi(): Promise<ApiResponseResult<{
138
- message: string;
139
- }>>;
138
+ pingApi(): Promise<ApiResponse<PingResponse>>;
140
139
  /**
141
140
  * Get backend server state
142
141
  * Calls /bodhi/v1/info and returns structured server state
@@ -152,8 +151,8 @@ export declare class WindowBodhiextClient implements IExtensionClient {
152
151
  get embeddings(): Embeddings;
153
152
  get toolsets(): Toolsets;
154
153
  get mcps(): Mcps;
155
- requestAccess(body: CreateAccessRequest): Promise<ApiResponseResult<CreateAccessRequestResponse>>;
156
- getAccessRequestStatus(requestId: string): Promise<ApiResponseResult<AccessRequestStatusResponse>>;
154
+ requestAccess(body: CreateAccessRequest): Promise<ApiResponse<CreateAccessRequestResponse>>;
155
+ getAccessRequestStatus(requestId: string): Promise<ApiResponse<AccessRequestStatusResponse>>;
157
156
  pollAccessRequestStatus(requestId: string, options?: {
158
157
  intervalMs?: number;
159
158
  timeoutMs?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
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.26",
41
- "@bodhiapp/bodhi-js-core": "0.0.26",
42
- "@bodhiapp/ts-client": "0.1.20"
40
+ "@bodhiapp/bodhi-browser-types": "0.0.28",
41
+ "@bodhiapp/bodhi-js-core": "0.0.28",
42
+ "@bodhiapp/ts-client": "0.1.23"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@eslint/js": "^9.23.0",