@bodhiapp/bodhi-js-ext 0.0.27 → 0.0.29

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",p={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 O(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);if(n.isApiResultOperationError(s))throw n.createOperationError(s.error.message,s.error.type);if(!n.isApiResultSuccess(s))throw n.createOperationError(`Access request failed: HTTP ${s.status}`,"auth_error");const{id:a,review_url:E}=s.body;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(`Access request ${c.status}`,"auth_error");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("No redirect URL received","oauth-error"));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("State mismatch","oauth-error"));return}if(!d){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("No authorization code","oauth-error"));return}await this.exchangeCodeForTokens(d);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("Login failed","oauth-error");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(p.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(p.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("No response from background script","extension_error");if(i.type!==h.EXT2EXT_CLIENT_RESPONSE)throw n.createOperationError("Invalid response type from background script","extension_error");const o=i.response;if(g.isExtError(o)){const s=o.error.type||"extension_error";throw n.createOperationError(o.error.message,s)}return o}catch(r){throw g.isOperationError(r)?r:n.createOperationError(r instanceof Error?r.message:"Unknown error occurred","extension_error")}}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(O(a)){const E=a.error.type||"extension_error";return{error:{message:a.error.message,type:E}}}return a.response}catch(s){return{error:{message:s instanceof Error?s.message:String(s),type:"network_error"}}}}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(`Login failed: ${s.error?.message}`,"auth-error"));return}if(s.status!=="authenticated"){r(n.createOperationError("Login failed: User is not logged in","auth-error"));return}this.setAuthState(s),t(s)}catch(s){r(s)}}};chrome.runtime.onMessage.addListener(i),this.sendExtRequest(p.LOGIN,e).catch(o=>{chrome.runtime.onMessage.removeListener(i),r(o)})})}async logout(){await this.sendExtRequest(p.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(p.GET_AUTH_STATE)).authState:n.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(n.isApiResultOperationError(e))return{status:"not-reachable",version:null,error:e.error};if(!n.isApiResultSuccess(e))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"tenant_selection":return{status:"tenant_selection",version:r,error:null,...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(n.createOperationError(d.error.message,"extension_error")),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(n.createApiError(T.response.body?.error?.message||"API error",T.response.status,T.response.body)),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(n.createOperationError("Connection closed unexpectedly","extension_error"))}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 b 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 L=["ggedphdcbekjlomjaidbajglgihbeaon"],M=["bjdjhiombmfbcoeojijpfckljjghmjbf"],y="production",_=class _{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),_.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",r);return _.base64UrlEncode(i)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=y!=="production"?L: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===p.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 p.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 p.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 p.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 p.LOGIN:{const a=o;await this.login(a),this.broadcastAuthStateChange();break}case p.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case p.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);if(n.isApiResultOperationError(s))throw n.createOperationError(s.error.message,s.error.type);if(!n.isApiResultSuccess(s))throw n.createOperationError(`Access request failed: HTTP ${s.status}`,"auth_error");const{id:a,review_url:E}=s.body;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(`Access request ${c.status}`,"auth_error");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){try{const t=await this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e);return{body:t.body,status:t.status,headers:t.headers}}catch(t){return{error:{message:t instanceof Error?t.message:String(t),type:"extension_error"}}}}async getAccessRequestStatus(e){try{const t=await this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`);return{body:t.body,status:t.status,headers:t.headers}}catch(t){return{error:{message:t instanceof Error?t.message:String(t),type:"extension_error"}}}}async pollAccessRequestStatus(e,t){return n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}async performOAuthPkce(e){const t=_.generateCodeVerifier(),r=await _.generateCodeChallenge(t),i=_.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("No redirect URL received","oauth-error"));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("State mismatch","oauth-error"));return}if(!d){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("No authorization code","oauth-error"));return}await this.exchangeCodeForTokens(d),await chrome.storage.session.remove(["codeVerifier","state"]);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("Login failed","oauth-error");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 Error(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 Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${o}:`,c),!c){this.logger.error(`[BodhiExtClient] No response received for request ${o}`),E(new 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 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 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 Error(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 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))},_.STREAM_TIMEOUT);d.onMessage.addListener(l=>{if(g.isStreamChunk(l)){const I=l.response,P=I.body;I.status>=400?e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:I}):P?.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("Access token expired and unable to refresh. Try logging out and logging in again.","token_refresh_failed")}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)}`}};_.STREAM_TIMEOUT=6e4;let R=_;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=p;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=h;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT_BUILD_MODE=k;exports.ExtUIClient=b;exports.isExtClientApiError=O;
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,5 +1,5 @@
1
- import { DirectClientBase as M, createStoragePrefixWithBasePath as v, STORAGE_PREFIXES as P, AccessRequestBuilder as X, isApiResultOperationError as x, createOperationError as E, isApiResultSuccess as w, DEFAULT_POLL_TIMEOUT_MS as b, DEFAULT_POLL_INTERVAL_MS as O, generateCodeVerifier as A, generateCodeChallenge as U, PENDING_EXTENSION_READY as I, Logger as C, NOOP_STATE_CALLBACK as D, createExtensionStateNotInitialized as $, BACKEND_SERVER_NOT_REACHABLE as B, createExtensionStateNotFound as K, INITIAL_AUTH_STATE as V, createApiError as F, Chat as z, 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 R, isOperationError as j, isApiSuccessResponse as ee, MESSAGE_TYPES as p, EXT_ACTIONS as te, BODHI_STREAM_PORT as re, isStreamChunk as se, isStreamApiError as oe, isStreamError as ie } 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 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";
3
3
  const c = {
4
4
  EXT2EXT_CLIENT_REQUEST: "EXT2EXT_CLIENT_REQUEST",
5
5
  EXT2EXT_CLIENT_RESPONSE: "EXT2EXT_CLIENT_RESPONSE",
@@ -12,22 +12,22 @@ const c = {
12
12
  EXT2EXT_CLIENT_STREAM_ERROR: "EXT2EXT_CLIENT_STREAM_ERROR",
13
13
  EXT2EXT_CLIENT_STREAM_API_ERROR: "EXT2EXT_CLIENT_STREAM_API_ERROR",
14
14
  EXT2EXT_CLIENT_STREAM_DONE: "EXT2EXT_CLIENT_STREAM_DONE"
15
- }, k = "ext2ext-client-stream", T = {
15
+ }, k = "ext2ext-client-stream", m = {
16
16
  LOGIN: "login",
17
17
  LOGOUT: "logout",
18
18
  GET_AUTH_STATE: "getAuthState",
19
19
  DISCOVER_EXTENSION: "discoverBodhiExtension",
20
20
  GET_EXTENSION_ID: "get_extension_id",
21
21
  SET_EXTENSION_ID: "setExtensionId"
22
- }, ne = 5e3, ae = 3, ce = 500, he = 500, ue = 3e4;
23
- function de(S) {
24
- return "error" in S;
22
+ }, ie = 5e3, ne = 3, ae = 500, ce = 500, he = 3e4;
23
+ function ue(y) {
24
+ return "error" in y;
25
25
  }
26
- class Ee extends M {
26
+ class de extends M {
27
27
  constructor(e, t) {
28
- const r = v(
28
+ const r = N(
29
29
  e.basePath,
30
- P.EXT_DIRECT
30
+ v.EXT_DIRECT
31
31
  ), i = {
32
32
  authClientId: e.authClientId,
33
33
  authServerUrl: e.authServerUrl,
@@ -49,29 +49,21 @@ class Ee extends M {
49
49
  e?.flowType === "redirect" && this.logger.warn("Extension mode does not support redirect flow type; using popup instead");
50
50
  const r = e?.userRole ?? this.userRole;
51
51
  e?.onProgress?.("requesting");
52
- const i = new X(this.authClientId).requestedRole(r).flowType("popup");
52
+ const i = new P(this.authClientId).requestedRole(r).flowType("popup");
53
53
  e?.requested && i.requested(e.requested);
54
- const o = i.build(), s = await this.requestAccess(o);
55
- if (x(s))
56
- throw E(s.error.message, s.error.type);
57
- if (!w(s))
58
- throw E(
59
- `Access request failed: HTTP ${s.status}`,
60
- "auth_error"
61
- );
62
- const { id: n, review_url: h } = s.body;
54
+ const o = i.build(), s = await this.requestAccess(o), { id: n, review_url: h } = X(s);
63
55
  e?.onProgress?.("reviewing"), await chrome.tabs.create({ url: h });
64
56
  const a = await this.pollAccessRequestStatus(n, {
65
57
  intervalMs: e?.pollIntervalMs ?? O,
66
58
  timeoutMs: e?.pollTimeoutMs ?? b
67
59
  });
68
60
  if (a.status !== "approved")
69
- throw E(`Access request ${a.status}`, "auth_error");
61
+ throw l("auth_error", `Access request ${a.status}`);
70
62
  const u = a.access_request_scope;
71
63
  return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${u ?? ""}`.trim());
72
64
  }
73
65
  async performOAuthPkce(e) {
74
- const t = A(), r = await U(t), i = A();
66
+ const t = C(), r = await U(t), i = C();
75
67
  await chrome.storage.session.set({
76
68
  [this.storageKeys.CODE_VERIFIER]: t,
77
69
  [this.storageKeys.STATE]: i
@@ -92,33 +84,33 @@ class Ee extends M {
92
84
  await chrome.storage.session.remove([
93
85
  this.storageKeys.CODE_VERIFIER,
94
86
  this.storageKeys.STATE
95
- ]), h(E("No redirect URL received", "oauth-error"));
87
+ ]), h(l("oauth_error", "No redirect URL received"));
96
88
  return;
97
89
  }
98
90
  try {
99
- const u = new URL(a), d = u.searchParams.get("code"), g = u.searchParams.get("state"), _ = await chrome.storage.session.get(this.storageKeys.STATE);
100
- if (g !== _[this.storageKeys.STATE]) {
91
+ const u = new URL(a), d = u.searchParams.get("code"), g = u.searchParams.get("state"), S = await chrome.storage.session.get(this.storageKeys.STATE);
92
+ if (g !== S[this.storageKeys.STATE]) {
101
93
  await chrome.storage.session.remove([
102
94
  this.storageKeys.CODE_VERIFIER,
103
95
  this.storageKeys.STATE
104
- ]), h(E("State mismatch", "oauth-error"));
96
+ ]), h(l("oauth_error", "State mismatch"));
105
97
  return;
106
98
  }
107
99
  if (!d) {
108
100
  await chrome.storage.session.remove([
109
101
  this.storageKeys.CODE_VERIFIER,
110
102
  this.storageKeys.STATE
111
- ]), h(E("No authorization code", "oauth-error"));
103
+ ]), h(l("oauth_error", "No authorization code"));
112
104
  return;
113
105
  }
114
106
  await this.exchangeCodeForTokens(d);
115
- const l = await this.getAuthState();
116
- if (l.status !== "authenticated")
117
- throw E("Login failed", "oauth-error");
118
- this.setAuthState(l), await chrome.storage.session.remove([
107
+ const E = await this.getAuthState();
108
+ if (E.status !== "authenticated")
109
+ throw l("oauth_error", "Login failed");
110
+ this.setAuthState(E), await chrome.storage.session.remove([
119
111
  this.storageKeys.CODE_VERIFIER,
120
112
  this.storageKeys.STATE
121
- ]), n(l);
113
+ ]), n(E);
122
114
  } catch (u) {
123
115
  await chrome.storage.session.remove([
124
116
  this.storageKeys.CODE_VERIFIER,
@@ -206,14 +198,14 @@ class Ee extends M {
206
198
  return chrome.identity.getRedirectURL("callback");
207
199
  }
208
200
  }
209
- class le {
201
+ class Ee {
210
202
  constructor(e = {}, t) {
211
203
  this.state = {
212
204
  type: "extension",
213
205
  extension: "not-initialized",
214
206
  extensionId: null,
215
- server: I
216
- }, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new C("ExtClient", e?.logLevel || "warn"), this.onStateChange = t ?? D, this.apiTimeoutMs = e.apiTimeoutMs ?? ue, this.authClientId = e.authClientId ?? "";
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 ?? "";
217
209
  }
218
210
  /**
219
211
  * Set client state and notify callback
@@ -286,12 +278,12 @@ class le {
286
278
  }
287
279
  if (this.extensionId && !e.testConnection)
288
280
  return this.logger.debug("Already initialized with extensionId, skipping discovery"), this.state;
289
- const t = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? ne, r = e.savedState?.extensionId;
281
+ const t = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? ie, r = e.savedState?.extensionId;
290
282
  try {
291
283
  if (!this.extensionId) {
292
284
  if (r)
293
285
  this.logger.info("Restoring with known extensionId:", r), await this.sendExtMessageWithTimeout(
294
- T.SET_EXTENSION_ID,
286
+ m.SET_EXTENSION_ID,
295
287
  { extensionId: r },
296
288
  t
297
289
  ), this.extensionId = r;
@@ -302,7 +294,7 @@ class le {
302
294
  attemptWaitMs: this.config.initParams?.extension?.attemptWaitMs,
303
295
  attemptTimeout: this.config.initParams?.extension?.attemptTimeout
304
296
  }, n = await this.sendExtMessageWithTimeout(
305
- T.DISCOVER_EXTENSION,
297
+ m.DISCOVER_EXTENSION,
306
298
  s,
307
299
  t
308
300
  );
@@ -314,9 +306,9 @@ class le {
314
306
  type: "extension",
315
307
  extension: "ready",
316
308
  extensionId: this.extensionId,
317
- server: I
309
+ server: R
318
310
  };
319
- let o = I;
311
+ let o = R;
320
312
  if (e.testConnection)
321
313
  try {
322
314
  o = await this.getServerState(), this.logger.info("Server connectivity tested, state:", o.status);
@@ -354,22 +346,22 @@ class le {
354
346
  }
355
347
  });
356
348
  if (!i)
357
- throw E("No response from background script", "extension_error");
349
+ throw l("extension_error", "No response from background script");
358
350
  if (i.type !== c.EXT2EXT_CLIENT_RESPONSE)
359
- throw E(
360
- "Invalid response type from background script",
361
- "extension_error"
351
+ throw l(
352
+ "extension_error",
353
+ "Invalid response type from background script"
362
354
  );
363
355
  const o = i.response;
364
- if (R(o)) {
356
+ if (x(o)) {
365
357
  const s = o.error.type || "extension_error";
366
- throw E(o.error.message, s);
358
+ throw l(s, o.error.message);
367
359
  }
368
360
  return o;
369
361
  } catch (r) {
370
- throw j(r) ? r : E(
371
- r instanceof Error ? r.message : "Unknown error occurred",
372
- "extension_error"
362
+ throw r instanceof T ? r : l(
363
+ "extension_error",
364
+ r instanceof Error ? r.message : "Unknown error occurred"
373
365
  );
374
366
  }
375
367
  }
@@ -392,7 +384,8 @@ class le {
392
384
  });
393
385
  }
394
386
  /**
395
- * Send an API message and convert to protocol-agnostic ApiResponseResult
387
+ * Send an API message and return ApiResponse
388
+ * @throws BodhiError on operational errors (network, timeout, extension-level)
396
389
  */
397
390
  async sendApiRequest(e, t, r, i, o) {
398
391
  try {
@@ -409,23 +402,16 @@ class le {
409
402
  this.sendRawApiMessage(e, t, r, i, o),
410
403
  s
411
404
  ]);
412
- if (de(n)) {
405
+ if (ue(n)) {
413
406
  const h = n.error.type || "extension_error";
414
- return {
415
- error: {
416
- message: n.error.message,
417
- type: h
418
- }
419
- };
407
+ throw new T(h, n.error.message);
420
408
  }
421
409
  return n.response;
422
410
  } catch (s) {
423
- return {
424
- error: {
425
- message: s instanceof Error ? s.message : String(s),
426
- type: "network_error"
427
- }
428
- };
411
+ if (s instanceof T)
412
+ throw s;
413
+ const n = s instanceof Error ? s.message : String(s);
414
+ throw new T("network_error", n);
429
415
  }
430
416
  }
431
417
  /**
@@ -443,12 +429,12 @@ class le {
443
429
  const s = await this.getAuthState();
444
430
  if (J(s)) {
445
431
  r(
446
- E(`Login failed: ${s.error?.message}`, "auth-error")
432
+ l("auth_error", `Login failed: ${s.error?.message}`)
447
433
  );
448
434
  return;
449
435
  }
450
436
  if (s.status !== "authenticated") {
451
- r(E("Login failed: User is not logged in", "auth-error"));
437
+ r(l("auth_error", "Login failed: User is not logged in"));
452
438
  return;
453
439
  }
454
440
  this.setAuthState(s), t(s);
@@ -457,7 +443,7 @@ class le {
457
443
  }
458
444
  }
459
445
  };
460
- chrome.runtime.onMessage.addListener(i), this.sendExtRequest(T.LOGIN, e).catch((o) => {
446
+ chrome.runtime.onMessage.addListener(i), this.sendExtRequest(m.LOGIN, e).catch((o) => {
461
447
  chrome.runtime.onMessage.removeListener(i), r(o);
462
448
  });
463
449
  });
@@ -468,7 +454,7 @@ class le {
468
454
  * @returns AuthLoggedOut with logged out state
469
455
  */
470
456
  async logout() {
471
- await this.sendExtRequest(T.LOGOUT);
457
+ await this.sendExtRequest(m.LOGOUT);
472
458
  const e = {
473
459
  status: "unauthenticated",
474
460
  user: null,
@@ -484,7 +470,7 @@ class le {
484
470
  */
485
471
  async getAuthState() {
486
472
  return this.isClientInitialized() ? (await this.sendExtRequest(
487
- T.GET_AUTH_STATE
473
+ m.GET_AUTH_STATE
488
474
  )).authState : V;
489
475
  }
490
476
  /**
@@ -498,14 +484,18 @@ class le {
498
484
  * Calls /bodhi/v1/info and returns structured server state
499
485
  */
500
486
  async getServerState() {
501
- const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
502
- if (x(e))
487
+ let e;
488
+ try {
489
+ e = await this.sendApiRequest("GET", "/bodhi/v1/info");
490
+ } catch (o) {
491
+ const s = o instanceof Error ? o.message : "Connection failed", n = o instanceof T ? o.code : "extension_error";
503
492
  return {
504
493
  status: "not-reachable",
505
494
  version: null,
506
- error: e.error
495
+ error: { message: s, type: n }
507
496
  };
508
- if (!w(e))
497
+ }
498
+ if (e.status >= 400)
509
499
  return {
510
500
  status: "not-reachable",
511
501
  version: null,
@@ -529,8 +519,6 @@ class le {
529
519
  error: t.error ? { message: t.error.message, type: t.error.type } : { message: "Resource admin required", type: "extension_error" },
530
520
  ...i
531
521
  };
532
- case "tenant_selection":
533
- return { status: "tenant_selection", version: r, error: null, ...i };
534
522
  case "error":
535
523
  return {
536
524
  status: "error",
@@ -572,9 +560,9 @@ class le {
572
560
  requestId: s,
573
561
  error: JSON.stringify(d.error)
574
562
  }), u.error(
575
- E(
576
- d.error.message,
577
- "extension_error"
563
+ new T(
564
+ "extension_error",
565
+ d.error.message
578
566
  )
579
567
  ), n.disconnect();
580
568
  break;
@@ -584,26 +572,24 @@ class le {
584
572
  requestId: s,
585
573
  error: g.response.body?.error
586
574
  }), u.error(
587
- F(
588
- g.response.body?.error?.message || "API error",
575
+ new z(
589
576
  g.response.status,
590
- g.response.body
577
+ g.response.body,
578
+ g.response.body?.error?.message || "API error"
591
579
  )
592
580
  ), n.disconnect();
593
581
  break;
594
582
  }
595
583
  case c.EXT2EXT_CLIENT_STREAM_CHUNK: {
596
584
  const g = d;
597
- ee(g.response) && u.enqueue(g.response.body);
585
+ j(g.response) && u.enqueue(g.response.body);
598
586
  break;
599
587
  }
600
588
  }
601
589
  }), n.onDisconnect.addListener(() => {
602
590
  this.logger.debug("Port disconnected", { requestId: s });
603
591
  try {
604
- u.error(
605
- E("Connection closed unexpectedly", "extension_error")
606
- );
592
+ u.error(new T("connection_closed", "Connection closed unexpectedly"));
607
593
  } catch {
608
594
  }
609
595
  }), n.postMessage({
@@ -630,7 +616,7 @@ class le {
630
616
  // OpenAI-Compatible Namespaced API
631
617
  // ============================================================================
632
618
  get chat() {
633
- return this._chat ??= new z(this);
619
+ return this._chat ??= new F(this);
634
620
  }
635
621
  get models() {
636
622
  return this._models ??= new G(this);
@@ -689,7 +675,7 @@ class le {
689
675
  };
690
676
  }
691
677
  }
692
- class _e extends Y {
678
+ class pe extends Y {
693
679
  constructor(e, t, r) {
694
680
  const i = t || {}, o = {
695
681
  basePath: i.basePath || "/",
@@ -702,13 +688,13 @@ class _e extends Y {
702
688
  super(e, o, r);
703
689
  }
704
690
  createLogger(e) {
705
- return new C("ExtUIClient", e.logLevel);
691
+ return new w("ExtUIClient", e.logLevel);
706
692
  }
707
693
  createStoragePrefix(e) {
708
- return v(e.basePath, P.EXT);
694
+ return N(e.basePath, v.EXT);
709
695
  }
710
696
  createExtClient(e, t) {
711
- return new le(
697
+ return new Ee(
712
698
  {
713
699
  authClientId: this.authClientId,
714
700
  logLevel: e.logLevel,
@@ -719,7 +705,7 @@ class _e extends Y {
719
705
  );
720
706
  }
721
707
  createDirectClient(e, t, r) {
722
- return new Ee(
708
+ return new de(
723
709
  {
724
710
  authClientId: e,
725
711
  authServerUrl: t.authServerUrl,
@@ -732,12 +718,12 @@ class _e extends Y {
732
718
  );
733
719
  }
734
720
  }
735
- const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckljjghmjbf"], f = "production", m = class m {
721
+ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckljjghmjbf"], f = "production", p = class p {
736
722
  // ============================================================================
737
723
  // Constructor
738
724
  // ============================================================================
739
725
  constructor(e, t) {
740
- 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 C("BodhiExtClient", t?.logLevel || "warn"), this.attempts = t?.attempts ?? ae, this.attemptWaitMs = t?.attemptWaitMs ?? ce, this.attemptTimeout = t?.attemptTimeout ?? he, this.authEndpoints = {
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 = {
741
727
  authorize: `${this.authServerUrl}/protocol/openid-connect/auth`,
742
728
  token: `${this.authServerUrl}/protocol/openid-connect/token`,
743
729
  userinfo: `${this.authServerUrl}/protocol/openid-connect/userinfo`,
@@ -755,11 +741,11 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
755
741
  }
756
742
  static generateCodeVerifier() {
757
743
  const e = new Uint8Array(32);
758
- return crypto.getRandomValues(e), m.base64UrlEncode(e.buffer);
744
+ return crypto.getRandomValues(e), p.base64UrlEncode(e.buffer);
759
745
  }
760
746
  static async generateCodeChallenge(e) {
761
747
  const r = new TextEncoder().encode(e), i = await crypto.subtle.digest("SHA-256", r);
762
- return m.base64UrlEncode(i);
748
+ return p.base64UrlEncode(i);
763
749
  }
764
750
  // ============================================================================
765
751
  // State Management
@@ -778,7 +764,7 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
778
764
  * Get extension IDs for current environment
779
765
  */
780
766
  getExtensionIdsForEnvironment() {
781
- const t = f !== "production" ? ge : Te;
767
+ const t = f !== "production" ? le : ge;
782
768
  return this.logger.info("[Ext2Ext/Registry] Environment: production"), this.logger.debug("[Ext2Ext/Registry] Using extension IDs:", t), t;
783
769
  }
784
770
  /**
@@ -793,7 +779,7 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
793
779
  }, this.attemptTimeout);
794
780
  try {
795
781
  const o = {
796
- type: p.EXT_REQUEST,
782
+ type: _.EXT_REQUEST,
797
783
  requestId: crypto.randomUUID(),
798
784
  request: {
799
785
  action: "get_extension_id"
@@ -809,7 +795,7 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
809
795
  }
810
796
  this.logger.debug(`[Ext2Ext/Discovery] Response from ${e}:`, s);
811
797
  const n = s;
812
- n && n.type === p.EXT_RESPONSE ? (this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`), t(!0)) : (this.logger.error(
798
+ n && n.type === _.EXT_RESPONSE ? (this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`), t(!0)) : (this.logger.error(
813
799
  `[Ext2Ext/Discovery] Invalid response from ${e}:`,
814
800
  s
815
801
  ), r(new Error("Invalid response")));
@@ -876,7 +862,7 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
876
862
  this.logger.debug("[BodhiExtClient] Listeners already initialized, skipping");
877
863
  return;
878
864
  }
879
- this.listenersInitialized = !0, chrome.runtime.onMessage.addListener((e, t, r) => e.type !== c.EXT2EXT_CLIENT_REQUEST && e.type !== c.EXT2EXT_CLIENT_API_REQUEST ? !1 : this.state !== "ready" && !(e.type === c.EXT2EXT_CLIENT_REQUEST && e.request.action === T.DISCOVER_EXTENSION) ? (e.type === c.EXT2EXT_CLIENT_REQUEST ? r({
865
+ this.listenersInitialized = !0, chrome.runtime.onMessage.addListener((e, t, r) => e.type !== c.EXT2EXT_CLIENT_REQUEST && e.type !== c.EXT2EXT_CLIENT_API_REQUEST ? !1 : this.state !== "ready" && !(e.type === c.EXT2EXT_CLIENT_REQUEST && e.request.action === m.DISCOVER_EXTENSION) ? (e.type === c.EXT2EXT_CLIENT_REQUEST ? r({
880
866
  type: c.EXT2EXT_CLIENT_RESPONSE,
881
867
  requestId: e.requestId,
882
868
  response: {
@@ -1022,7 +1008,7 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1022
1008
  try {
1023
1009
  let s = {};
1024
1010
  switch (i) {
1025
- case T.DISCOVER_EXTENSION: {
1011
+ case m.DISCOVER_EXTENSION: {
1026
1012
  const n = o;
1027
1013
  await this.init(n), this.logger.info("[BodhiExtClient] Discovery successful:", {
1028
1014
  extensionId: this.extensionId,
@@ -1033,14 +1019,14 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1033
1019
  };
1034
1020
  break;
1035
1021
  }
1036
- case T.SET_EXTENSION_ID: {
1022
+ case m.SET_EXTENSION_ID: {
1037
1023
  const { extensionId: n } = o;
1038
1024
  this.extensionId = n, this.state = "ready", this.logger.info("[BodhiExtClient] Extension ID set:", { extensionId: n }), s = { success: !0 };
1039
1025
  break;
1040
1026
  }
1041
- case T.GET_EXTENSION_ID: {
1042
- const n = await this.sendExtRequestRaw(te.GET_EXTENSION_ID, o);
1043
- return R(n.response) ? {
1027
+ case m.GET_EXTENSION_ID: {
1028
+ const n = await this.sendExtRequestRaw(ee.GET_EXTENSION_ID, o);
1029
+ return x(n.response) ? {
1044
1030
  type: c.EXT2EXT_CLIENT_RESPONSE,
1045
1031
  requestId: t,
1046
1032
  response: {
@@ -1055,15 +1041,15 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1055
1041
  response: n.response
1056
1042
  };
1057
1043
  }
1058
- case T.LOGIN: {
1044
+ case m.LOGIN: {
1059
1045
  const n = o;
1060
1046
  await this.login(n), this.broadcastAuthStateChange();
1061
1047
  break;
1062
1048
  }
1063
- case T.LOGOUT:
1049
+ case m.LOGOUT:
1064
1050
  await this.logout(), this.broadcastAuthStateChange();
1065
1051
  break;
1066
- case T.GET_AUTH_STATE:
1052
+ case m.GET_AUTH_STATE:
1067
1053
  s = { authState: await this.getAuthState() };
1068
1054
  break;
1069
1055
  default:
@@ -1129,27 +1115,16 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1129
1115
  if (!this.extensionId)
1130
1116
  throw new Error("Extension not discovered. Please detect Bodhi extension before login.");
1131
1117
  e?.flowType === "redirect" && this.logger.warn("Extension mode does not support redirect flow type; using popup instead");
1132
- const r = e?.userRole ?? this.userRole, i = new X(this.authClientId).requestedRole(r).flowType("popup");
1118
+ const r = e?.userRole ?? this.userRole, i = new P(this.authClientId).requestedRole(r).flowType("popup");
1133
1119
  e?.requested && i.requested(e.requested);
1134
- const o = i.build(), s = await this.requestAccess(o);
1135
- if (x(s))
1136
- throw E(
1137
- s.error.message,
1138
- s.error.type
1139
- );
1140
- if (!w(s))
1141
- throw E(
1142
- `Access request failed: HTTP ${s.status}`,
1143
- "auth_error"
1144
- );
1145
- const { id: n, review_url: h } = s.body;
1120
+ const o = i.build(), s = await this.requestAccess(o), { id: n, review_url: h } = X(s);
1146
1121
  await chrome.tabs.create({ url: h });
1147
1122
  const a = await this.pollAccessRequestStatus(n, {
1148
1123
  intervalMs: e?.pollIntervalMs ?? O,
1149
1124
  timeoutMs: e?.pollTimeoutMs ?? b
1150
1125
  });
1151
1126
  if (a.status !== "approved")
1152
- throw E(`Access request ${a.status}`, "auth_error");
1127
+ throw l("auth_error", `Access request ${a.status}`);
1153
1128
  const d = `openid profile email roles ${a.access_request_scope ?? ""}`.trim();
1154
1129
  await this.performOAuthPkce(d);
1155
1130
  } finally {
@@ -1244,45 +1219,17 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1244
1219
  // Access Request Methods
1245
1220
  // ============================================================================
1246
1221
  async requestAccess(e) {
1247
- try {
1248
- const t = await this.sendApiRequest(
1249
- "POST",
1250
- "/bodhi/v1/apps/request-access",
1251
- e
1252
- );
1253
- return {
1254
- body: t.body,
1255
- status: t.status,
1256
- headers: t.headers
1257
- };
1258
- } catch (t) {
1259
- return {
1260
- error: {
1261
- message: t instanceof Error ? t.message : String(t),
1262
- type: "extension_error"
1263
- }
1264
- };
1265
- }
1222
+ return this.sendApiRequest(
1223
+ "POST",
1224
+ "/bodhi/v1/apps/request-access",
1225
+ e
1226
+ );
1266
1227
  }
1267
1228
  async getAccessRequestStatus(e) {
1268
- try {
1269
- const t = await this.sendApiRequest(
1270
- "GET",
1271
- `/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`
1272
- );
1273
- return {
1274
- body: t.body,
1275
- status: t.status,
1276
- headers: t.headers
1277
- };
1278
- } catch (t) {
1279
- return {
1280
- error: {
1281
- message: t instanceof Error ? t.message : String(t),
1282
- type: "extension_error"
1283
- }
1284
- };
1285
- }
1229
+ return this.sendApiRequest(
1230
+ "GET",
1231
+ `/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`
1232
+ );
1286
1233
  }
1287
1234
  async pollAccessRequestStatus(e, t) {
1288
1235
  return L(
@@ -1292,7 +1239,7 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1292
1239
  );
1293
1240
  }
1294
1241
  async performOAuthPkce(e) {
1295
- const t = m.generateCodeVerifier(), r = await m.generateCodeChallenge(t), i = m.generateCodeVerifier();
1242
+ const t = p.generateCodeVerifier(), r = await p.generateCodeChallenge(t), i = p.generateCodeVerifier();
1296
1243
  await chrome.storage.session.set({
1297
1244
  codeVerifier: t,
1298
1245
  state: i,
@@ -1308,24 +1255,24 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1308
1255
  return;
1309
1256
  }
1310
1257
  if (!a) {
1311
- await chrome.storage.session.remove(["codeVerifier", "state"]), h(E("No redirect URL received", "oauth-error"));
1258
+ await chrome.storage.session.remove(["codeVerifier", "state"]), h(l("oauth_error", "No redirect URL received"));
1312
1259
  return;
1313
1260
  }
1314
1261
  try {
1315
- const u = new URL(a), d = u.searchParams.get("code"), g = u.searchParams.get("state"), { state: _ } = await chrome.storage.session.get("state");
1316
- if (g !== _) {
1317
- await chrome.storage.session.remove(["codeVerifier", "state"]), h(E("State mismatch", "oauth-error"));
1262
+ const u = new URL(a), d = u.searchParams.get("code"), g = u.searchParams.get("state"), { state: S } = await chrome.storage.session.get("state");
1263
+ if (g !== S) {
1264
+ await chrome.storage.session.remove(["codeVerifier", "state"]), h(l("oauth_error", "State mismatch"));
1318
1265
  return;
1319
1266
  }
1320
1267
  if (!d) {
1321
- await chrome.storage.session.remove(["codeVerifier", "state"]), h(E("No authorization code", "oauth-error"));
1268
+ await chrome.storage.session.remove(["codeVerifier", "state"]), h(l("oauth_error", "No authorization code"));
1322
1269
  return;
1323
1270
  }
1324
1271
  await this.exchangeCodeForTokens(d), await chrome.storage.session.remove(["codeVerifier", "state"]);
1325
- const l = await this.getAuthState();
1326
- if (l.status !== "authenticated")
1327
- throw E("Login failed", "oauth-error");
1328
- n(l);
1272
+ const E = await this.getAuthState();
1273
+ if (E.status !== "authenticated")
1274
+ throw l("oauth_error", "Login failed");
1275
+ n(E);
1329
1276
  } catch (u) {
1330
1277
  await chrome.storage.session.remove(["codeVerifier", "state"]), h(u);
1331
1278
  }
@@ -1343,7 +1290,7 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1343
1290
  */
1344
1291
  async sendExtRequest(e, t) {
1345
1292
  const r = await this.sendExtRequestRaw(e, t);
1346
- if (R(r.response))
1293
+ if (x(r.response))
1347
1294
  throw this.logger.error("[BodhiExtClient] Extension error:", r.response.error), new Error(
1348
1295
  r.response.error.message || `Extension request failed: ${JSON.stringify(r.response)}`
1349
1296
  );
@@ -1359,13 +1306,16 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1359
1306
  */
1360
1307
  async sendApiRequest(e, t, r, i) {
1361
1308
  if (!this.extensionId)
1362
- throw new Error(this.createErrorClientNotInitialized({ type: "api", method: e, endpoint: t }));
1309
+ throw new T(
1310
+ "not_initialized",
1311
+ this.createErrorClientNotInitialized({ type: "api", method: e, endpoint: t })
1312
+ );
1363
1313
  this.logger.debug(
1364
1314
  `[BodhiExtClient] Sending API_REQUEST: method=${e}, endpoint=${t}`,
1365
1315
  r ? { body: r } : ""
1366
1316
  );
1367
1317
  const o = crypto.randomUUID(), s = {
1368
- type: p.API_REQUEST,
1318
+ type: _.API_REQUEST,
1369
1319
  requestId: o,
1370
1320
  request: {
1371
1321
  method: e,
@@ -1381,17 +1331,24 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1381
1331
  this.logger.error(
1382
1332
  `[BodhiExtClient] Chrome runtime error for request ${o}:`,
1383
1333
  chrome.runtime.lastError
1384
- ), h(new Error(chrome.runtime.lastError.message));
1334
+ ), h(
1335
+ new T(
1336
+ "extension_error",
1337
+ chrome.runtime.lastError.message ?? "Chrome runtime error"
1338
+ )
1339
+ );
1385
1340
  return;
1386
1341
  }
1387
1342
  if (this.logger.debug(`[BodhiExtClient] Response for request ${o}:`, a), !a) {
1388
- this.logger.error(`[BodhiExtClient] No response received for request ${o}`), h(new Error("No response from extension"));
1343
+ this.logger.error(`[BodhiExtClient] No response received for request ${o}`), h(new T("extension_error", "No response from extension"));
1389
1344
  return;
1390
1345
  }
1391
- a.type === p.API_RESPONSE && a.requestId === o ? "error" in a ? (this.logger.error(`[BodhiExtClient] API error for ${o}:`, a.error), h(new Error(a.error.message))) : (this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${o}`), n(a.response)) : (this.logger.error(
1346
+ a.type === _.API_RESPONSE && a.requestId === o ? "error" in a ? (this.logger.error(`[BodhiExtClient] API error for ${o}:`, a.error), h(
1347
+ new T(a.error.type || "extension_error", a.error.message)
1348
+ )) : (this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${o}`), n(a.response)) : (this.logger.error(
1392
1349
  `[BodhiExtClient] Invalid response format for ${o}:`,
1393
1350
  a
1394
- ), h(new Error("Invalid response format")));
1351
+ ), h(new T("extension_error", "Invalid response format")));
1395
1352
  });
1396
1353
  } catch (a) {
1397
1354
  this.logger.error(`[BodhiExtClient] Exception sending message for ${o}:`, a), h(a);
@@ -1406,13 +1363,16 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1406
1363
  */
1407
1364
  async sendExtRequestRaw(e, t) {
1408
1365
  if (!this.extensionId)
1409
- throw new Error(this.createErrorClientNotInitialized({ type: "ext", action: e, params: t }));
1366
+ throw new T(
1367
+ "not_initialized",
1368
+ this.createErrorClientNotInitialized({ type: "ext", action: e, params: t })
1369
+ );
1410
1370
  this.logger.debug(
1411
1371
  `[BodhiExtClient] Sending EXT_REQUEST (raw): action=${e}`,
1412
1372
  t ? { params: t } : ""
1413
1373
  );
1414
1374
  const r = crypto.randomUUID(), i = {
1415
- type: p.EXT_REQUEST,
1375
+ type: _.EXT_REQUEST,
1416
1376
  requestId: r,
1417
1377
  request: {
1418
1378
  action: e,
@@ -1433,10 +1393,10 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1433
1393
  this.logger.error(`[BodhiExtClient] No response received for request ${r}`), s(new Error("No response from extension"));
1434
1394
  return;
1435
1395
  }
1436
- n.type === p.EXT_RESPONSE && n.requestId === r ? (this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`), o(n)) : (this.logger.error(
1396
+ n.type === _.EXT_RESPONSE && n.requestId === r ? (this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`), o(n)) : (this.logger.error(
1437
1397
  `[BodhiExtClient] Invalid response format for ${r}:`,
1438
1398
  n
1439
- ), s(new Error("Invalid response format")));
1399
+ ), s(new T("extension_error", "Invalid response format")));
1440
1400
  });
1441
1401
  } catch (n) {
1442
1402
  this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`, n), s(n);
@@ -1467,8 +1427,8 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1467
1427
  try {
1468
1428
  let u = { ...h };
1469
1429
  if (a !== !1) {
1470
- const l = await this._getAccessTokenRaw();
1471
- if (!l) {
1430
+ const E = await this._getAccessTokenRaw();
1431
+ if (!E) {
1472
1432
  e.postMessage({
1473
1433
  type: c.EXT2EXT_CLIENT_STREAM_ERROR,
1474
1434
  requestId: r,
@@ -1481,11 +1441,11 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1481
1441
  }
1482
1442
  u = {
1483
1443
  ...u,
1484
- Authorization: `Bearer ${l}`
1444
+ Authorization: `Bearer ${E}`
1485
1445
  }, this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request");
1486
1446
  }
1487
1447
  const d = chrome.runtime.connect(this.extensionId, {
1488
- name: re
1448
+ name: te
1489
1449
  });
1490
1450
  this.activeStreamPorts.set(r, d);
1491
1451
  const g = setTimeout(() => {
@@ -1497,36 +1457,36 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1497
1457
  type: "timeout_error"
1498
1458
  }
1499
1459
  }), this.cleanupStreamPort(r));
1500
- }, m.STREAM_TIMEOUT);
1501
- d.onMessage.addListener((l) => {
1502
- if (se(l)) {
1503
- const y = l.response, q = y.body;
1504
- y.status >= 400 ? e.postMessage({
1460
+ }, p.STREAM_TIMEOUT);
1461
+ d.onMessage.addListener((E) => {
1462
+ if (re(E)) {
1463
+ const I = E.response, q = I.body;
1464
+ I.status >= 400 ? e.postMessage({
1505
1465
  type: c.EXT2EXT_CLIENT_STREAM_API_ERROR,
1506
1466
  requestId: r,
1507
- response: y
1467
+ response: I
1508
1468
  }) : q?.done ? (e.postMessage({
1509
1469
  type: c.EXT2EXT_CLIENT_STREAM_DONE,
1510
1470
  requestId: r
1511
1471
  }), this.logger.info(`[BodhiExtClient] Stream complete for ${r}`), clearTimeout(g), this.cleanupStreamPort(r)) : e.postMessage({
1512
1472
  type: c.EXT2EXT_CLIENT_STREAM_CHUNK,
1513
1473
  requestId: r,
1514
- response: y
1474
+ response: I
1515
1475
  });
1516
- } else oe(l) ? (this.logger.error(
1517
- `[BodhiExtClient] Stream API error for ${r}: ${l.response.status}`
1476
+ } else se(E) ? (this.logger.error(
1477
+ `[BodhiExtClient] Stream API error for ${r}: ${E.response.status}`
1518
1478
  ), e.postMessage({
1519
1479
  type: c.EXT2EXT_CLIENT_STREAM_API_ERROR,
1520
1480
  requestId: r,
1521
- response: l.response
1522
- })) : ie(l) && (this.logger.error(
1481
+ response: E.response
1482
+ })) : oe(E) && (this.logger.error(
1523
1483
  `[BodhiExtClient] Stream error for ${r}:`,
1524
- l.error.message
1484
+ E.error.message
1525
1485
  ), e.postMessage({
1526
1486
  type: c.EXT2EXT_CLIENT_STREAM_ERROR,
1527
1487
  requestId: r,
1528
1488
  error: {
1529
- message: `stream error: ${JSON.stringify(l)}`,
1489
+ message: `stream error: ${JSON.stringify(E)}`,
1530
1490
  type: "extension_error"
1531
1491
  }
1532
1492
  }), clearTimeout(g), this.cleanupStreamPort(r));
@@ -1540,8 +1500,8 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1540
1500
  }
1541
1501
  }), this.activeStreamPorts.delete(r));
1542
1502
  });
1543
- const _ = {
1544
- type: p.STREAM_REQUEST,
1503
+ const S = {
1504
+ type: _.STREAM_REQUEST,
1545
1505
  requestId: r,
1546
1506
  request: {
1547
1507
  method: o,
@@ -1550,7 +1510,7 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1550
1510
  headers: u
1551
1511
  }
1552
1512
  };
1553
- this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:", _), d.postMessage(_);
1513
+ this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:", S), d.postMessage(S);
1554
1514
  } catch (u) {
1555
1515
  const d = u;
1556
1516
  this.logger.error("[BodhiExtClient] Stream error:", JSON.stringify(d.message)), e.postMessage({
@@ -1633,9 +1593,9 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1633
1593
  } catch (t) {
1634
1594
  this.logger.warn("Token refresh failed:", t);
1635
1595
  }
1636
- throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), E(
1637
- "Access token expired and unable to refresh. Try logging out and logging in again.",
1638
- "token_refresh_failed"
1596
+ throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), l(
1597
+ "auth_error",
1598
+ "Access token expired and unable to refresh. Try logging out and logging in again."
1639
1599
  );
1640
1600
  }
1641
1601
  /**
@@ -1670,20 +1630,20 @@ const ge = ["ggedphdcbekjlomjaidbajglgihbeaon"], Te = ["bjdjhiombmfbcoeojijpfckl
1670
1630
  return `Client not initialized. Extension discovery not triggered nor extensionId set, cannot handle request: ${JSON.stringify(e)}`;
1671
1631
  }
1672
1632
  };
1673
- m.STREAM_TIMEOUT = 6e4;
1674
- let N = m;
1675
- const Se = "production";
1633
+ p.STREAM_TIMEOUT = 6e4;
1634
+ let A = p;
1635
+ const _e = "production";
1676
1636
  export {
1677
- N as BodhiExtClient,
1678
- ue as DEFAULT_API_TIMEOUT_MS,
1679
- ae as DISCOVERY_ATTEMPTS,
1680
- he as DISCOVERY_ATTEMPT_TIMEOUT,
1681
- ce as DISCOVERY_ATTEMPT_WAIT_MS,
1682
- ne as DISCOVERY_TIMEOUT_MS,
1683
- T as EXT2EXT_CLIENT_ACTIONS,
1637
+ 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,
1643
+ m as EXT2EXT_CLIENT_ACTIONS,
1684
1644
  c as EXT2EXT_CLIENT_MESSAGE_TYPES,
1685
1645
  k as EXT2EXT_CLIENT_STREAM_PORT,
1686
- Se as EXT_BUILD_MODE,
1687
- _e as ExtUIClient,
1688
- de as isExtClientApiError
1646
+ _e as EXT_BUILD_MODE,
1647
+ pe as ExtUIClient,
1648
+ ue as isExtClientApiError
1689
1649
  };
@@ -1,5 +1,6 @@
1
- import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse } from '@bodhiapp/ts-client';
2
- import { IExtensionClient, Chat, Models, Embeddings, Toolsets, Mcps, ApiResponseResult, AuthState, BackendServerState, ClientState, ExtensionState, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
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';
3
+ import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
3
4
  export type SerializedExt2ExtState = {
4
5
  extensionId?: string;
5
6
  };
@@ -99,9 +100,10 @@ export declare class ExtClient implements IExtensionClient {
99
100
  */
100
101
  private sendRawApiMessage;
101
102
  /**
102
- * Send an API message and convert to protocol-agnostic ApiResponseResult
103
+ * Send an API message and return ApiResponse
104
+ * @throws BodhiError on operational errors (network, timeout, extension-level)
103
105
  */
104
- sendApiRequest<TReq = void, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): Promise<ApiResponseResult<TRes>>;
106
+ sendApiRequest<TReq = void, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): Promise<ApiResponse<TRes>>;
105
107
  /**
106
108
  * Login user via OAuth
107
109
  * @param options - Optional login parameters including toolsetScopeIds
@@ -124,9 +126,7 @@ export declare class ExtClient implements IExtensionClient {
124
126
  /**
125
127
  * Ping bodhi-browser-ext API via /ping endpoint
126
128
  */
127
- pingApi(): Promise<ApiResponseResult<{
128
- message: string;
129
- }>>;
129
+ pingApi(): Promise<ApiResponse<PingResponse>>;
130
130
  /**
131
131
  * Get backend server state
132
132
  * Calls /bodhi/v1/info and returns structured server state
@@ -141,8 +141,8 @@ export declare class ExtClient implements IExtensionClient {
141
141
  get embeddings(): Embeddings;
142
142
  get toolsets(): Toolsets;
143
143
  get mcps(): Mcps;
144
- requestAccess(body: CreateAccessRequest): Promise<ApiResponseResult<CreateAccessRequestResponse>>;
145
- getAccessRequestStatus(requestId: string): Promise<ApiResponseResult<AccessRequestStatusResponse>>;
144
+ requestAccess(body: CreateAccessRequest): Promise<ApiResponse<CreateAccessRequestResponse>>;
145
+ getAccessRequestStatus(requestId: string): Promise<ApiResponse<AccessRequestStatusResponse>>;
146
146
  pollAccessRequestStatus(requestId: string, options?: {
147
147
  intervalMs?: number;
148
148
  timeoutMs?: number;
@@ -1,6 +1,7 @@
1
1
  import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse } from '@bodhiapp/ts-client';
2
2
  import { ExtClientApiRequestMessage, ExtClientApiResponseMessage, ExtClientRequestMessage, ExtClientResponseMessage } from './messages';
3
- import { ApiResponseResult, AuthState, LoginOptions, LogLevel, UserScope } from '@bodhiapp/bodhi-js-core';
3
+ import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
4
+ import { AuthState, LoginOptions, LogLevel, UserScope } from '@bodhiapp/bodhi-js-core';
4
5
  export type ClientExtState = 'setup' | 'ready' | 'error';
5
6
  export interface BodhiExtClientConfig {
6
7
  authServerUrl?: string;
@@ -121,8 +122,8 @@ export declare class BodhiExtClient {
121
122
  * Logout current user and revoke tokens
122
123
  */
123
124
  logout(): Promise<void>;
124
- requestAccess(body: CreateAccessRequest): Promise<ApiResponseResult<CreateAccessRequestResponse>>;
125
- getAccessRequestStatus(requestId: string): Promise<ApiResponseResult<AccessRequestStatusResponse>>;
125
+ requestAccess(body: CreateAccessRequest): Promise<ApiResponse<CreateAccessRequestResponse>>;
126
+ getAccessRequestStatus(requestId: string): Promise<ApiResponse<AccessRequestStatusResponse>>;
126
127
  pollAccessRequestStatus(requestId: string, options?: {
127
128
  intervalMs?: number;
128
129
  timeoutMs?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js-ext",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
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.27",
41
- "@bodhiapp/bodhi-js-core": "0.0.27",
42
- "@bodhiapp/ts-client": "0.1.20"
40
+ "@bodhiapp/bodhi-browser-types": "0.0.29",
41
+ "@bodhiapp/bodhi-js-core": "0.0.29",
42
+ "@bodhiapp/ts-client": "0.1.23"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@eslint/js": "^9.23.0",