@bodhiapp/bodhi-js-ext 0.0.29 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("@bodhiapp/bodhi-js-core"),g=require("@bodhiapp/bodhi-browser-types"),h={EXT2EXT_CLIENT_REQUEST:"EXT2EXT_CLIENT_REQUEST",EXT2EXT_CLIENT_RESPONSE:"EXT2EXT_CLIENT_RESPONSE",EXT2EXT_CLIENT_BROADCAST:"EXT2EXT_CLIENT_BROADCAST",EXT2EXT_CLIENT_API_REQUEST:"EXT2EXT_CLIENT_API_REQUEST",EXT2EXT_CLIENT_API_RESPONSE:"EXT2EXT_CLIENT_API_RESPONSE",EXT2EXT_CLIENT_STREAM_REQUEST:"EXT2EXT_CLIENT_STREAM_REQUEST",EXT2EXT_CLIENT_STREAM_CHUNK:"EXT2EXT_CLIENT_STREAM_CHUNK",EXT2EXT_CLIENT_STREAM_ERROR:"EXT2EXT_CLIENT_STREAM_ERROR",EXT2EXT_CLIENT_STREAM_API_ERROR:"EXT2EXT_CLIENT_STREAM_API_ERROR",EXT2EXT_CLIENT_STREAM_DONE:"EXT2EXT_CLIENT_STREAM_DONE"},f="ext2ext-client-stream",_={LOGIN:"login",LOGOUT:"logout",GET_AUTH_STATE:"getAuthState",DISCOVER_EXTENSION:"discoverBodhiExtension",GET_EXTENSION_ID:"get_extension_id",SET_EXTENSION_ID:"setExtensionId"},x=5e3,C=3,w=500,A=500,N=3e4;function P(S){return"error"in S}class v extends n.DirectClientBase{constructor(e,t){const r=n.createStoragePrefixWithBasePath(e.basePath,n.STORAGE_PREFIXES.EXT_DIRECT),i={authClientId:e.authClientId,authServerUrl:e.authServerUrl,userRole:e.userRole,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectExtClient",apiTimeoutMs:e.apiTimeoutMs};super(i,t)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;e?.flowType==="redirect"&&this.logger.warn("Extension mode does not support redirect flow type; using popup instead");const r=e?.userRole??this.userRole;e?.onProgress?.("requesting");const i=new n.AccessRequestBuilder(this.authClientId).requestedRole(r).flowType("popup");e?.requested&&i.requested(e.requested);const o=i.build(),s=await this.requestAccess(o),{id:a,review_url:E}=n.unwrapResponse(s);e?.onProgress?.("reviewing"),await chrome.tabs.create({url:E});const c=await this.pollAccessRequestStatus(a,{intervalMs:e?.pollIntervalMs??n.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??n.DEFAULT_POLL_TIMEOUT_MS});if(c.status!=="approved")throw n.createOperationError("auth_error",`Access request ${c.status}`);const u=c.access_request_scope;return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${u??""}`.trim())}async performOAuthPkce(e){const t=n.generateCodeVerifier(),r=await n.generateCodeChallenge(t),i=n.generateCodeVerifier();await chrome.storage.session.set({[this.storageKeys.CODE_VERIFIER]:t,[this.storageKeys.STATE]:i});const o=chrome.identity.getRedirectURL("callback"),s=new URL(this.authEndpoints.authorize);return s.searchParams.set("client_id",this.authClientId),s.searchParams.set("response_type","code"),s.searchParams.set("redirect_uri",o),s.searchParams.set("scope",e),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),s.searchParams.set("state",i),new Promise((a,E)=>{chrome.identity.launchWebAuthFlow({url:s.toString(),interactive:!0},async c=>{if(chrome.runtime.lastError){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(chrome.runtime.lastError);return}if(!c){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","No redirect URL received"));return}try{const u=new URL(c),d=u.searchParams.get("code"),T=u.searchParams.get("state"),m=await chrome.storage.session.get(this.storageKeys.STATE);if(T!==m[this.storageKeys.STATE]){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","State mismatch"));return}if(!d){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","No authorization code"));return}await this.exchangeCodeForTokens(d);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("oauth_error","Login failed");this.setAuthState(l),await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a(l)}catch(u){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(u)}})})}async logout(){const t=(await chrome.storage.session.get(this.storageKeys.REFRESH_TOKEN))[this.storageKeys.REFRESH_TOKEN];if(t)try{const i=new URLSearchParams({token:t,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:i})}catch(i){this.logger.warn("Token revocation failed:",i)}await chrome.storage.session.remove([this.storageKeys.ACCESS_TOKEN,this.storageKeys.REFRESH_TOKEN,this.storageKeys.EXPIRES_AT]);const r={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(r),r}async exchangeCodeForTokens(e){const r=(await chrome.storage.session.get(this.storageKeys.CODE_VERIFIER))[this.storageKeys.CODE_VERIFIER],i=chrome.identity.getRedirectURL("callback"),o=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:i,client_id:this.authClientId,code_verifier:r})});if(!o.ok){const E=await o.text();throw new Error(`Token exchange failed: ${o.status} ${E}`)}const s=await o.json(),a=Date.now()+(s.expires_in||3600)*1e3;await chrome.storage.session.set({[this.storageKeys.ACCESS_TOKEN]:s.access_token,[this.storageKeys.REFRESH_TOKEN]:s.refresh_token,[this.storageKeys.EXPIRES_AT]:a}),await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE])}async _storageGet(e){const r=(await chrome.storage.session.get(e))[e];return r!==void 0?String(r):null}async _storageSet(e){await chrome.storage.session.set(e)}async _storageRemove(e){await chrome.storage.session.remove(e)}_getRedirectUri(){return chrome.identity.getRedirectURL("callback")}}class X{constructor(e={},t){this.state={type:"extension",extension:"not-initialized",extensionId:null,server:n.PENDING_EXTENSION_READY},this.extensionId=null,this.broadcastListenerActive=!1,this.config=e,this.logger=new n.Logger("ExtClient",e?.logLevel||"warn"),this.onStateChange=t??n.NOOP_STATE_CALLBACK,this.apiTimeoutMs=e.apiTimeoutMs??N,this.authClientId=e.authClientId??""}setState(e){this.state=e,this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}setupBroadcastListener(){this.broadcastListenerActive||(this.broadcastListenerActive=!0,chrome.runtime.onMessage.addListener(e=>{const t=e;return t?.type===h.EXT2EXT_CLIENT_BROADCAST&&t.event==="authStateChanged"&&this.handleAuthStateChangedBroadcast(),!1}),this.logger.debug("Broadcast listener setup complete"))}async handleAuthStateChangedBroadcast(){this.logger.debug("Received authStateChanged broadcast, refreshing auth state");const e=await this.getAuthState();this.setAuthState(e)}generateRequestId(){return crypto.randomUUID()}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){this.logger.info("No testConnection or selectedConnection, returning not-initialized state");const i=n.createExtensionStateNotInitialized();return this.setState(i),i}if(this.extensionId&&!e.testConnection)return this.logger.debug("Already initialized with extensionId, skipping discovery"),this.state;const t=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??x,r=e.savedState?.extensionId;try{if(!this.extensionId){if(r)this.logger.info("Restoring with known extensionId:",r),await this.sendExtMessageWithTimeout(_.SET_EXTENSION_ID,{extensionId:r},t),this.extensionId=r;else{this.logger.info("Discovering bodhi-browser extension...");const s={attempts:this.config.initParams?.extension?.attempts,attemptWaitMs:this.config.initParams?.extension?.attemptWaitMs,attemptTimeout:this.config.initParams?.extension?.attemptTimeout},a=await this.sendExtMessageWithTimeout(_.DISCOVER_EXTENSION,s,t);this.extensionId=a.extensionId,this.logger.info("Extension discovered:",this.extensionId)}this.setupBroadcastListener()}const i={type:"extension",extension:"ready",extensionId:this.extensionId,server:n.PENDING_EXTENSION_READY};let o=n.PENDING_EXTENSION_READY;if(e.testConnection)try{o=await this.getServerState(),this.logger.info("Server connectivity tested, state:",o.status)}catch(s){this.logger.error("Failed to get server state:",s),o=n.BACKEND_SERVER_NOT_REACHABLE}return this.setState({...i,server:o}),this.state}catch(i){this.logger.error("Failed to initialize extension:",i),this.extensionId=null;const o=n.createExtensionStateNotFound();return this.setState(o),this.state}}async sendExtMessageWithTimeout(e,t,r=1e4){const i=new Promise((o,s)=>setTimeout(()=>s(new Error("Timeout")),r));return Promise.race([this.sendExtRequest(e,t),i])}async sendExtRequest(e,t){try{const r=this.generateRequestId(),i=await chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_REQUEST,requestId:r,request:{action:e,params:t}});if(!i)throw n.createOperationError("extension_error","No response from background script");if(i.type!==h.EXT2EXT_CLIENT_RESPONSE)throw n.createOperationError("extension_error","Invalid response type from background script");const o=i.response;if(g.isExtError(o)){const s=o.error.type||"extension_error";throw n.createOperationError(s,o.error.message)}return o}catch(r){throw r instanceof n.BodhiError?r:n.createOperationError("extension_error",r instanceof Error?r.message:"Unknown error occurred")}}async sendRawApiMessage(e,t,r,i,o){const s=this.generateRequestId();return await chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_API_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i,authenticated:o}})}async sendApiRequest(e,t,r,i,o){try{const s=new Promise((E,c)=>setTimeout(()=>c(new Error(`[bodhi-js-sdk/ext] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),a=await Promise.race([this.sendRawApiMessage(e,t,r,i,o),s]);if(P(a)){const E=a.error.type||"extension_error";throw new n.BodhiError(E,a.error.message)}return a.response}catch(s){if(s instanceof n.BodhiError)throw s;const a=s instanceof Error?s.message:String(s);throw new n.BodhiError("network_error",a)}}async login(e){return new Promise((t,r)=>{const i=async o=>{if(o&&typeof o=="object"&&"type"in o&&o.type==="EXT2EXT_CLIENT_BROADCAST"&&"event"in o&&o.event==="authStateChanged"){chrome.runtime.onMessage.removeListener(i);try{const s=await this.getAuthState();if(n.isAuthError(s)){r(n.createOperationError("auth_error",`Login failed: ${s.error?.message}`));return}if(s.status!=="authenticated"){r(n.createOperationError("auth_error","Login failed: User is not logged in"));return}this.setAuthState(s),t(s)}catch(s){r(s)}}};chrome.runtime.onMessage.addListener(i),this.sendExtRequest(_.LOGIN,e).catch(o=>{chrome.runtime.onMessage.removeListener(i),r(o)})})}async logout(){await this.sendExtRequest(_.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(_.GET_AUTH_STATE)).authState:n.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){let e;try{e=await this.sendApiRequest("GET","/bodhi/v1/info")}catch(o){const s=o instanceof Error?o.message:"Connection failed",a=o instanceof n.BodhiError?o.code:"extension_error";return{status:"not-reachable",version:null,error:{message:s,type:a}}}if(e.status>=400)return{status:"not-reachable",version:null,error:{message:"API error from server",type:"extension_error"}};const t=e.body,r=t.version||"unknown",i={deployment:t.deployment??null,client_id:t.client_id??null};switch(t.status){case"ready":return{status:"ready",version:r,error:null,...i};case"setup":return{status:"setup",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Setup required",type:"extension_error"},...i};case"resource_admin":return{status:"resource_admin",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Resource admin required",type:"extension_error"},...i};case"error":return{status:"error",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Server error",type:"extension_error"},...i};default:return{status:"not-reachable",version:null,error:{message:"Unknown server status",type:"extension_error"}}}}async*stream(e,t,r,i,o=!0){const s=this.generateRequestId();this.logger.debug("Starting stream",{method:e,endpoint:t,requestId:s});const a=chrome.runtime.connect({name:f}),c=new ReadableStream({start:u=>{a.onMessage.addListener(d=>{if(d.requestId===s)switch(d.type){case h.EXT2EXT_CLIENT_STREAM_DONE:this.logger.debug("Stream complete",{requestId:s}),u.close(),a.disconnect();break;case h.EXT2EXT_CLIENT_STREAM_ERROR:this.logger.error("Stream error",{requestId:s,error:JSON.stringify(d.error)}),u.error(new n.BodhiError("extension_error",d.error.message)),a.disconnect();break;case h.EXT2EXT_CLIENT_STREAM_API_ERROR:{const T=d;this.logger.error("Stream API error",{requestId:s,error:T.response.body?.error}),u.error(new n.BodhiApiError(T.response.status,T.response.body,T.response.body?.error?.message||"API error")),a.disconnect();break}case h.EXT2EXT_CLIENT_STREAM_CHUNK:{const T=d;g.isApiSuccessResponse(T.response)&&u.enqueue(T.response.body);break}}}),a.onDisconnect.addListener(()=>{this.logger.debug("Port disconnected",{requestId:s});try{u.error(new n.BodhiError("connection_closed","Connection closed unexpectedly"))}catch{}}),a.postMessage({type:h.EXT2EXT_CLIENT_STREAM_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i,authenticated:o}})}}).getReader();try{for(;;){const{done:u,value:d}=await c.read();if(u){this.logger.debug("Stream iteration complete");break}yield d}}finally{c.releaseLock()}}get chat(){return this._chat??=new n.Chat(this)}get models(){return this._models??=new n.Models(this)}get embeddings(){return this._embeddings??=new n.Embeddings(this)}get toolsets(){return this._toolsets??=new n.Toolsets(this)}get mcps(){return this._mcps??=new n.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 n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}serialize(){return{extensionId:this.extensionId??void 0}}async debug(){return{type:"ExtClient",state:this.state,authState:await this.getAuthState()}}}class L extends n.BaseFacadeClient{constructor(e,t,r){const i=t||{},o={basePath:i.basePath||"/",authServerUrl:i.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userRole:i.userRole||"scope_user_user",logLevel:i.logLevel||"warn",apiTimeoutMs:i.apiTimeoutMs,initParams:i.initParams};super(e,o,r)}createLogger(e){return new n.Logger("ExtUIClient",e.logLevel)}createStoragePrefix(e){return n.createStoragePrefixWithBasePath(e.basePath,n.STORAGE_PREFIXES.EXT)}createExtClient(e,t){return new X({authClientId:this.authClientId,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new v({authClientId:e,authServerUrl:t.authServerUrl,userRole:t.userRole,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},r)}}const b=["ggedphdcbekjlomjaidbajglgihbeaon"],M=["bjdjhiombmfbcoeojijpfckljjghmjbf"],y="production",p=class p{constructor(e,t){this.isAuthenticating=!1,this.state="setup",this.listenersInitialized=!1,this.refreshPromise=null,this.activeStreamPorts=new Map,this.authClientId=e,this.authServerUrl=t?.authServerUrl||"https://id.getbodhi.app/realms/bodhi",this.userRole=t?.userRole||"scope_user_user",this.extensionId=t?.extensionId,this.logger=new n.Logger("BodhiExtClient",t?.logLevel||"warn"),this.attempts=t?.attempts??C,this.attemptWaitMs=t?.attemptWaitMs??w,this.attemptTimeout=t?.attemptTimeout??A,this.authEndpoints={authorize:`${this.authServerUrl}/protocol/openid-connect/auth`,token:`${this.authServerUrl}/protocol/openid-connect/token`,userinfo:`${this.authServerUrl}/protocol/openid-connect/userinfo`,logout:`${this.authServerUrl}/protocol/openid-connect/logout`,revoke:`${this.authServerUrl}/protocol/openid-connect/revoke`},this.extensionId?this.logger.info(`[BodhiExtClient] Created client for extension: ${this.extensionId}`):this.logger.info("[BodhiExtClient] Created client without extension ID (call init() to discover)")}static base64UrlEncode(e){return btoa(String.fromCharCode(...new Uint8Array(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}static generateCodeVerifier(){const e=new Uint8Array(32);return crypto.getRandomValues(e),p.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",r);return p.base64UrlEncode(i)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=y!=="production"?b:M;return this.logger.info("[Ext2Ext/Registry] Environment: production"),this.logger.debug("[Ext2Ext/Registry] Using extension IDs:",t),t}async pingExtension(e){return this.logger.debug(`[Ext2Ext/Discovery] Pinging extension: ${e} with timeout ${this.attemptTimeout}ms`),new Promise((t,r)=>{const i=setTimeout(()=>{this.logger.debug(`[Ext2Ext/Discovery] Timeout waiting for extension ${e}`),r(new Error("Timeout"))},this.attemptTimeout);try{const o={type:g.MESSAGE_TYPES.EXT_REQUEST,requestId:crypto.randomUUID(),request:{action:"get_extension_id"}};this.logger.debug(`[Ext2Ext/Discovery] Sending message to ${e}:`,o),chrome.runtime.sendMessage(e,o,s=>{if(clearTimeout(i),chrome.runtime.lastError){this.logger.error(`[Ext2Ext/Discovery] Error from extension ${e}:`,chrome.runtime.lastError.message),r(new Error(chrome.runtime.lastError.message));return}this.logger.debug(`[Ext2Ext/Discovery] Response from ${e}:`,s);const a=s;a&&a.type===g.MESSAGE_TYPES.EXT_RESPONSE?(this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`),t(!0)):(this.logger.error(`[Ext2Ext/Discovery] Invalid response from ${e}:`,s),r(new Error("Invalid response")))})}catch(o){this.logger.error(`[Ext2Ext/Discovery] Exception pinging ${e}:`,o),clearTimeout(i),r(o)}})}sleep(e){return new Promise(t=>setTimeout(t,e))}async discoverBodhiExtension(e){const{attempts:t,attemptWaitMs:r,attemptTimeout:i}=e;this.logger.info(`[Ext2Ext/Discovery] Starting discovery: ${t} attempts per ID, ${i}ms timeout, ${r}ms between attempts`);const o=this.getExtensionIdsForEnvironment();this.logger.debug(`[Ext2Ext/Discovery] Will try ${o.length} extension(s):`,o);for(const E of o){for(let c=1;c<=t;c++){this.logger.debug(`[Ext2Ext/Discovery] Trying ${E} - attempt ${c}/${t}`);try{return await this.pingExtension(E),this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${E} on attempt ${c}`),{success:!0,extensionId:E}}catch(u){this.logger.debug(`[Ext2Ext/Discovery] Attempt ${c} failed for ${E}: ${u instanceof Error?u.message:"Unknown error"}`),c<t&&await this.sleep(r)}}this.logger.warn(`[Ext2Ext/Discovery] ✗ Not found: ${E} after ${t} attempts`)}const s=o.join(", "),a=`Extension not found. Tried ${o.length} IDs with ${t} attempts each: ${s}`;return this.logger.error(`[Ext2Ext/Discovery] ${a}`),{success:!1,error:a}}setupListeners(){if(this.listenersInitialized){this.logger.debug("[BodhiExtClient] Listeners already initialized, skipping");return}this.listenersInitialized=!0,chrome.runtime.onMessage.addListener((e,t,r)=>e.type!==h.EXT2EXT_CLIENT_REQUEST&&e.type!==h.EXT2EXT_CLIENT_API_REQUEST?!1:this.state!=="ready"&&!(e.type===h.EXT2EXT_CLIENT_REQUEST&&e.request.action===_.DISCOVER_EXTENSION)?(e.type===h.EXT2EXT_CLIENT_REQUEST?r({type:h.EXT2EXT_CLIENT_RESPONSE,requestId:e.requestId,response:{error:{message:this.createErrorClientNotInitialized(e),type:"NOT_INITIALIZED"}}}):r({type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:e.requestId,error:{message:`Client not initialized. Extension discovery not complete, cannot handle type:${e.type}, message:${JSON.stringify(e)}`,type:"NOT_INITIALIZED"}}),!0):(this.logger.debug(`[BodhiExtClient] Processing message.type=${e.type}`),(async()=>{const i=await this.handleAction(e);r(i)})(),!0)),chrome.runtime.onConnect.addListener(e=>{if(e.name!==f){this.logger.debug("[BodhiExtClient] Ignoring port with name:",e.name);return}this.logger.info("[BodhiExtClient] Streaming port connected"),e.onMessage.addListener(async t=>{if(t.type!==h.EXT2EXT_CLIENT_STREAM_REQUEST){this.logger.warn("[BodhiExtClient] Unknown stream message type:",t.type),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:t.requestId,error:{message:"Unknown stream message type",type:"extension_error"}});return}await this.handleStreamRequest(e,t)}),e.onDisconnect.addListener(()=>{this.logger.info("[BodhiExtClient] Streaming port disconnected")})}),this.logger.info("[BodhiExtClient] Streaming listeners initialized")}async init(e){if(this.setupListeners(),this.extensionId){this.state="ready",this.logger.warn(`[BodhiExtClient] Already initialized with extension ID: ${this.extensionId}`);return}this.logger.info("[BodhiExtClient] Starting discovery");const t={attempts:e?.attempts??this.attempts,attemptWaitMs:e?.attemptWaitMs??this.attemptWaitMs,attemptTimeout:e?.attemptTimeout??this.attemptTimeout},r=await this.discoverBodhiExtension(t);if(!r.success||!r.extensionId)throw new Error(r.error||"Discovery failed");this.extensionId=r.extensionId,this.state="ready",this.logger.info(`[BodhiExtClient] ✓ Initialized: ${this.extensionId}`)}broadcastAuthStateChange(){chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_BROADCAST,event:"authStateChanged"}).catch(e=>{this.logger.debug("[BodhiExtClient] No listeners for broadcast:",e.message)})}async getExtensionIdFromExt(){this.logger.debug("[BodhiExtClient] Getting extension ID from bodhi-browser-ext");const e=await this.sendExtRequest("get_extension_id");return this.logger.debug("[BodhiExtClient] Extension ID response:",e),e.extension_id}async handleApiRequest(e){const{requestId:t}=e;this.logger.debug("[BodhiExtClient] Handling API request:",e.request);try{let r=e.request.headers||{};if(e.request.authenticated){const o=await this._getAccessTokenRaw();if(!o)return{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"auth_error"}};r={...r,Authorization:`Bearer ${o}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const i=await this.sendApiRequest(e.request.method,e.request.endpoint,e.request.body,r);return{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,response:i}}catch(r){return this.logger.error("[BodhiExtClient] API request failed:",r),{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:r instanceof Error?r.message:"Unknown error",type:"network_error"}}}}async handleExtClientRequest(e){const{requestId:t,request:r}=e,{action:i,params:o}=r;this.logger.debug(`[BodhiExtClient] Handling action: ${i}`);try{let s={};switch(i){case _.DISCOVER_EXTENSION:{const a=o;await this.init(a),this.logger.info("[BodhiExtClient] Discovery successful:",{extensionId:this.extensionId,environment:y}),s={extensionId:this.extensionId,environment:y};break}case _.SET_EXTENSION_ID:{const{extensionId:a}=o;this.extensionId=a,this.state="ready",this.logger.info("[BodhiExtClient] Extension ID set:",{extensionId:a}),s={success:!0};break}case _.GET_EXTENSION_ID:{const a=await this.sendExtRequestRaw(g.EXT_ACTIONS.GET_EXTENSION_ID,o);return g.isExtError(a.response)?{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:a.response.error.message||`Extension request failed to get extension ID: ${JSON.stringify(a.response)}`,type:a.response.error.type}}}:{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:a.response}}case _.LOGIN:{const a=o;await this.login(a),this.broadcastAuthStateChange();break}case _.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case _.GET_AUTH_STATE:s={authState:await this.getAuthState()};break;default:return{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown action: ${i}`,type:"UNKNOWN_ACTION"}}}}return{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:s}}catch(s){return this.logger.error("[BodhiExtClient] Unexpected error:",s),{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:s instanceof Error?s.message:`Unexpected error: ${JSON.stringify(s)}`}}}}}async handleAction(e){switch(e.type){case h.EXT2EXT_CLIENT_API_REQUEST:return this.handleApiRequest(e);case h.EXT2EXT_CLIENT_REQUEST:return this.handleExtClientRequest(e);default:{const{requestId:t}=e;return this.logger.error("[BodhiExtClient] Unknown message type:",e.type),{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown message type: ${e.type}`,type:"UNKNOWN_MESSAGE_TYPE"}}}}}}async login(e){if(!(this.isAuthenticating||(await this.getAuthState()).status==="authenticated")){this.isAuthenticating=!0;try{if(!this.extensionId)throw new Error("Extension not discovered. Please detect Bodhi extension before login.");e?.flowType==="redirect"&&this.logger.warn("Extension mode does not support redirect flow type; using popup instead");const r=e?.userRole??this.userRole,i=new n.AccessRequestBuilder(this.authClientId).requestedRole(r).flowType("popup");e?.requested&&i.requested(e.requested);const o=i.build(),s=await this.requestAccess(o),{id:a,review_url:E}=n.unwrapResponse(s);await chrome.tabs.create({url:E});const c=await this.pollAccessRequestStatus(a,{intervalMs:e?.pollIntervalMs??n.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??n.DEFAULT_POLL_TIMEOUT_MS});if(c.status!=="approved")throw n.createOperationError("auth_error",`Access request ${c.status}`);const d=`openid profile email roles ${c.access_request_scope??""}`.trim();await this.performOAuthPkce(d)}finally{this.isAuthenticating=!1}}}async exchangeCodeForTokens(e){if((await this.getAuthState()).status==="authenticated")return;const{codeVerifier:r}=await chrome.storage.session.get("codeVerifier"),i=chrome.identity.getRedirectURL("callback"),o=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:i,client_id:this.authClientId,code_verifier:r})});if(!o.ok){const a=await o.text();throw new Error(`Token exchange failed: ${o.status} ${a}`)}const s=await o.json();await this.storeTokens({accessToken:s.access_token,refreshToken:s.refresh_token,idToken:s.id_token,expiresIn:s.expires_in})}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{const t=this.parseJwt(e);return{status:"authenticated",user:{sub:t.sub,email:t.email,name:t.name,given_name:t.given_name,family_name:t.family_name,preferred_username:t.preferred_username},accessToken:e,error:null}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null}}}async logout(){const{refreshToken:e}=await chrome.storage.session.get("refreshToken");if(e)try{await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"})})}catch(t){this.logger.warn("[OAuth] Token revocation failed:",t)}await this.clearTokens()}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e)}async getAccessRequestStatus(e){return this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`)}async pollAccessRequestStatus(e,t){return n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}async performOAuthPkce(e){const t=p.generateCodeVerifier(),r=await p.generateCodeChallenge(t),i=p.generateCodeVerifier();await chrome.storage.session.set({codeVerifier:t,state:i,authInProgress:!0});const o=chrome.identity.getRedirectURL("callback"),s=new URL(this.authEndpoints.authorize);return s.searchParams.set("client_id",this.authClientId),s.searchParams.set("response_type","code"),s.searchParams.set("redirect_uri",o),s.searchParams.set("scope",e),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),s.searchParams.set("state",i),new Promise((a,E)=>{chrome.identity.launchWebAuthFlow({url:s.toString(),interactive:!0},async c=>{if(await chrome.storage.session.set({authInProgress:!1}),chrome.runtime.lastError){await chrome.storage.session.remove(["codeVerifier","state"]),E(chrome.runtime.lastError);return}if(!c){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","No redirect URL received"));return}try{const u=new URL(c),d=u.searchParams.get("code"),T=u.searchParams.get("state"),{state:m}=await chrome.storage.session.get("state");if(T!==m){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","State mismatch"));return}if(!d){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","No authorization code"));return}await this.exchangeCodeForTokens(d),await chrome.storage.session.remove(["codeVerifier","state"]);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("oauth_error","Login failed");a(l)}catch(u){await chrome.storage.session.remove(["codeVerifier","state"]),E(u)}})})}async sendExtRequest(e,t){const r=await this.sendExtRequestRaw(e,t);if(g.isExtError(r.response))throw this.logger.error("[BodhiExtClient] Extension error:",r.response.error),new Error(r.response.error.message||`Extension request failed: ${JSON.stringify(r.response)}`);return r.response}async sendApiRequest(e,t,r,i){if(!this.extensionId)throw new n.BodhiError("not_initialized",this.createErrorClientNotInitialized({type:"api",method:e,endpoint:t}));this.logger.debug(`[BodhiExtClient] Sending API_REQUEST: method=${e}, endpoint=${t}`,r?{body:r}:"");const o=crypto.randomUUID(),s={type:g.MESSAGE_TYPES.API_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i}};return this.logger.debug(`[BodhiExtClient] Request ID: ${o}, Extension: ${this.extensionId}`),new Promise((a,E)=>{try{chrome.runtime.sendMessage(this.extensionId,s,c=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${o}:`,chrome.runtime.lastError),E(new n.BodhiError("extension_error",chrome.runtime.lastError.message??"Chrome runtime error"));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${o}:`,c),!c){this.logger.error(`[BodhiExtClient] No response received for request ${o}`),E(new n.BodhiError("extension_error","No response from extension"));return}c.type===g.MESSAGE_TYPES.API_RESPONSE&&c.requestId===o?"error"in c?(this.logger.error(`[BodhiExtClient] API error for ${o}:`,c.error),E(new n.BodhiError(c.error.type||"extension_error",c.error.message))):(this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${o}`),a(c.response)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${o}:`,c),E(new n.BodhiError("extension_error","Invalid response format")))})}catch(c){this.logger.error(`[BodhiExtClient] Exception sending message for ${o}:`,c),E(c)}})}async sendExtRequestRaw(e,t){if(!this.extensionId)throw new n.BodhiError("not_initialized",this.createErrorClientNotInitialized({type:"ext",action:e,params:t}));this.logger.debug(`[BodhiExtClient] Sending EXT_REQUEST (raw): action=${e}`,t?{params:t}:"");const r=crypto.randomUUID(),i={type:g.MESSAGE_TYPES.EXT_REQUEST,requestId:r,request:{action:e,params:t}};return this.logger.debug(`[BodhiExtClient] Request ID: ${r}, Extension: ${this.extensionId}`),new Promise((o,s)=>{try{chrome.runtime.sendMessage(this.extensionId,i,a=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${r}:`,chrome.runtime.lastError),s(new Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${r}:`,a),!a){this.logger.error(`[BodhiExtClient] No response received for request ${r}`),s(new Error("No response from extension"));return}a.type===g.MESSAGE_TYPES.EXT_RESPONSE&&a.requestId===r?(this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`),o(a)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${r}:`,a),s(new n.BodhiError("extension_error","Invalid response format")))})}catch(a){this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`,a),s(a)}})}async handleStreamRequest(e,t){const{requestId:r,request:i}=t,{method:o,endpoint:s,body:a,headers:E,authenticated:c}=i;this.logger.debug("[BodhiExtClient] Processing stream request:",{requestId:r,method:o,endpoint:s,authenticated:c}),this.extensionId||e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:this.createErrorClientNotInitialized(t),type:"extension_error"}});try{let u={...E};if(c!==!1){const l=await this._getAccessTokenRaw();if(!l){e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Not authenticated. Please log in first.",type:"extension_error"}});return}u={...u,Authorization:`Bearer ${l}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const d=chrome.runtime.connect(this.extensionId,{name:g.BODHI_STREAM_PORT});this.activeStreamPorts.set(r,d);const T=setTimeout(()=>{this.activeStreamPorts.has(r)&&(this.logger.error(`[BodhiExtClient] Stream timeout for ${r}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Stream request timed out",type:"timeout_error"}}),this.cleanupStreamPort(r))},p.STREAM_TIMEOUT);d.onMessage.addListener(l=>{if(g.isStreamChunk(l)){const I=l.response,O=I.body;I.status>=400?e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:I}):O?.done?(e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_DONE,requestId:r}),this.logger.info(`[BodhiExtClient] Stream complete for ${r}`),clearTimeout(T),this.cleanupStreamPort(r)):e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_CHUNK,requestId:r,response:I})}else g.isStreamApiError(l)?(this.logger.error(`[BodhiExtClient] Stream API error for ${r}: ${l.response.status}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:l.response})):g.isStreamError(l)&&(this.logger.error(`[BodhiExtClient] Stream error for ${r}:`,l.error.message),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:`stream error: ${JSON.stringify(l)}`,type:"extension_error"}}),clearTimeout(T),this.cleanupStreamPort(r))}),d.onDisconnect.addListener(()=>{clearTimeout(T),this.activeStreamPorts.has(r)&&(this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${r}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Connection to Bodhi extension closed unexpectedly",type:"network_error"}}),this.activeStreamPorts.delete(r))});const m={type:g.MESSAGE_TYPES.STREAM_REQUEST,requestId:r,request:{method:o,endpoint:s,body:a,headers:u}};this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",m),d.postMessage(m)}catch(u){const d=u;this.logger.error("[BodhiExtClient] Stream error:",JSON.stringify(d.message)),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:`uncaught error: ${JSON.stringify({error:d,message:d.message})}`,type:"extension_error"}})}}cleanupStreamPort(e){const t=this.activeStreamPorts.get(e);if(t){try{t.disconnect()}catch{}this.activeStreamPorts.delete(e)}}async storeTokens(e){const t=Date.now()+(e.expiresIn||3600)*1e3;await chrome.storage.session.set({accessToken:e.accessToken,refreshToken:e.refreshToken,idToken:e.idToken,expiresAt:t})}async _getAccessTokenRaw(){const{accessToken:e,expiresAt:t}=await chrome.storage.session.get(["accessToken","expiresAt"]);if(!e||!t)return null;if(Date.now()>=t-5*1e3){const{refreshToken:r}=await chrome.storage.session.get("refreshToken");return r?this._tryRefreshToken(r):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 n.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success)return await this._storeRefreshedTokens(t.tokens),this.logger.info("Token refreshed successfully"),this.broadcastAuthStateChange(),t.tokens.access_token;if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),await this.clearTokens(),this.broadcastAuthStateChange(),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),n.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}async _storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3,r={accessToken:e.access_token,expiresAt:t};e.refresh_token&&(r.refreshToken=e.refresh_token),e.id_token&&(r.idToken=e.id_token),await chrome.storage.session.set(r)}async clearTokens(){await chrome.storage.session.remove(["accessToken","refreshToken","idToken","expiresAt","codeVerifier","state","authInProgress","bodhiUserInfo"])}parseJwt(e){const r=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),i=decodeURIComponent(atob(r).split("").map(o=>"%"+("00"+o.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(i)}createErrorClientNotInitialized(e){return`Client not initialized. Extension discovery not triggered nor extensionId set, cannot handle request: ${JSON.stringify(e)}`}};p.STREAM_TIMEOUT=6e4;let R=p;const k="production";exports.BodhiExtClient=R;exports.DEFAULT_API_TIMEOUT_MS=N;exports.DISCOVERY_ATTEMPTS=C;exports.DISCOVERY_ATTEMPT_TIMEOUT=A;exports.DISCOVERY_ATTEMPT_WAIT_MS=w;exports.DISCOVERY_TIMEOUT_MS=x;exports.EXT2EXT_CLIENT_ACTIONS=_;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=h;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT_BUILD_MODE=k;exports.ExtUIClient=L;exports.isExtClientApiError=P;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("@bodhiapp/bodhi-js-core"),g=require("@bodhiapp/bodhi-browser-types"),h={EXT2EXT_CLIENT_REQUEST:"EXT2EXT_CLIENT_REQUEST",EXT2EXT_CLIENT_RESPONSE:"EXT2EXT_CLIENT_RESPONSE",EXT2EXT_CLIENT_BROADCAST:"EXT2EXT_CLIENT_BROADCAST",EXT2EXT_CLIENT_API_REQUEST:"EXT2EXT_CLIENT_API_REQUEST",EXT2EXT_CLIENT_API_RESPONSE:"EXT2EXT_CLIENT_API_RESPONSE",EXT2EXT_CLIENT_STREAM_REQUEST:"EXT2EXT_CLIENT_STREAM_REQUEST",EXT2EXT_CLIENT_STREAM_CHUNK:"EXT2EXT_CLIENT_STREAM_CHUNK",EXT2EXT_CLIENT_STREAM_ERROR:"EXT2EXT_CLIENT_STREAM_ERROR",EXT2EXT_CLIENT_STREAM_API_ERROR:"EXT2EXT_CLIENT_STREAM_API_ERROR",EXT2EXT_CLIENT_STREAM_DONE:"EXT2EXT_CLIENT_STREAM_DONE"},f="ext2ext-client-stream",_={LOGIN:"login",LOGOUT:"logout",GET_AUTH_STATE:"getAuthState",DISCOVER_EXTENSION:"discoverBodhiExtension",GET_EXTENSION_ID:"get_extension_id",SET_EXTENSION_ID:"setExtensionId"},x=5e3,C=3,w=500,A=500,N=3e4;function P(S){return"error"in S}class v extends n.DirectClientBase{constructor(e,t){const r=n.createStoragePrefixWithBasePath(e.basePath,n.STORAGE_PREFIXES.EXT_DIRECT),i={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectExtClient",apiTimeoutMs:e.apiTimeoutMs};super(i,t)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;e?.flowType==="redirect"&&this.logger.warn("Extension mode does not support redirect flow type; using popup instead");const r=e?.userRole??"scope_user_user";e?.onProgress?.("requesting");const i=new n.AccessRequestBuilder(this.authClientId).requestedRole(r).flowType("popup");e?.requested&&i.requested(e.requested);const o=i.build(),s=await this.requestAccess(o),{id:a,review_url:E}=n.unwrapResponse(s);e?.onProgress?.("reviewing"),await chrome.tabs.create({url:E});const c=await this.pollAccessRequestStatus(a,{intervalMs:e?.pollIntervalMs??n.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??n.DEFAULT_POLL_TIMEOUT_MS});if(c.status!=="approved")throw n.createOperationError("auth_error",`Access request ${c.status}`);const u=c.access_request_scope;return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${u??""}`.trim())}async performOAuthPkce(e){const t=n.generateCodeVerifier(),r=await n.generateCodeChallenge(t),i=n.generateCodeVerifier();await chrome.storage.session.set({[this.storageKeys.CODE_VERIFIER]:t,[this.storageKeys.STATE]:i});const o=chrome.identity.getRedirectURL("callback"),s=new URL(this.authEndpoints.authorize);return s.searchParams.set("client_id",this.authClientId),s.searchParams.set("response_type","code"),s.searchParams.set("redirect_uri",o),s.searchParams.set("scope",e),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),s.searchParams.set("state",i),new Promise((a,E)=>{chrome.identity.launchWebAuthFlow({url:s.toString(),interactive:!0},async c=>{if(chrome.runtime.lastError){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(chrome.runtime.lastError);return}if(!c){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","No redirect URL received"));return}try{const u=new URL(c),d=u.searchParams.get("code"),T=u.searchParams.get("state"),m=await chrome.storage.session.get(this.storageKeys.STATE);if(T!==m[this.storageKeys.STATE]){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","State mismatch"));return}if(!d){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","No authorization code"));return}await this.exchangeCodeForTokens(d);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("oauth_error","Login failed");this.setAuthState(l),await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a(l)}catch(u){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(u)}})})}async logout(){const t=(await chrome.storage.session.get(this.storageKeys.REFRESH_TOKEN))[this.storageKeys.REFRESH_TOKEN];if(t)try{const i=new URLSearchParams({token:t,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:i})}catch(i){this.logger.warn("Token revocation failed:",i)}await chrome.storage.session.remove([this.storageKeys.ACCESS_TOKEN,this.storageKeys.REFRESH_TOKEN,this.storageKeys.EXPIRES_AT]);const r={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(r),r}async exchangeCodeForTokens(e){const r=(await chrome.storage.session.get(this.storageKeys.CODE_VERIFIER))[this.storageKeys.CODE_VERIFIER],i=chrome.identity.getRedirectURL("callback"),o=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:i,client_id:this.authClientId,code_verifier:r})});if(!o.ok){const E=await o.text();throw new Error(`Token exchange failed: ${o.status} ${E}`)}const s=await o.json(),a=Date.now()+(s.expires_in||3600)*1e3;await chrome.storage.session.set({[this.storageKeys.ACCESS_TOKEN]:s.access_token,[this.storageKeys.REFRESH_TOKEN]:s.refresh_token,[this.storageKeys.EXPIRES_AT]:a}),await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE])}async _storageGet(e){const r=(await chrome.storage.session.get(e))[e];return r!==void 0?String(r):null}async _storageSet(e){await chrome.storage.session.set(e)}async _storageRemove(e){await chrome.storage.session.remove(e)}_getRedirectUri(){return chrome.identity.getRedirectURL("callback")}}class X{constructor(e={},t){this.state={type:"extension",extension:"not-initialized",extensionId:null,server:n.PENDING_EXTENSION_READY},this.extensionId=null,this.broadcastListenerActive=!1,this.config=e,this.logger=new n.Logger("ExtClient",e?.logLevel||"warn"),this.onStateChange=t??n.NOOP_STATE_CALLBACK,this.apiTimeoutMs=e.apiTimeoutMs??N,this.authClientId=e.authClientId??""}setState(e){this.state=e,this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}setupBroadcastListener(){this.broadcastListenerActive||(this.broadcastListenerActive=!0,chrome.runtime.onMessage.addListener(e=>{const t=e;return t?.type===h.EXT2EXT_CLIENT_BROADCAST&&t.event==="authStateChanged"&&this.handleAuthStateChangedBroadcast(),!1}),this.logger.debug("Broadcast listener setup complete"))}async handleAuthStateChangedBroadcast(){this.logger.debug("Received authStateChanged broadcast, refreshing auth state");const e=await this.getAuthState();this.setAuthState(e)}generateRequestId(){return crypto.randomUUID()}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){this.logger.info("No testConnection or selectedConnection, returning not-initialized state");const i=n.createExtensionStateNotInitialized();return this.setState(i),i}if(this.extensionId&&!e.testConnection)return this.logger.debug("Already initialized with extensionId, skipping discovery"),this.state;const t=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??x,r=e.savedState?.extensionId;try{if(!this.extensionId){if(r)this.logger.info("Restoring with known extensionId:",r),await this.sendExtMessageWithTimeout(_.SET_EXTENSION_ID,{extensionId:r},t),this.extensionId=r;else{this.logger.info("Discovering bodhi-browser extension...");const s={attempts:this.config.initParams?.extension?.attempts,attemptWaitMs:this.config.initParams?.extension?.attemptWaitMs,attemptTimeout:this.config.initParams?.extension?.attemptTimeout},a=await this.sendExtMessageWithTimeout(_.DISCOVER_EXTENSION,s,t);this.extensionId=a.extensionId,this.logger.info("Extension discovered:",this.extensionId)}this.setupBroadcastListener()}const i={type:"extension",extension:"ready",extensionId:this.extensionId,server:n.PENDING_EXTENSION_READY};let o=n.PENDING_EXTENSION_READY;if(e.testConnection)try{o=await this.getServerState(),this.logger.info("Server connectivity tested, state:",o.status)}catch(s){this.logger.error("Failed to get server state:",s),o=n.BACKEND_SERVER_NOT_REACHABLE}return this.setState({...i,server:o}),this.state}catch(i){this.logger.error("Failed to initialize extension:",i),this.extensionId=null;const o=n.createExtensionStateNotFound();return this.setState(o),this.state}}async sendExtMessageWithTimeout(e,t,r=1e4){const i=new Promise((o,s)=>setTimeout(()=>s(new Error("Timeout")),r));return Promise.race([this.sendExtRequest(e,t),i])}async sendExtRequest(e,t){try{const r=this.generateRequestId(),i=await chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_REQUEST,requestId:r,request:{action:e,params:t}});if(!i)throw n.createOperationError("extension_error","No response from background script");if(i.type!==h.EXT2EXT_CLIENT_RESPONSE)throw n.createOperationError("extension_error","Invalid response type from background script");const o=i.response;if(g.isExtError(o)){const s=o.error.type||"extension_error";throw n.createOperationError(s,o.error.message)}return o}catch(r){throw r instanceof n.BodhiError?r:n.createOperationError("extension_error",r instanceof Error?r.message:"Unknown error occurred")}}async sendRawApiMessage(e,t,r,i,o){const s=this.generateRequestId();return await chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_API_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i,authenticated:o}})}async sendApiRequest(e,t,r,i,o){try{const s=new Promise((E,c)=>setTimeout(()=>c(new Error(`[bodhi-js-sdk/ext] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),a=await Promise.race([this.sendRawApiMessage(e,t,r,i,o),s]);if(P(a)){const E=a.error.type||"extension_error";throw new n.BodhiError(E,a.error.message)}return a.response}catch(s){if(s instanceof n.BodhiError)throw s;const a=s instanceof Error?s.message:String(s);throw new n.BodhiError("network_error",a)}}async login(e){return new Promise((t,r)=>{const i=async o=>{if(o&&typeof o=="object"&&"type"in o&&o.type==="EXT2EXT_CLIENT_BROADCAST"&&"event"in o&&o.event==="authStateChanged"){chrome.runtime.onMessage.removeListener(i);try{const s=await this.getAuthState();if(n.isAuthError(s)){r(n.createOperationError("auth_error",`Login failed: ${s.error?.message}`));return}if(s.status!=="authenticated"){r(n.createOperationError("auth_error","Login failed: User is not logged in"));return}this.setAuthState(s),t(s)}catch(s){r(s)}}};chrome.runtime.onMessage.addListener(i),this.sendExtRequest(_.LOGIN,e).catch(o=>{chrome.runtime.onMessage.removeListener(i),r(o)})})}async logout(){await this.sendExtRequest(_.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(_.GET_AUTH_STATE)).authState:n.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){let e;try{e=await this.sendApiRequest("GET","/bodhi/v1/info")}catch(o){const s=o instanceof Error?o.message:"Connection failed",a=o instanceof n.BodhiError?o.code:"extension_error";return{status:"not-reachable",version:null,error:{message:s,type:a}}}if(e.status>=400)return{status:"not-reachable",version:null,error:{message:"API error from server",type:"extension_error"}};const t=e.body,r=t.version||"unknown",i={deployment:t.deployment??null,client_id:t.client_id??null};switch(t.status){case"ready":return{status:"ready",version:r,error:null,...i};case"setup":return{status:"setup",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Setup required",type:"extension_error"},...i};case"resource_admin":return{status:"resource_admin",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Resource admin required",type:"extension_error"},...i};case"error":return{status:"error",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Server error",type:"extension_error"},...i};default:return{status:"not-reachable",version:null,error:{message:"Unknown server status",type:"extension_error"}}}}async*stream(e,t,r,i,o=!0){const s=this.generateRequestId();this.logger.debug("Starting stream",{method:e,endpoint:t,requestId:s});const a=chrome.runtime.connect({name:f}),c=new ReadableStream({start:u=>{a.onMessage.addListener(d=>{if(d.requestId===s)switch(d.type){case h.EXT2EXT_CLIENT_STREAM_DONE:this.logger.debug("Stream complete",{requestId:s}),u.close(),a.disconnect();break;case h.EXT2EXT_CLIENT_STREAM_ERROR:this.logger.error("Stream error",{requestId:s,error:JSON.stringify(d.error)}),u.error(new n.BodhiError("extension_error",d.error.message)),a.disconnect();break;case h.EXT2EXT_CLIENT_STREAM_API_ERROR:{const T=d;this.logger.error("Stream API error",{requestId:s,error:T.response.body?.error}),u.error(new n.BodhiApiError(T.response.status,T.response.body,T.response.body?.error?.message||"API error")),a.disconnect();break}case h.EXT2EXT_CLIENT_STREAM_CHUNK:{const T=d;g.isApiSuccessResponse(T.response)&&u.enqueue(T.response.body);break}}}),a.onDisconnect.addListener(()=>{this.logger.debug("Port disconnected",{requestId:s});try{u.error(new n.BodhiError("connection_closed","Connection closed unexpectedly"))}catch{}}),a.postMessage({type:h.EXT2EXT_CLIENT_STREAM_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i,authenticated:o}})}}).getReader();try{for(;;){const{done:u,value:d}=await c.read();if(u){this.logger.debug("Stream iteration complete");break}yield d}}finally{c.releaseLock()}}get chat(){return this._chat??=new n.Chat(this)}get models(){return this._models??=new n.Models(this)}get embeddings(){return this._embeddings??=new n.Embeddings(this)}get mcps(){return this._mcps??=new n.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 n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}serialize(){return{extensionId:this.extensionId??void 0}}async debug(){return{type:"ExtClient",state:this.state,authState:await this.getAuthState()}}}class L extends n.BaseFacadeClient{constructor(e,t,r){const i=t||{},o={basePath:i.basePath||"/",authServerUrl:i.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:i.logLevel||"warn",apiTimeoutMs:i.apiTimeoutMs,initParams:i.initParams};super(e,o,r)}createLogger(e){return new n.Logger("ExtUIClient",e.logLevel)}createStoragePrefix(e){return n.createStoragePrefixWithBasePath(e.basePath,n.STORAGE_PREFIXES.EXT)}createExtClient(e,t){return new X({authClientId:this.authClientId,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new v({authClientId:e,authServerUrl:t.authServerUrl,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},r)}}const b=["ggedphdcbekjlomjaidbajglgihbeaon"],M=["bjdjhiombmfbcoeojijpfckljjghmjbf"],y="production",p=class p{constructor(e,t){this.isAuthenticating=!1,this.state="setup",this.listenersInitialized=!1,this.refreshPromise=null,this.activeStreamPorts=new Map,this.authClientId=e,this.authServerUrl=t?.authServerUrl||"https://id.getbodhi.app/realms/bodhi",this.extensionId=t?.extensionId,this.logger=new n.Logger("BodhiExtClient",t?.logLevel||"warn"),this.attempts=t?.attempts??C,this.attemptWaitMs=t?.attemptWaitMs??w,this.attemptTimeout=t?.attemptTimeout??A,this.authEndpoints={authorize:`${this.authServerUrl}/protocol/openid-connect/auth`,token:`${this.authServerUrl}/protocol/openid-connect/token`,userinfo:`${this.authServerUrl}/protocol/openid-connect/userinfo`,logout:`${this.authServerUrl}/protocol/openid-connect/logout`,revoke:`${this.authServerUrl}/protocol/openid-connect/revoke`},this.extensionId?this.logger.info(`[BodhiExtClient] Created client for extension: ${this.extensionId}`):this.logger.info("[BodhiExtClient] Created client without extension ID (call init() to discover)")}static base64UrlEncode(e){return btoa(String.fromCharCode(...new Uint8Array(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}static generateCodeVerifier(){const e=new Uint8Array(32);return crypto.getRandomValues(e),p.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",r);return p.base64UrlEncode(i)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=y!=="production"?b:M;return this.logger.info("[Ext2Ext/Registry] Environment: production"),this.logger.debug("[Ext2Ext/Registry] Using extension IDs:",t),t}async pingExtension(e){return this.logger.debug(`[Ext2Ext/Discovery] Pinging extension: ${e} with timeout ${this.attemptTimeout}ms`),new Promise((t,r)=>{const i=setTimeout(()=>{this.logger.debug(`[Ext2Ext/Discovery] Timeout waiting for extension ${e}`),r(new Error("Timeout"))},this.attemptTimeout);try{const o={type:g.MESSAGE_TYPES.EXT_REQUEST,requestId:crypto.randomUUID(),request:{action:"get_extension_id"}};this.logger.debug(`[Ext2Ext/Discovery] Sending message to ${e}:`,o),chrome.runtime.sendMessage(e,o,s=>{if(clearTimeout(i),chrome.runtime.lastError){this.logger.error(`[Ext2Ext/Discovery] Error from extension ${e}:`,chrome.runtime.lastError.message),r(new Error(chrome.runtime.lastError.message));return}this.logger.debug(`[Ext2Ext/Discovery] Response from ${e}:`,s);const a=s;a&&a.type===g.MESSAGE_TYPES.EXT_RESPONSE?(this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`),t(!0)):(this.logger.error(`[Ext2Ext/Discovery] Invalid response from ${e}:`,s),r(new Error("Invalid response")))})}catch(o){this.logger.error(`[Ext2Ext/Discovery] Exception pinging ${e}:`,o),clearTimeout(i),r(o)}})}sleep(e){return new Promise(t=>setTimeout(t,e))}async discoverBodhiExtension(e){const{attempts:t,attemptWaitMs:r,attemptTimeout:i}=e;this.logger.info(`[Ext2Ext/Discovery] Starting discovery: ${t} attempts per ID, ${i}ms timeout, ${r}ms between attempts`);const o=this.getExtensionIdsForEnvironment();this.logger.debug(`[Ext2Ext/Discovery] Will try ${o.length} extension(s):`,o);for(const E of o){for(let c=1;c<=t;c++){this.logger.debug(`[Ext2Ext/Discovery] Trying ${E} - attempt ${c}/${t}`);try{return await this.pingExtension(E),this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${E} on attempt ${c}`),{success:!0,extensionId:E}}catch(u){this.logger.debug(`[Ext2Ext/Discovery] Attempt ${c} failed for ${E}: ${u instanceof Error?u.message:"Unknown error"}`),c<t&&await this.sleep(r)}}this.logger.warn(`[Ext2Ext/Discovery] ✗ Not found: ${E} after ${t} attempts`)}const s=o.join(", "),a=`Extension not found. Tried ${o.length} IDs with ${t} attempts each: ${s}`;return this.logger.error(`[Ext2Ext/Discovery] ${a}`),{success:!1,error:a}}setupListeners(){if(this.listenersInitialized){this.logger.debug("[BodhiExtClient] Listeners already initialized, skipping");return}this.listenersInitialized=!0,chrome.runtime.onMessage.addListener((e,t,r)=>e.type!==h.EXT2EXT_CLIENT_REQUEST&&e.type!==h.EXT2EXT_CLIENT_API_REQUEST?!1:this.state!=="ready"&&!(e.type===h.EXT2EXT_CLIENT_REQUEST&&e.request.action===_.DISCOVER_EXTENSION)?(e.type===h.EXT2EXT_CLIENT_REQUEST?r({type:h.EXT2EXT_CLIENT_RESPONSE,requestId:e.requestId,response:{error:{message:this.createErrorClientNotInitialized(e),type:"NOT_INITIALIZED"}}}):r({type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:e.requestId,error:{message:`Client not initialized. Extension discovery not complete, cannot handle type:${e.type}, message:${JSON.stringify(e)}`,type:"NOT_INITIALIZED"}}),!0):(this.logger.debug(`[BodhiExtClient] Processing message.type=${e.type}`),(async()=>{const i=await this.handleAction(e);r(i)})(),!0)),chrome.runtime.onConnect.addListener(e=>{if(e.name!==f){this.logger.debug("[BodhiExtClient] Ignoring port with name:",e.name);return}this.logger.info("[BodhiExtClient] Streaming port connected"),e.onMessage.addListener(async t=>{if(t.type!==h.EXT2EXT_CLIENT_STREAM_REQUEST){this.logger.warn("[BodhiExtClient] Unknown stream message type:",t.type),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:t.requestId,error:{message:"Unknown stream message type",type:"extension_error"}});return}await this.handleStreamRequest(e,t)}),e.onDisconnect.addListener(()=>{this.logger.info("[BodhiExtClient] Streaming port disconnected")})}),this.logger.info("[BodhiExtClient] Streaming listeners initialized")}async init(e){if(this.setupListeners(),this.extensionId){this.state="ready",this.logger.warn(`[BodhiExtClient] Already initialized with extension ID: ${this.extensionId}`);return}this.logger.info("[BodhiExtClient] Starting discovery");const t={attempts:e?.attempts??this.attempts,attemptWaitMs:e?.attemptWaitMs??this.attemptWaitMs,attemptTimeout:e?.attemptTimeout??this.attemptTimeout},r=await this.discoverBodhiExtension(t);if(!r.success||!r.extensionId)throw new Error(r.error||"Discovery failed");this.extensionId=r.extensionId,this.state="ready",this.logger.info(`[BodhiExtClient] ✓ Initialized: ${this.extensionId}`)}broadcastAuthStateChange(){chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_BROADCAST,event:"authStateChanged"}).catch(e=>{this.logger.debug("[BodhiExtClient] No listeners for broadcast:",e.message)})}async getExtensionIdFromExt(){this.logger.debug("[BodhiExtClient] Getting extension ID from bodhi-browser-ext");const e=await this.sendExtRequest("get_extension_id");return this.logger.debug("[BodhiExtClient] Extension ID response:",e),e.extension_id}async handleApiRequest(e){const{requestId:t}=e;this.logger.debug("[BodhiExtClient] Handling API request:",e.request);try{let r=e.request.headers||{};if(e.request.authenticated){const o=await this._getAccessTokenRaw();if(!o)return{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"auth_error"}};r={...r,Authorization:`Bearer ${o}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const i=await this.sendApiRequest(e.request.method,e.request.endpoint,e.request.body,r);return{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,response:i}}catch(r){return this.logger.error("[BodhiExtClient] API request failed:",r),{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:r instanceof Error?r.message:"Unknown error",type:"network_error"}}}}async handleExtClientRequest(e){const{requestId:t,request:r}=e,{action:i,params:o}=r;this.logger.debug(`[BodhiExtClient] Handling action: ${i}`);try{let s={};switch(i){case _.DISCOVER_EXTENSION:{const a=o;await this.init(a),this.logger.info("[BodhiExtClient] Discovery successful:",{extensionId:this.extensionId,environment:y}),s={extensionId:this.extensionId,environment:y};break}case _.SET_EXTENSION_ID:{const{extensionId:a}=o;this.extensionId=a,this.state="ready",this.logger.info("[BodhiExtClient] Extension ID set:",{extensionId:a}),s={success:!0};break}case _.GET_EXTENSION_ID:{const a=await this.sendExtRequestRaw(g.EXT_ACTIONS.GET_EXTENSION_ID,o);return g.isExtError(a.response)?{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:a.response.error.message||`Extension request failed to get extension ID: ${JSON.stringify(a.response)}`,type:a.response.error.type}}}:{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:a.response}}case _.LOGIN:{const a=o;await this.login(a),this.broadcastAuthStateChange();break}case _.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case _.GET_AUTH_STATE:s={authState:await this.getAuthState()};break;default:return{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown action: ${i}`,type:"UNKNOWN_ACTION"}}}}return{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:s}}catch(s){return this.logger.error("[BodhiExtClient] Unexpected error:",s),{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:s instanceof Error?s.message:`Unexpected error: ${JSON.stringify(s)}`}}}}}async handleAction(e){switch(e.type){case h.EXT2EXT_CLIENT_API_REQUEST:return this.handleApiRequest(e);case h.EXT2EXT_CLIENT_REQUEST:return this.handleExtClientRequest(e);default:{const{requestId:t}=e;return this.logger.error("[BodhiExtClient] Unknown message type:",e.type),{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown message type: ${e.type}`,type:"UNKNOWN_MESSAGE_TYPE"}}}}}}async login(e){if(!(this.isAuthenticating||(await this.getAuthState()).status==="authenticated")){this.isAuthenticating=!0;try{if(!this.extensionId)throw new Error("Extension not discovered. Please detect Bodhi extension before login.");e?.flowType==="redirect"&&this.logger.warn("Extension mode does not support redirect flow type; using popup instead");const r=e?.userRole??"scope_user_user",i=new n.AccessRequestBuilder(this.authClientId).requestedRole(r).flowType("popup");e?.requested&&i.requested(e.requested);const o=i.build(),s=await this.requestAccess(o),{id:a,review_url:E}=n.unwrapResponse(s);await chrome.tabs.create({url:E});const c=await this.pollAccessRequestStatus(a,{intervalMs:e?.pollIntervalMs??n.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??n.DEFAULT_POLL_TIMEOUT_MS});if(c.status!=="approved")throw n.createOperationError("auth_error",`Access request ${c.status}`);const d=`openid profile email roles ${c.access_request_scope??""}`.trim();await this.performOAuthPkce(d)}finally{this.isAuthenticating=!1}}}async exchangeCodeForTokens(e){if((await this.getAuthState()).status==="authenticated")return;const{codeVerifier:r}=await chrome.storage.session.get("codeVerifier"),i=chrome.identity.getRedirectURL("callback"),o=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:i,client_id:this.authClientId,code_verifier:r})});if(!o.ok){const a=await o.text();throw new Error(`Token exchange failed: ${o.status} ${a}`)}const s=await o.json();await this.storeTokens({accessToken:s.access_token,refreshToken:s.refresh_token,idToken:s.id_token,expiresIn:s.expires_in})}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{const t=this.parseJwt(e);return{status:"authenticated",user:{sub:t.sub,email:t.email,name:t.name,given_name:t.given_name,family_name:t.family_name,preferred_username:t.preferred_username},accessToken:e,error:null}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null}}}async logout(){const{refreshToken:e}=await chrome.storage.session.get("refreshToken");if(e)try{await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"})})}catch(t){this.logger.warn("[OAuth] Token revocation failed:",t)}await this.clearTokens()}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e)}async getAccessRequestStatus(e){return this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`)}async pollAccessRequestStatus(e,t){return n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}async performOAuthPkce(e){const t=p.generateCodeVerifier(),r=await p.generateCodeChallenge(t),i=p.generateCodeVerifier();await chrome.storage.session.set({codeVerifier:t,state:i,authInProgress:!0});const o=chrome.identity.getRedirectURL("callback"),s=new URL(this.authEndpoints.authorize);return s.searchParams.set("client_id",this.authClientId),s.searchParams.set("response_type","code"),s.searchParams.set("redirect_uri",o),s.searchParams.set("scope",e),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),s.searchParams.set("state",i),new Promise((a,E)=>{chrome.identity.launchWebAuthFlow({url:s.toString(),interactive:!0},async c=>{if(await chrome.storage.session.set({authInProgress:!1}),chrome.runtime.lastError){await chrome.storage.session.remove(["codeVerifier","state"]),E(chrome.runtime.lastError);return}if(!c){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","No redirect URL received"));return}try{const u=new URL(c),d=u.searchParams.get("code"),T=u.searchParams.get("state"),{state:m}=await chrome.storage.session.get("state");if(T!==m){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","State mismatch"));return}if(!d){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","No authorization code"));return}await this.exchangeCodeForTokens(d),await chrome.storage.session.remove(["codeVerifier","state"]);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("oauth_error","Login failed");a(l)}catch(u){await chrome.storage.session.remove(["codeVerifier","state"]),E(u)}})})}async sendExtRequest(e,t){const r=await this.sendExtRequestRaw(e,t);if(g.isExtError(r.response))throw this.logger.error("[BodhiExtClient] Extension error:",r.response.error),new Error(r.response.error.message||`Extension request failed: ${JSON.stringify(r.response)}`);return r.response}async sendApiRequest(e,t,r,i){if(!this.extensionId)throw new n.BodhiError("not_initialized",this.createErrorClientNotInitialized({type:"api",method:e,endpoint:t}));this.logger.debug(`[BodhiExtClient] Sending API_REQUEST: method=${e}, endpoint=${t}`,r?{body:r}:"");const o=crypto.randomUUID(),s={type:g.MESSAGE_TYPES.API_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i}};return this.logger.debug(`[BodhiExtClient] Request ID: ${o}, Extension: ${this.extensionId}`),new Promise((a,E)=>{try{chrome.runtime.sendMessage(this.extensionId,s,c=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${o}:`,chrome.runtime.lastError),E(new n.BodhiError("extension_error",chrome.runtime.lastError.message??"Chrome runtime error"));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${o}:`,c),!c){this.logger.error(`[BodhiExtClient] No response received for request ${o}`),E(new n.BodhiError("extension_error","No response from extension"));return}c.type===g.MESSAGE_TYPES.API_RESPONSE&&c.requestId===o?"error"in c?(this.logger.error(`[BodhiExtClient] API error for ${o}:`,c.error),E(new n.BodhiError(c.error.type||"extension_error",c.error.message))):(this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${o}`),a(c.response)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${o}:`,c),E(new n.BodhiError("extension_error","Invalid response format")))})}catch(c){this.logger.error(`[BodhiExtClient] Exception sending message for ${o}:`,c),E(c)}})}async sendExtRequestRaw(e,t){if(!this.extensionId)throw new n.BodhiError("not_initialized",this.createErrorClientNotInitialized({type:"ext",action:e,params:t}));this.logger.debug(`[BodhiExtClient] Sending EXT_REQUEST (raw): action=${e}`,t?{params:t}:"");const r=crypto.randomUUID(),i={type:g.MESSAGE_TYPES.EXT_REQUEST,requestId:r,request:{action:e,params:t}};return this.logger.debug(`[BodhiExtClient] Request ID: ${r}, Extension: ${this.extensionId}`),new Promise((o,s)=>{try{chrome.runtime.sendMessage(this.extensionId,i,a=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${r}:`,chrome.runtime.lastError),s(new Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${r}:`,a),!a){this.logger.error(`[BodhiExtClient] No response received for request ${r}`),s(new Error("No response from extension"));return}a.type===g.MESSAGE_TYPES.EXT_RESPONSE&&a.requestId===r?(this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`),o(a)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${r}:`,a),s(new n.BodhiError("extension_error","Invalid response format")))})}catch(a){this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`,a),s(a)}})}async handleStreamRequest(e,t){const{requestId:r,request:i}=t,{method:o,endpoint:s,body:a,headers:E,authenticated:c}=i;this.logger.debug("[BodhiExtClient] Processing stream request:",{requestId:r,method:o,endpoint:s,authenticated:c}),this.extensionId||e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:this.createErrorClientNotInitialized(t),type:"extension_error"}});try{let u={...E};if(c!==!1){const l=await this._getAccessTokenRaw();if(!l){e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Not authenticated. Please log in first.",type:"extension_error"}});return}u={...u,Authorization:`Bearer ${l}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const d=chrome.runtime.connect(this.extensionId,{name:g.BODHI_STREAM_PORT});this.activeStreamPorts.set(r,d);const T=setTimeout(()=>{this.activeStreamPorts.has(r)&&(this.logger.error(`[BodhiExtClient] Stream timeout for ${r}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Stream request timed out",type:"timeout_error"}}),this.cleanupStreamPort(r))},p.STREAM_TIMEOUT);d.onMessage.addListener(l=>{if(g.isStreamChunk(l)){const I=l.response,O=I.body;I.status>=400?e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:I}):O?.done?(e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_DONE,requestId:r}),this.logger.info(`[BodhiExtClient] Stream complete for ${r}`),clearTimeout(T),this.cleanupStreamPort(r)):e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_CHUNK,requestId:r,response:I})}else g.isStreamApiError(l)?(this.logger.error(`[BodhiExtClient] Stream API error for ${r}: ${l.response.status}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:l.response})):g.isStreamError(l)&&(this.logger.error(`[BodhiExtClient] Stream error for ${r}:`,l.error.message),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:`stream error: ${JSON.stringify(l)}`,type:"extension_error"}}),clearTimeout(T),this.cleanupStreamPort(r))}),d.onDisconnect.addListener(()=>{clearTimeout(T),this.activeStreamPorts.has(r)&&(this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${r}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Connection to Bodhi extension closed unexpectedly",type:"network_error"}}),this.activeStreamPorts.delete(r))});const m={type:g.MESSAGE_TYPES.STREAM_REQUEST,requestId:r,request:{method:o,endpoint:s,body:a,headers:u}};this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",m),d.postMessage(m)}catch(u){const d=u;this.logger.error("[BodhiExtClient] Stream error:",JSON.stringify(d.message)),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:`uncaught error: ${JSON.stringify({error:d,message:d.message})}`,type:"extension_error"}})}}cleanupStreamPort(e){const t=this.activeStreamPorts.get(e);if(t){try{t.disconnect()}catch{}this.activeStreamPorts.delete(e)}}async storeTokens(e){const t=Date.now()+(e.expiresIn||3600)*1e3;await chrome.storage.session.set({accessToken:e.accessToken,refreshToken:e.refreshToken,idToken:e.idToken,expiresAt:t})}async _getAccessTokenRaw(){const{accessToken:e,expiresAt:t}=await chrome.storage.session.get(["accessToken","expiresAt"]);if(!e||!t)return null;if(Date.now()>=t-5*1e3){const{refreshToken:r}=await chrome.storage.session.get("refreshToken");return r?this._tryRefreshToken(r):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 n.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success)return await this._storeRefreshedTokens(t.tokens),this.logger.info("Token refreshed successfully"),this.broadcastAuthStateChange(),t.tokens.access_token;if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),await this.clearTokens(),this.broadcastAuthStateChange(),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),n.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}async _storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3,r={accessToken:e.access_token,expiresAt:t};e.refresh_token&&(r.refreshToken=e.refresh_token),e.id_token&&(r.idToken=e.id_token),await chrome.storage.session.set(r)}async clearTokens(){await chrome.storage.session.remove(["accessToken","refreshToken","idToken","expiresAt","codeVerifier","state","authInProgress","bodhiUserInfo"])}parseJwt(e){const r=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),i=decodeURIComponent(atob(r).split("").map(o=>"%"+("00"+o.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(i)}createErrorClientNotInitialized(e){return`Client not initialized. Extension discovery not triggered nor extensionId set, cannot handle request: ${JSON.stringify(e)}`}};p.STREAM_TIMEOUT=6e4;let R=p;const k="production";exports.BodhiExtClient=R;exports.DEFAULT_API_TIMEOUT_MS=N;exports.DISCOVERY_ATTEMPTS=C;exports.DISCOVERY_ATTEMPT_TIMEOUT=A;exports.DISCOVERY_ATTEMPT_WAIT_MS=w;exports.DISCOVERY_TIMEOUT_MS=x;exports.EXT2EXT_CLIENT_ACTIONS=_;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=h;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT_BUILD_MODE=k;exports.ExtUIClient=L;exports.isExtClientApiError=P;
@@ -1,5 +1,5 @@
1
- import { DirectClientBase as M, createStoragePrefixWithBasePath as N, STORAGE_PREFIXES as v, AccessRequestBuilder as P, unwrapResponse as X, DEFAULT_POLL_TIMEOUT_MS as b, DEFAULT_POLL_INTERVAL_MS as O, createOperationError as l, generateCodeVerifier as C, generateCodeChallenge as U, PENDING_EXTENSION_READY as R, Logger as w, NOOP_STATE_CALLBACK as D, createExtensionStateNotInitialized as $, BACKEND_SERVER_NOT_REACHABLE as B, createExtensionStateNotFound as K, BodhiError as T, INITIAL_AUTH_STATE as V, BodhiApiError as z, Chat as F, Models as G, Embeddings as Q, Toolsets as H, Mcps as W, pollAccessRequestUntilResolved as L, isAuthError as J, BaseFacadeClient as Y, refreshAccessToken as Z } from "@bodhiapp/bodhi-js-core";
2
- import { isExtError as x, isApiSuccessResponse as j, MESSAGE_TYPES as _, EXT_ACTIONS as ee, BODHI_STREAM_PORT as te, isStreamChunk as re, isStreamApiError as se, isStreamError as oe } from "@bodhiapp/bodhi-browser-types";
1
+ import { DirectClientBase as M, createStoragePrefixWithBasePath as N, STORAGE_PREFIXES as v, AccessRequestBuilder as P, unwrapResponse as X, DEFAULT_POLL_TIMEOUT_MS as b, DEFAULT_POLL_INTERVAL_MS as O, createOperationError as l, generateCodeVerifier as C, generateCodeChallenge as U, PENDING_EXTENSION_READY as f, Logger as w, NOOP_STATE_CALLBACK as D, createExtensionStateNotInitialized as $, BACKEND_SERVER_NOT_REACHABLE as B, createExtensionStateNotFound as K, BodhiError as T, INITIAL_AUTH_STATE as V, BodhiApiError as z, Chat as F, Models as G, Embeddings as Q, Mcps as H, pollAccessRequestUntilResolved as L, isAuthError as W, BaseFacadeClient as J, refreshAccessToken as Y } from "@bodhiapp/bodhi-js-core";
2
+ import { isExtError as x, isApiSuccessResponse as Z, MESSAGE_TYPES as _, EXT_ACTIONS as j, BODHI_STREAM_PORT as ee, isStreamChunk as te, isStreamApiError as re, isStreamError as se } from "@bodhiapp/bodhi-browser-types";
3
3
  const c = {
4
4
  EXT2EXT_CLIENT_REQUEST: "EXT2EXT_CLIENT_REQUEST",
5
5
  EXT2EXT_CLIENT_RESPONSE: "EXT2EXT_CLIENT_RESPONSE",
@@ -19,11 +19,11 @@ const c = {
19
19
  DISCOVER_EXTENSION: "discoverBodhiExtension",
20
20
  GET_EXTENSION_ID: "get_extension_id",
21
21
  SET_EXTENSION_ID: "setExtensionId"
22
- }, ie = 5e3, ne = 3, ae = 500, ce = 500, he = 3e4;
23
- function ue(y) {
22
+ }, oe = 5e3, ie = 3, ne = 500, ae = 500, ce = 3e4;
23
+ function he(y) {
24
24
  return "error" in y;
25
25
  }
26
- class de extends M {
26
+ class ue extends M {
27
27
  constructor(e, t) {
28
28
  const r = N(
29
29
  e.basePath,
@@ -31,7 +31,6 @@ class de extends M {
31
31
  ), i = {
32
32
  authClientId: e.authClientId,
33
33
  authServerUrl: e.authServerUrl,
34
- userRole: e.userRole,
35
34
  storagePrefix: r,
36
35
  logLevel: e.logLevel,
37
36
  loggerPrefix: "DirectExtClient",
@@ -47,7 +46,7 @@ class de extends M {
47
46
  if (t.status === "authenticated")
48
47
  return t;
49
48
  e?.flowType === "redirect" && this.logger.warn("Extension mode does not support redirect flow type; using popup instead");
50
- const r = e?.userRole ?? this.userRole;
49
+ const r = e?.userRole ?? "scope_user_user";
51
50
  e?.onProgress?.("requesting");
52
51
  const i = new P(this.authClientId).requestedRole(r).flowType("popup");
53
52
  e?.requested && i.requested(e.requested);
@@ -198,14 +197,14 @@ class de extends M {
198
197
  return chrome.identity.getRedirectURL("callback");
199
198
  }
200
199
  }
201
- class Ee {
200
+ class de {
202
201
  constructor(e = {}, t) {
203
202
  this.state = {
204
203
  type: "extension",
205
204
  extension: "not-initialized",
206
205
  extensionId: null,
207
- server: R
208
- }, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new w("ExtClient", e?.logLevel || "warn"), this.onStateChange = t ?? D, this.apiTimeoutMs = e.apiTimeoutMs ?? he, this.authClientId = e.authClientId ?? "";
206
+ server: f
207
+ }, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new w("ExtClient", e?.logLevel || "warn"), this.onStateChange = t ?? D, this.apiTimeoutMs = e.apiTimeoutMs ?? ce, this.authClientId = e.authClientId ?? "";
209
208
  }
210
209
  /**
211
210
  * Set client state and notify callback
@@ -278,7 +277,7 @@ class Ee {
278
277
  }
279
278
  if (this.extensionId && !e.testConnection)
280
279
  return this.logger.debug("Already initialized with extensionId, skipping discovery"), this.state;
281
- const t = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? ie, r = e.savedState?.extensionId;
280
+ const t = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? oe, r = e.savedState?.extensionId;
282
281
  try {
283
282
  if (!this.extensionId) {
284
283
  if (r)
@@ -306,9 +305,9 @@ class Ee {
306
305
  type: "extension",
307
306
  extension: "ready",
308
307
  extensionId: this.extensionId,
309
- server: R
308
+ server: f
310
309
  };
311
- let o = R;
310
+ let o = f;
312
311
  if (e.testConnection)
313
312
  try {
314
313
  o = await this.getServerState(), this.logger.info("Server connectivity tested, state:", o.status);
@@ -402,7 +401,7 @@ class Ee {
402
401
  this.sendRawApiMessage(e, t, r, i, o),
403
402
  s
404
403
  ]);
405
- if (ue(n)) {
404
+ if (he(n)) {
406
405
  const h = n.error.type || "extension_error";
407
406
  throw new T(h, n.error.message);
408
407
  }
@@ -416,7 +415,7 @@ class Ee {
416
415
  }
417
416
  /**
418
417
  * Login user via OAuth
419
- * @param options - Optional login parameters including toolsetScopeIds
418
+ * @param options - Optional login parameters
420
419
  * @throws ExtError if login fails
421
420
  * @returns AuthState with login state and user info
422
421
  */
@@ -427,7 +426,7 @@ class Ee {
427
426
  chrome.runtime.onMessage.removeListener(i);
428
427
  try {
429
428
  const s = await this.getAuthState();
430
- if (J(s)) {
429
+ if (W(s)) {
431
430
  r(
432
431
  l("auth_error", `Login failed: ${s.error?.message}`)
433
432
  );
@@ -582,7 +581,7 @@ class Ee {
582
581
  }
583
582
  case c.EXT2EXT_CLIENT_STREAM_CHUNK: {
584
583
  const g = d;
585
- j(g.response) && u.enqueue(g.response.body);
584
+ Z(g.response) && u.enqueue(g.response.body);
586
585
  break;
587
586
  }
588
587
  }
@@ -624,11 +623,8 @@ class Ee {
624
623
  get embeddings() {
625
624
  return this._embeddings ??= new Q(this);
626
625
  }
627
- get toolsets() {
628
- return this._toolsets ??= new H(this);
629
- }
630
626
  get mcps() {
631
- return this._mcps ??= new W(this);
627
+ return this._mcps ??= new H(this);
632
628
  }
633
629
  // ============================================================================
634
630
  // Access Request Methods
@@ -675,12 +671,11 @@ class Ee {
675
671
  };
676
672
  }
677
673
  }
678
- class pe extends Y {
674
+ class me extends J {
679
675
  constructor(e, t, r) {
680
676
  const i = t || {}, o = {
681
677
  basePath: i.basePath || "/",
682
678
  authServerUrl: i.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
683
- userRole: i.userRole || "scope_user_user",
684
679
  logLevel: i.logLevel || "warn",
685
680
  apiTimeoutMs: i.apiTimeoutMs,
686
681
  initParams: i.initParams
@@ -694,7 +689,7 @@ class pe extends Y {
694
689
  return N(e.basePath, v.EXT);
695
690
  }
696
691
  createExtClient(e, t) {
697
- return new Ee(
692
+ return new de(
698
693
  {
699
694
  authClientId: this.authClientId,
700
695
  logLevel: e.logLevel,
@@ -705,11 +700,10 @@ class pe extends Y {
705
700
  );
706
701
  }
707
702
  createDirectClient(e, t, r) {
708
- return new de(
703
+ return new ue(
709
704
  {
710
705
  authClientId: e,
711
706
  authServerUrl: t.authServerUrl,
712
- userRole: t.userRole,
713
707
  logLevel: t.logLevel,
714
708
  basePath: t.basePath,
715
709
  apiTimeoutMs: t.apiTimeoutMs
@@ -718,12 +712,12 @@ class pe extends Y {
718
712
  );
719
713
  }
720
714
  }
721
- const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckljjghmjbf"], f = "production", p = class p {
715
+ const Ee = ["ggedphdcbekjlomjaidbajglgihbeaon"], le = ["bjdjhiombmfbcoeojijpfckljjghmjbf"], R = "production", p = class p {
722
716
  // ============================================================================
723
717
  // Constructor
724
718
  // ============================================================================
725
719
  constructor(e, t) {
726
- this.isAuthenticating = !1, this.state = "setup", this.listenersInitialized = !1, this.refreshPromise = null, this.activeStreamPorts = /* @__PURE__ */ new Map(), this.authClientId = e, this.authServerUrl = t?.authServerUrl || "https://id.getbodhi.app/realms/bodhi", this.userRole = t?.userRole || "scope_user_user", this.extensionId = t?.extensionId, this.logger = new w("BodhiExtClient", t?.logLevel || "warn"), this.attempts = t?.attempts ?? ne, this.attemptWaitMs = t?.attemptWaitMs ?? ae, this.attemptTimeout = t?.attemptTimeout ?? ce, this.authEndpoints = {
720
+ this.isAuthenticating = !1, this.state = "setup", this.listenersInitialized = !1, this.refreshPromise = null, this.activeStreamPorts = /* @__PURE__ */ new Map(), this.authClientId = e, this.authServerUrl = t?.authServerUrl || "https://id.getbodhi.app/realms/bodhi", this.extensionId = t?.extensionId, this.logger = new w("BodhiExtClient", t?.logLevel || "warn"), this.attempts = t?.attempts ?? ie, this.attemptWaitMs = t?.attemptWaitMs ?? ne, this.attemptTimeout = t?.attemptTimeout ?? ae, this.authEndpoints = {
727
721
  authorize: `${this.authServerUrl}/protocol/openid-connect/auth`,
728
722
  token: `${this.authServerUrl}/protocol/openid-connect/token`,
729
723
  userinfo: `${this.authServerUrl}/protocol/openid-connect/userinfo`,
@@ -764,7 +758,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
764
758
  * Get extension IDs for current environment
765
759
  */
766
760
  getExtensionIdsForEnvironment() {
767
- const t = f !== "production" ? le : ge;
761
+ const t = R !== "production" ? Ee : le;
768
762
  return this.logger.info("[Ext2Ext/Registry] Environment: production"), this.logger.debug("[Ext2Ext/Registry] Using extension IDs:", t), t;
769
763
  }
770
764
  /**
@@ -1012,10 +1006,10 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
1012
1006
  const n = o;
1013
1007
  await this.init(n), this.logger.info("[BodhiExtClient] Discovery successful:", {
1014
1008
  extensionId: this.extensionId,
1015
- environment: f
1009
+ environment: R
1016
1010
  }), s = {
1017
1011
  extensionId: this.extensionId,
1018
- environment: f
1012
+ environment: R
1019
1013
  };
1020
1014
  break;
1021
1015
  }
@@ -1025,7 +1019,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
1025
1019
  break;
1026
1020
  }
1027
1021
  case m.GET_EXTENSION_ID: {
1028
- const n = await this.sendExtRequestRaw(ee.GET_EXTENSION_ID, o);
1022
+ const n = await this.sendExtRequestRaw(j.GET_EXTENSION_ID, o);
1029
1023
  return x(n.response) ? {
1030
1024
  type: c.EXT2EXT_CLIENT_RESPONSE,
1031
1025
  requestId: t,
@@ -1115,7 +1109,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
1115
1109
  if (!this.extensionId)
1116
1110
  throw new Error("Extension not discovered. Please detect Bodhi extension before login.");
1117
1111
  e?.flowType === "redirect" && this.logger.warn("Extension mode does not support redirect flow type; using popup instead");
1118
- const r = e?.userRole ?? this.userRole, i = new P(this.authClientId).requestedRole(r).flowType("popup");
1112
+ const r = e?.userRole ?? "scope_user_user", i = new P(this.authClientId).requestedRole(r).flowType("popup");
1119
1113
  e?.requested && i.requested(e.requested);
1120
1114
  const o = i.build(), s = await this.requestAccess(o), { id: n, review_url: h } = X(s);
1121
1115
  await chrome.tabs.create({ url: h });
@@ -1445,7 +1439,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
1445
1439
  }, this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request");
1446
1440
  }
1447
1441
  const d = chrome.runtime.connect(this.extensionId, {
1448
- name: te
1442
+ name: ee
1449
1443
  });
1450
1444
  this.activeStreamPorts.set(r, d);
1451
1445
  const g = setTimeout(() => {
@@ -1459,7 +1453,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
1459
1453
  }), this.cleanupStreamPort(r));
1460
1454
  }, p.STREAM_TIMEOUT);
1461
1455
  d.onMessage.addListener((E) => {
1462
- if (re(E)) {
1456
+ if (te(E)) {
1463
1457
  const I = E.response, q = I.body;
1464
1458
  I.status >= 400 ? e.postMessage({
1465
1459
  type: c.EXT2EXT_CLIENT_STREAM_API_ERROR,
@@ -1473,13 +1467,13 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
1473
1467
  requestId: r,
1474
1468
  response: I
1475
1469
  });
1476
- } else se(E) ? (this.logger.error(
1470
+ } else re(E) ? (this.logger.error(
1477
1471
  `[BodhiExtClient] Stream API error for ${r}: ${E.response.status}`
1478
1472
  ), e.postMessage({
1479
1473
  type: c.EXT2EXT_CLIENT_STREAM_API_ERROR,
1480
1474
  requestId: r,
1481
1475
  response: E.response
1482
- })) : oe(E) && (this.logger.error(
1476
+ })) : se(E) && (this.logger.error(
1483
1477
  `[BodhiExtClient] Stream error for ${r}:`,
1484
1478
  E.error.message
1485
1479
  ), e.postMessage({
@@ -1581,7 +1575,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
1581
1575
  async _doRefreshToken(e) {
1582
1576
  this.logger.debug("Refreshing access token");
1583
1577
  try {
1584
- const t = await Z(
1578
+ const t = await Y(
1585
1579
  this.authEndpoints.token,
1586
1580
  e,
1587
1581
  this.authClientId
@@ -1632,18 +1626,18 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
1632
1626
  };
1633
1627
  p.STREAM_TIMEOUT = 6e4;
1634
1628
  let A = p;
1635
- const _e = "production";
1629
+ const pe = "production";
1636
1630
  export {
1637
1631
  A as BodhiExtClient,
1638
- he as DEFAULT_API_TIMEOUT_MS,
1639
- ne as DISCOVERY_ATTEMPTS,
1640
- ce as DISCOVERY_ATTEMPT_TIMEOUT,
1641
- ae as DISCOVERY_ATTEMPT_WAIT_MS,
1642
- ie as DISCOVERY_TIMEOUT_MS,
1632
+ ce as DEFAULT_API_TIMEOUT_MS,
1633
+ ie as DISCOVERY_ATTEMPTS,
1634
+ ae as DISCOVERY_ATTEMPT_TIMEOUT,
1635
+ ne as DISCOVERY_ATTEMPT_WAIT_MS,
1636
+ oe as DISCOVERY_TIMEOUT_MS,
1643
1637
  m as EXT2EXT_CLIENT_ACTIONS,
1644
1638
  c as EXT2EXT_CLIENT_MESSAGE_TYPES,
1645
1639
  k as EXT2EXT_CLIENT_STREAM_PORT,
1646
- _e as EXT_BUILD_MODE,
1647
- pe as ExtUIClient,
1648
- ue as isExtClientApiError
1640
+ pe as EXT_BUILD_MODE,
1641
+ me as ExtUIClient,
1642
+ he as isExtClientApiError
1649
1643
  };
@@ -1,12 +1,10 @@
1
1
  import { DirectClientBase, AuthState, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
2
- import { UserScope } from '@bodhiapp/ts-client';
3
2
  /**
4
3
  * Configuration for DirectExtClient
5
4
  */
6
5
  export interface DirectExtClientConfig {
7
6
  authClientId: string;
8
7
  authServerUrl: string;
9
- userRole: UserScope;
10
8
  basePath: string;
11
9
  logLevel: LogLevel;
12
10
  apiTimeoutMs?: number;
@@ -1,5 +1,5 @@
1
1
  import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, PingResponse } from '@bodhiapp/ts-client';
2
- import { IExtensionClient, Chat, Models, Embeddings, Toolsets, Mcps, AuthState, BackendServerState, ClientState, ExtensionState, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
2
+ import { IExtensionClient, Chat, Models, Embeddings, Mcps, AuthState, BackendServerState, ClientState, ExtensionState, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
3
3
  import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
4
4
  export type SerializedExt2ExtState = {
5
5
  extensionId?: string;
@@ -41,7 +41,6 @@ export declare class ExtClient implements IExtensionClient {
41
41
  private _chat;
42
42
  private _models;
43
43
  private _embeddings;
44
- private _toolsets;
45
44
  private _mcps;
46
45
  constructor(config?: ExtClientConfig, onStateChange?: StateChangeCallback);
47
46
  /**
@@ -106,7 +105,7 @@ export declare class ExtClient implements IExtensionClient {
106
105
  sendApiRequest<TReq = void, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): Promise<ApiResponse<TRes>>;
107
106
  /**
108
107
  * Login user via OAuth
109
- * @param options - Optional login parameters including toolsetScopeIds
108
+ * @param options - Optional login parameters
110
109
  * @throws ExtError if login fails
111
110
  * @returns AuthState with login state and user info
112
111
  */
@@ -139,7 +138,6 @@ export declare class ExtClient implements IExtensionClient {
139
138
  get chat(): Chat;
140
139
  get models(): Models;
141
140
  get embeddings(): Embeddings;
142
- get toolsets(): Toolsets;
143
141
  get mcps(): Mcps;
144
142
  requestAccess(body: CreateAccessRequest): Promise<ApiResponse<CreateAccessRequestResponse>>;
145
143
  getAccessRequestStatus(requestId: string): Promise<ApiResponse<AccessRequestStatusResponse>>;
@@ -1,13 +1,12 @@
1
1
  import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse } from '@bodhiapp/ts-client';
2
2
  import { ExtClientApiRequestMessage, ExtClientApiResponseMessage, ExtClientRequestMessage, ExtClientResponseMessage } from './messages';
3
3
  import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
4
- import { AuthState, LoginOptions, LogLevel, UserScope } from '@bodhiapp/bodhi-js-core';
4
+ import { AuthState, LoginOptions, LogLevel } from '@bodhiapp/bodhi-js-core';
5
5
  export type ClientExtState = 'setup' | 'ready' | 'error';
6
6
  export interface BodhiExtClientConfig {
7
7
  authServerUrl?: string;
8
8
  extensionId?: string;
9
9
  logLevel?: LogLevel;
10
- userRole?: UserScope;
11
10
  attempts?: number;
12
11
  attemptWaitMs?: number;
13
12
  attemptTimeout?: number;
@@ -20,7 +19,6 @@ export declare class BodhiExtClient {
20
19
  private isAuthenticating;
21
20
  private authClientId;
22
21
  private authServerUrl;
23
- private userRole;
24
22
  private logger;
25
23
  private state;
26
24
  private listenersInitialized;
@@ -1,4 +1,4 @@
1
- import { BaseFacadeClient, Logger, LogLevel, StateChange, StateChangeCallback, UIClient, UserScope } from '@bodhiapp/bodhi-js-core';
1
+ import { BaseFacadeClient, Logger, LogLevel, StateChange, StateChangeCallback, UIClient } from '@bodhiapp/bodhi-js-core';
2
2
  import { DirectExtClient } from './direct-client';
3
3
  import { ExtClient } from './ext-client';
4
4
  /**
@@ -7,7 +7,6 @@ import { ExtClient } from './ext-client';
7
7
  */
8
8
  export interface ExtUIClientConfig {
9
9
  authServerUrl: string;
10
- userRole: UserScope;
11
10
  basePath: string;
12
11
  logLevel: LogLevel;
13
12
  apiTimeoutMs?: number;
@@ -26,7 +25,6 @@ export interface ExtUIClientConfig {
26
25
  */
27
26
  export interface ExtUIClientParams {
28
27
  authServerUrl?: string;
29
- userRole?: UserScope;
30
28
  basePath?: string;
31
29
  logLevel?: LogLevel;
32
30
  apiTimeoutMs?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js-ext",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "description": "Extension SDK for Bodhi Browser - chrome.runtime communication",
5
5
  "type": "module",
6
6
  "main": "dist/bodhi-ext.cjs.js",
@@ -37,9 +37,9 @@
37
37
  "typecheck": "tsc --noEmit"
38
38
  },
39
39
  "dependencies": {
40
- "@bodhiapp/bodhi-browser-types": "0.0.29",
41
- "@bodhiapp/bodhi-js-core": "0.0.29",
42
- "@bodhiapp/ts-client": "0.1.23"
40
+ "@bodhiapp/bodhi-browser-types": "0.0.30",
41
+ "@bodhiapp/bodhi-js-core": "0.0.30",
42
+ "@bodhiapp/ts-client": "0.1.24"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@eslint/js": "^9.23.0",