@bodhiapp/bodhi-js 0.0.27 → 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.
- package/dist/bodhi-web.cjs.js +1 -1
- package/dist/bodhi-web.esm.js +144 -172
- package/dist/ext-client.d.ts +9 -10
- package/package.json +4 -4
package/dist/bodhi-web.cjs.js
CHANGED
|
@@ -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;
|
package/dist/bodhi-web.esm.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { DirectClientBase as
|
|
2
|
-
class
|
|
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 =
|
|
4
|
+
const s = T(
|
|
5
5
|
e.basePath,
|
|
6
|
-
|
|
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
|
|
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(),
|
|
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
|
|
34
|
+
let l;
|
|
43
35
|
if (r === "popup") {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
const { status:
|
|
48
|
-
return
|
|
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 ??
|
|
51
|
-
timeoutMs: e?.pollTimeoutMs ??
|
|
42
|
+
intervalMs: e?.pollIntervalMs ?? b,
|
|
43
|
+
timeoutMs: e?.pollTimeoutMs ?? x
|
|
52
44
|
});
|
|
53
|
-
if (!
|
|
54
|
-
throw
|
|
55
|
-
|
|
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,
|
|
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 ${
|
|
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
|
|
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
|
|
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
|
|
152
|
+
const j = 500, Q = 5e3, Y = 3e4;
|
|
164
153
|
class J {
|
|
165
154
|
constructor(e, t, s) {
|
|
166
|
-
this.state =
|
|
167
|
-
const r =
|
|
168
|
-
this.storageKeys =
|
|
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
|
|
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
|
|
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
|
-
*
|
|
196
|
+
* @throws BodhiError on operational errors (extension not ready, auth, network, timeout)
|
|
208
197
|
*/
|
|
209
198
|
async sendApiRequest(e, t, s, r, o) {
|
|
210
|
-
|
|
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
|
-
(
|
|
202
|
+
(n, i) => setTimeout(
|
|
223
203
|
() => i(
|
|
224
|
-
new
|
|
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
|
-
),
|
|
231
|
-
let
|
|
211
|
+
), c = (async () => {
|
|
212
|
+
let n = r || {};
|
|
232
213
|
if (o) {
|
|
233
214
|
const i = await this._getAccessTokenRaw();
|
|
234
215
|
if (!i)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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,
|
|
222
|
+
return this.bodhiext.sendApiRequest(e, t, s, n);
|
|
247
223
|
})();
|
|
248
|
-
return await Promise.race([
|
|
224
|
+
return await Promise.race([c, a]);
|
|
249
225
|
} catch (a) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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"),
|
|
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 ??
|
|
285
|
-
if (!await new Promise((
|
|
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,
|
|
268
|
+
this.bodhiext = window.bodhiext, n(!0);
|
|
289
269
|
return;
|
|
290
270
|
}
|
|
291
271
|
if (Date.now() - a >= r) {
|
|
292
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
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(),
|
|
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
|
|
322
|
+
let l;
|
|
351
323
|
if (r === "popup") {
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
if (
|
|
355
|
-
const { status:
|
|
356
|
-
return
|
|
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 ??
|
|
359
|
-
timeoutMs: e?.pollTimeoutMs ??
|
|
330
|
+
intervalMs: e?.pollIntervalMs ?? b,
|
|
331
|
+
timeoutMs: e?.pollTimeoutMs ?? x
|
|
360
332
|
});
|
|
361
|
-
if (!
|
|
362
|
-
throw
|
|
363
|
-
|
|
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,
|
|
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 ${
|
|
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:
|
|
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
|
|
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 =
|
|
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"),
|
|
524
|
-
"
|
|
525
|
-
"
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
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
|
|
582
|
+
const n = this.bodhiext.sendStreamRequest(e, t, s, a).getReader();
|
|
611
583
|
try {
|
|
612
584
|
for (; ; ) {
|
|
613
|
-
const { value: i, done:
|
|
614
|
-
if (
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
-
|
|
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
|
|
619
|
+
return this._toolsets ??= new z(this);
|
|
645
620
|
}
|
|
646
621
|
get mcps() {
|
|
647
|
-
return this._mcps ??= new
|
|
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
|
|
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
|
|
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
|
|
718
|
+
return new O("WebUIClient", e.logLevel);
|
|
747
719
|
}
|
|
748
720
|
createStoragePrefix(e) {
|
|
749
|
-
return
|
|
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
|
|
739
|
+
return new G(
|
|
768
740
|
{
|
|
769
741
|
authClientId: e,
|
|
770
742
|
authServerUrl: t.authServerUrl,
|
package/dist/ext-client.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, UserScope } from '@bodhiapp/ts-client';
|
|
2
|
-
import { Chat, Models, Embeddings, Toolsets, Mcps,
|
|
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
|
|
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
|
-
*
|
|
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<
|
|
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<
|
|
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<
|
|
156
|
-
getAccessRequestStatus(requestId: string): Promise<
|
|
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.
|
|
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.
|
|
41
|
-
"@bodhiapp/bodhi-js-core": "0.0.
|
|
42
|
-
"@bodhiapp/ts-client": "0.1.
|
|
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",
|