@bodhiapp/bodhi-js-ext 0.0.43 → 0.0.44

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 a=require("@bodhiapp/bodhi-js-core"),T=require("@bodhiapp/bodhi-browser-types"),E={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",EXT2EXT_CLIENT_STREAM_TEXT_REQUEST:"EXT2EXT_CLIENT_STREAM_TEXT_REQUEST",EXT2EXT_CLIENT_STREAM_TEXT_START:"EXT2EXT_CLIENT_STREAM_TEXT_START",EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:"EXT2EXT_CLIENT_STREAM_TEXT_CHUNK",EXT2EXT_CLIENT_STREAM_TEXT_DONE:"EXT2EXT_CLIENT_STREAM_TEXT_DONE",EXT2EXT_CLIENT_STREAM_TEXT_ERROR:"EXT2EXT_CLIENT_STREAM_TEXT_ERROR"},f="ext2ext-client-stream",y="ext2ext-client-stream-text",_={LOGIN:"login",LOGOUT:"logout",GET_AUTH_STATE:"getAuthState",DISCOVER_EXTENSION:"discoverBodhiExtension",GET_EXTENSION_ID:"get_extension_id",SET_EXTENSION_ID:"setExtensionId"},C=5e3,A=3,w=500,X=500,N=3e4;function O(m){return"error"in m}class P{async get(e){const r=(await chrome.storage.session.get(e))[e];return r!==void 0?String(r):null}async set(e){await chrome.storage.session.set(e)}async remove(e){await chrome.storage.session.remove(e)}}class M extends a.DirectClientBase{constructor(e,t){const r=a.createStoragePrefixWithNamespace(e.basePath,a.STORAGE_PREFIXES.EXT_DIRECT),i={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectExtClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new P,initialTokens:e.initialTokens};super(i,t)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated"&&!e?.reauthorize)return t;const r=e?.userRole??"scope_user_user",i=this._getRedirectUri();e?.onProgress?.("requesting");const s=a.generateCodeVerifier(),o=await a.generateCodeChallenge(s),n=a.generateCodeVerifier();await this._storageSet({[this.storageKeys.CODE_VERIFIER]:s,[this.storageKeys.STATE]:n});const h=a.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:i,scope:a.BASE_OAUTH_SCOPE,state:n,codeChallenge:o}),c=a.buildErrorUrl(i),u=new a.AccessRequestBuilder(this.authClientId).requestedRole(r);e?.requested&&u.requested(e.requested);const l=await this.requestAccess(u.build()),{review_url:d}=a.unwrapResponse(l),g=a.buildReviewUrl(d,h,c);e?.onProgress?.("reviewing");const p=await this.launchReview(g);return e?.onProgress?.("authenticating"),this.completeOAuthRedirect(p)}launchReview(e){return new Promise((t,r)=>{chrome.identity.launchWebAuthFlow({url:e,interactive:!0},i=>{if(chrome.runtime.lastError){r(chrome.runtime.lastError);return}if(!i){r(a.createOperationError("oauth_error","No redirect URL received"));return}t(i)})})}async completeOAuthRedirect(e){const t=new URL(e),r=t.searchParams.get("error");if(r)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),t.searchParams.get("error_source")==="bodhi"&&a.throwAccessRequestDenialError(r==="access_denied"?"denied":r),a.createOperationError("oauth_error",t.searchParams.get("error_description")??r);const i=t.searchParams.get("code"),s=t.searchParams.get("state"),o=await this._storageGet(this.storageKeys.STATE);if(!s||s!==o)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","State mismatch");if(!i)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","No authorization code");await this.exchangeCodeForTokens(i);const n=await this.getAuthState();if(n.status!=="authenticated")throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","Login failed");return this.setAuthState(n),n}_getRedirectUri(){return chrome.identity.getRedirectURL("callback")}}class k{constructor(e={},t){this.state={type:"extension",extension:"not-initialized",extensionId:null,server:a.PENDING_EXTENSION_READY},this.extensionId=null,this.broadcastListenerActive=!1,this.config=e,this.logger=new a.Logger("ExtClient",e?.logLevel||"warn"),this.onStateChange=t??a.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===E.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=a.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??C,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 o={attempts:this.config.initParams?.extension?.attempts,attemptWaitMs:this.config.initParams?.extension?.attemptWaitMs,attemptTimeout:this.config.initParams?.extension?.attemptTimeout},n=await this.sendExtMessageWithTimeout(_.DISCOVER_EXTENSION,o,t);this.extensionId=n.extensionId,this.logger.info("Extension discovered:",this.extensionId)}this.setupBroadcastListener()}const i={type:"extension",extension:"ready",extensionId:this.extensionId,server:a.PENDING_EXTENSION_READY};let s=a.PENDING_EXTENSION_READY;if(e.testConnection)try{s=await this.getServerState(),this.logger.info("Server connectivity tested, state:",s.status)}catch(o){this.logger.error("Failed to get server state:",o),s=a.BACKEND_SERVER_NOT_REACHABLE}return this.setState({...i,server:s}),this.state}catch(i){this.logger.error("Failed to initialize extension:",i),this.extensionId=null;const s=a.createExtensionStateNotFound();return this.setState(s),this.state}}async sendExtMessageWithTimeout(e,t,r=1e4){const i=new Promise((s,o)=>setTimeout(()=>o(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:E.EXT2EXT_CLIENT_REQUEST,requestId:r,request:{action:e,params:t}});if(!i)throw a.createOperationError("extension_error","No response from background script");if(i.type!==E.EXT2EXT_CLIENT_RESPONSE)throw a.createOperationError("extension_error","Invalid response type from background script");const s=i.response;if(T.isExtError(s)){const o=s.error.type||"extension_error";throw a.createOperationError(o,s.error.message)}return s}catch(r){throw r instanceof a.BodhiError?r:a.createOperationError("extension_error",r instanceof Error?r.message:"Unknown error occurred")}}async sendRawApiMessage(e,t,r,i,s){const o=this.generateRequestId();return await chrome.runtime.sendMessage({type:E.EXT2EXT_CLIENT_API_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i,authenticated:s}})}async sendApiRequest(e,t,r,i,s){try{const o=new Promise((h,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)),n=await Promise.race([this.sendRawApiMessage(e,t,r,i,s),o]);if(O(n)){const h=n.error.type||"extension_error";throw new a.BodhiError(h,n.error.message)}return n.response}catch(o){if(o instanceof a.BodhiError)throw o;const n=o instanceof Error?o.message:String(o);throw new a.BodhiError("network_error",n)}}async login(e){return new Promise((t,r)=>{const i=async s=>{if(s&&typeof s=="object"&&"type"in s&&s.type==="EXT2EXT_CLIENT_BROADCAST"&&"event"in s&&s.event==="authStateChanged"){chrome.runtime.onMessage.removeListener(i);try{const o=await this.getAuthState();if(a.isAuthError(o)){r(a.createOperationError("auth_error",`Login failed: ${o.error?.message}`));return}if(o.status!=="authenticated"){r(a.createOperationError("auth_error","Login failed: User is not logged in"));return}this.setAuthState(o),t(o)}catch(o){r(o)}}};chrome.runtime.onMessage.addListener(i),this.sendExtRequest(_.LOGIN,e).catch(s=>{chrome.runtime.onMessage.removeListener(i),r(s)})})}async logout(){await this.sendExtRequest(_.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(_.GET_AUTH_STATE)).authState:a.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){let e;try{e=await this.sendApiRequest("GET","/bodhi/v1/info")}catch(s){const o=s instanceof Error?s.message:"Connection failed",n=s instanceof a.BodhiError?s.code:"extension_error";return{status:"not-reachable",version:null,error:{message:o,type:n}}}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,s=!0){const o=this.generateRequestId();this.logger.debug("Starting stream",{method:e,endpoint:t,requestId:o});const n=chrome.runtime.connect({name:f}),c=new ReadableStream({start:u=>{n.onMessage.addListener(l=>{if(l.requestId===o)switch(l.type){case E.EXT2EXT_CLIENT_STREAM_DONE:this.logger.debug("Stream complete",{requestId:o}),u.close(),n.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_ERROR:this.logger.error("Stream error",{requestId:o,error:JSON.stringify(l.error)}),u.error(new a.BodhiError("extension_error",l.error.message)),n.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_API_ERROR:{const d=l;this.logger.error("Stream API error",{requestId:o,error:d.response.body?.error}),u.error(new a.BodhiApiError(d.response.status,d.response.body,d.response.body?.error?.message||"API error")),n.disconnect();break}case E.EXT2EXT_CLIENT_STREAM_CHUNK:{const d=l;T.isApiSuccessResponse(d.response)&&u.enqueue(d.response.body);break}}}),n.onDisconnect.addListener(()=>{this.logger.debug("Port disconnected",{requestId:o});try{u.error(new a.BodhiError("connection_closed","Connection closed unexpectedly"))}catch{}}),n.postMessage({type:E.EXT2EXT_CLIENT_STREAM_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i,authenticated:s}})}}).getReader();try{for(;;){const{done:u,value:l}=await c.read();if(u){this.logger.debug("Stream iteration complete");break}yield l}}finally{c.releaseLock()}}async streamText(e,t,r,i,s=!0){const o=this.generateRequestId();return new Promise((n,h)=>{let c,u=!1;const l=new ReadableStream({start:g=>{c=g}}),d=chrome.runtime.connect({name:y});d.onMessage.addListener(g=>{if(g.requestId===o)switch(g.type){case E.EXT2EXT_CLIENT_STREAM_TEXT_START:{u=!0;async function*p(v){const x=v.getReader();try{for(;;){const{done:b,value:L}=await x.read();if(b)break;yield L}}finally{x.releaseLock()}}n({status:g.status,headers:g.headers,body:p(l)});break}case E.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:c.enqueue(g.chunk);break;case E.EXT2EXT_CLIENT_STREAM_TEXT_DONE:c.close(),d.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR:{const p=a.createOperationError("extension_error",g.error.message);u?c.error(p):h(p),d.disconnect();break}}}),d.onDisconnect.addListener(()=>{if(!u)h(a.createOperationError("extension_error","Connection closed unexpectedly"));else try{c.error(a.createOperationError("extension_error","Connection closed unexpectedly"))}catch{}}),d.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i,authenticated:s}})})}get chat(){return this._chat??=new a.Chat(this)}get models(){return this._models??=new a.Models(this)}get embeddings(){return this._embeddings??=new a.Embeddings(this)}get mcps(){return this._mcps??=new a.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}serialize(){return{extensionId:this.extensionId??void 0}}async debug(){return{type:"ExtClient",state:this.state,authState:await this.getAuthState()}}}class U extends a.BaseFacadeClient{constructor(e,t,r){const i=t||{},s={basePath:i.basePath||"/",authServerUrl:i.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:i.logLevel||"warn",apiTimeoutMs:i.apiTimeoutMs,storage:i.storage,initialTokens:i.initialTokens,initParams:i.initParams};super(e,s,r)}createLogger(e){return new a.Logger("ExtUIClient",e.logLevel)}createStoragePrefix(e){return a.createStoragePrefixWithNamespace(e.basePath,a.STORAGE_PREFIXES.EXT)}createExtClient(e,t){return new k({authClientId:this.authClientId,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new M({authClientId:e,authServerUrl:t.authServerUrl,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs,storage:t.storage,initialTokens:t.initialTokens},r)}}const D=["ggedphdcbekjlomjaidbajglgihbeaon"],B=["bjdjhiombmfbcoeojijpfckljjghmjbf"],R="production",S=class S{constructor(e,t){this.isAuthenticating=!1,this.isReauthorizing=!1,this.state="setup",this.listenersInitialized=!1,this.refreshPromise=null,this.activeStreamPorts=new Map,this.authClientId=e,this.authServerUrl=t?.authServerUrl||"https://id.getbodhi.app/realms/bodhi",this.extensionId=t?.extensionId,this.logger=new a.Logger("BodhiExtClient",t?.logLevel||"warn"),this.attempts=t?.attempts??A,this.attemptWaitMs=t?.attemptWaitMs??w,this.attemptTimeout=t?.attemptTimeout??X,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),S.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",r);return S.base64UrlEncode(i)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=R!=="production"?D:B;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 s={type:T.MESSAGE_TYPES.EXT_REQUEST,requestId:crypto.randomUUID(),request:{action:"get_extension_id"}};this.logger.debug(`[Ext2Ext/Discovery] Sending message to ${e}:`,s),chrome.runtime.sendMessage(e,s,o=>{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}:`,o);const n=o;n&&n.type===T.MESSAGE_TYPES.EXT_RESPONSE?(this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`),t(!0)):(this.logger.error(`[Ext2Ext/Discovery] Invalid response from ${e}:`,o),r(new Error("Invalid response")))})}catch(s){this.logger.error(`[Ext2Ext/Discovery] Exception pinging ${e}:`,s),clearTimeout(i),r(s)}})}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 s=this.getExtensionIdsForEnvironment();this.logger.debug(`[Ext2Ext/Discovery] Will try ${s.length} extension(s):`,s);for(const h of s){for(let c=1;c<=t;c++){this.logger.debug(`[Ext2Ext/Discovery] Trying ${h} - attempt ${c}/${t}`);try{return await this.pingExtension(h),this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${h} on attempt ${c}`),{success:!0,extensionId:h}}catch(u){this.logger.debug(`[Ext2Ext/Discovery] Attempt ${c} failed for ${h}: ${u instanceof Error?u.message:"Unknown error"}`),c<t&&await this.sleep(r)}}this.logger.warn(`[Ext2Ext/Discovery] ✗ Not found: ${h} after ${t} attempts`)}const o=s.join(", "),n=`Extension not found. Tried ${s.length} IDs with ${t} attempts each: ${o}`;return this.logger.error(`[Ext2Ext/Discovery] ${n}`),{success:!1,error:n}}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!==E.EXT2EXT_CLIENT_REQUEST&&e.type!==E.EXT2EXT_CLIENT_API_REQUEST?!1:this.state!=="ready"&&!(e.type===E.EXT2EXT_CLIENT_REQUEST&&e.request.action===_.DISCOVER_EXTENSION)?(e.type===E.EXT2EXT_CLIENT_REQUEST?r({type:E.EXT2EXT_CLIENT_RESPONSE,requestId:e.requestId,response:{error:{message:this.createErrorClientNotInitialized(e),type:"NOT_INITIALIZED"}}}):r({type:E.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)),this.registerStreamPortListener(f,E.EXT2EXT_CLIENT_STREAM_REQUEST,E.EXT2EXT_CLIENT_STREAM_ERROR,(e,t)=>this.handleStreamRequest(e,t)),this.registerStreamPortListener(y,E.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,(e,t)=>this.handleStreamTextRequest(e,t)),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:E.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 s=await this._getAccessTokenRaw();if(!s)return{type:E.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"auth_error"}};r={...r,Authorization:`Bearer ${s}`},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:E.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,response:i}}catch(r){return this.logger.error("[BodhiExtClient] API request failed:",r),{type:E.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:s}=r;this.logger.debug(`[BodhiExtClient] Handling action: ${i}`);try{let o={};switch(i){case _.DISCOVER_EXTENSION:{const n=s;await this.init(n),this.logger.info("[BodhiExtClient] Discovery successful:",{extensionId:this.extensionId,environment:R}),o={extensionId:this.extensionId,environment:R};break}case _.SET_EXTENSION_ID:{const{extensionId:n}=s;this.extensionId=n,this.state="ready",this.logger.info("[BodhiExtClient] Extension ID set:",{extensionId:n}),o={success:!0};break}case _.GET_EXTENSION_ID:{const n=await this.sendExtRequestRaw(T.EXT_ACTIONS.GET_EXTENSION_ID,s);return T.isExtError(n.response)?{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:n.response.error.message||`Extension request failed to get extension ID: ${JSON.stringify(n.response)}`,type:n.response.error.type}}}:{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:n.response}}case _.LOGIN:{const n=s;await this.login(n),this.broadcastAuthStateChange();break}case _.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case _.GET_AUTH_STATE:o={authState:await this.getAuthState()};break;default:return{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown action: ${i}`,type:"UNKNOWN_ACTION"}}}}return{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:o}}catch(o){return this.logger.error("[BodhiExtClient] Unexpected error:",o),{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:o instanceof Error?o.message:`Unexpected error: ${JSON.stringify(o)}`}}}}}async handleAction(e){switch(e.type){case E.EXT2EXT_CLIENT_API_REQUEST:return this.handleApiRequest(e);case E.EXT2EXT_CLIENT_REQUEST:return this.handleExtClientRequest(e);default:{const{requestId:t}=e;return this.logger.error("[BodhiExtClient] Unknown message type:",e.type),{type:E.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"&&!e?.reauthorize)){this.isAuthenticating=!0,this.isReauthorizing=e?.reauthorize??!1;try{if(!this.extensionId)throw new Error("Extension not discovered. Please detect Bodhi extension before login.");const r=e?.userRole??"scope_user_user",i=chrome.identity.getRedirectURL("callback"),s=S.generateCodeVerifier(),o=await S.generateCodeChallenge(s),n=S.generateCodeVerifier();await chrome.storage.session.set({codeVerifier:s,state:n,authInProgress:!0});const h=a.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:i,scope:a.BASE_OAUTH_SCOPE,state:n,codeChallenge:o}),c=a.buildErrorUrl(i),u=new a.AccessRequestBuilder(this.authClientId).requestedRole(r);e?.requested&&u.requested(e.requested);const l=await this.requestAccess(u.build()),{review_url:d}=a.unwrapResponse(l),g=a.buildReviewUrl(d,h,c),p=await this.launchReview(g);await this.completeOAuthRedirect(p)}finally{this.isAuthenticating=!1,this.isReauthorizing=!1}}}async exchangeCodeForTokens(e){if((await this.getAuthState()).status==="authenticated"&&!this.isReauthorizing)return;const{codeVerifier:r}=await chrome.storage.session.get("codeVerifier"),i=chrome.identity.getRedirectURL("callback"),s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:i,client_id:this.authClientId,code_verifier:r})});if(!s.ok){const n=await s.text();throw new Error(`Token exchange failed: ${s.status} ${n}`)}const o=await s.json();await this.storeTokens({accessToken:o.access_token,refreshToken:o.refresh_token,idToken:o.id_token,expiresIn:o.expires_in})}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};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,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}}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)}launchReview(e){return new Promise((t,r)=>{chrome.identity.launchWebAuthFlow({url:e,interactive:!0},async i=>{if(await chrome.storage.session.set({authInProgress:!1}),chrome.runtime.lastError){await chrome.storage.session.remove(["codeVerifier","state"]),r(chrome.runtime.lastError);return}if(!i){await chrome.storage.session.remove(["codeVerifier","state"]),r(a.createOperationError("oauth_error","No redirect URL received"));return}t(i)})})}async completeOAuthRedirect(e){const t=new URL(e),r=t.searchParams.get("error");if(r)throw await chrome.storage.session.remove(["codeVerifier","state"]),t.searchParams.get("error_source")==="bodhi"&&a.throwAccessRequestDenialError(r==="access_denied"?"denied":r),a.createOperationError("oauth_error",t.searchParams.get("error_description")??r);const i=t.searchParams.get("code"),s=t.searchParams.get("state"),{state:o}=await chrome.storage.session.get("state");if(!s||s!==o)throw await chrome.storage.session.remove(["codeVerifier","state"]),a.createOperationError("oauth_error","State mismatch");if(!i)throw await chrome.storage.session.remove(["codeVerifier","state"]),a.createOperationError("oauth_error","No authorization code");if(await this.exchangeCodeForTokens(i),await chrome.storage.session.remove(["codeVerifier","state"]),(await this.getAuthState()).status!=="authenticated")throw a.createOperationError("oauth_error","Login failed")}async sendExtRequest(e,t){const r=await this.sendExtRequestRaw(e,t);if(T.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 a.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 s=crypto.randomUUID(),o={type:T.MESSAGE_TYPES.API_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i}};return this.logger.debug(`[BodhiExtClient] Request ID: ${s}, Extension: ${this.extensionId}`),new Promise((n,h)=>{try{chrome.runtime.sendMessage(this.extensionId,o,c=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${s}:`,chrome.runtime.lastError),h(new a.BodhiError("extension_error",chrome.runtime.lastError.message??"Chrome runtime error"));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${s}:`,c),!c){this.logger.error(`[BodhiExtClient] No response received for request ${s}`),h(new a.BodhiError("extension_error","No response from extension"));return}c.type===T.MESSAGE_TYPES.API_RESPONSE&&c.requestId===s?"error"in c?(this.logger.error(`[BodhiExtClient] API error for ${s}:`,c.error),h(new a.BodhiError(c.error.type||"extension_error",c.error.message))):(this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${s}`),n(c.response)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${s}:`,c),h(new a.BodhiError("extension_error","Invalid response format")))})}catch(c){this.logger.error(`[BodhiExtClient] Exception sending message for ${s}:`,c),h(c)}})}async sendExtRequestRaw(e,t){if(!this.extensionId)throw new a.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:T.MESSAGE_TYPES.EXT_REQUEST,requestId:r,request:{action:e,params:t}};return this.logger.debug(`[BodhiExtClient] Request ID: ${r}, Extension: ${this.extensionId}`),new Promise((s,o)=>{try{chrome.runtime.sendMessage(this.extensionId,i,n=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${r}:`,chrome.runtime.lastError),o(new Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${r}:`,n),!n){this.logger.error(`[BodhiExtClient] No response received for request ${r}`),o(new Error("No response from extension"));return}n.type===T.MESSAGE_TYPES.EXT_RESPONSE&&n.requestId===r?(this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`),s(n)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${r}:`,n),o(new a.BodhiError("extension_error","Invalid response format")))})}catch(n){this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`,n),o(n)}})}registerStreamPortListener(e,t,r,i){chrome.runtime.onConnect.addListener(s=>{s.name===e&&(this.logger.info(`[BodhiExtClient] Port connected: ${e}`),s.onMessage.addListener(async o=>{if(o.type!==t){this.logger.warn(`[BodhiExtClient] Unknown message type on ${e}:`,o.type),s.postMessage({type:r,requestId:o.requestId,error:{message:"Unknown message type",type:"extension_error"}});return}await i(s,o)}),s.onDisconnect.addListener(()=>{this.logger.info(`[BodhiExtClient] Port disconnected: ${e}`)}))})}async handleGenericStreamRelay(e,t,r,i,s,o,n){if(this.logger.debug("[BodhiExtClient] Processing stream relay:",{requestId:t,method:r.method,endpoint:r.endpoint,bodhiPortName:i}),!this.extensionId){e.postMessage({type:o,requestId:t,error:{message:"Client not initialized (no extensionId)",type:"extension_error"}});return}try{let h={...r.headers};if(r.authenticated!==!1){const d=await this._getAccessTokenRaw();if(!d){e.postMessage({type:o,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"extension_error"}});return}h={...h,Authorization:`Bearer ${d}`},this.logger.debug("[BodhiExtClient] Injected auth token for stream relay")}const c=chrome.runtime.connect(this.extensionId,{name:i});this.activeStreamPorts.set(t,c);const u=setTimeout(()=>{this.activeStreamPorts.has(t)&&(this.logger.error(`[BodhiExtClient] Stream timeout for ${t}`),e.postMessage({type:o,requestId:t,error:{message:"Stream request timed out",type:"timeout_error"}}),this.cleanupStreamPort(t))},S.STREAM_TIMEOUT);c.onMessage.addListener(d=>{n(d,e,t)&&(clearTimeout(u),this.cleanupStreamPort(t))}),c.onDisconnect.addListener(()=>{clearTimeout(u),this.activeStreamPorts.has(t)&&(this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${t}`),e.postMessage({type:o,requestId:t,error:{message:"Connection to Bodhi extension closed unexpectedly",type:"network_error"}}),this.activeStreamPorts.delete(t))});const l={type:s,requestId:t,request:{method:r.method,endpoint:r.endpoint,body:r.body,headers:h}};this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",l),c.postMessage(l)}catch(h){const c=h;this.logger.error("[BodhiExtClient] Stream relay error:",JSON.stringify(c.message)),e.postMessage({type:o,requestId:t,error:{message:`uncaught error: ${JSON.stringify({error:c,message:c.message})}`,type:"extension_error"}})}}cleanupStreamPort(e){const t=this.activeStreamPorts.get(e);if(t){try{t.disconnect()}catch{}this.activeStreamPorts.delete(e)}}async handleStreamRequest(e,t){const{requestId:r,request:i}=t;await this.handleGenericStreamRelay(e,r,i,T.BODHI_STREAM_PORT,T.MESSAGE_TYPES.STREAM_REQUEST,E.EXT2EXT_CLIENT_STREAM_ERROR,(s,o,n)=>{if(T.isStreamChunk(s)){const h=s.response,c=h.body;return h.status>=400?(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:n,response:h}),!1):c?.done?(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_DONE,requestId:n}),this.logger.info(`[BodhiExtClient] Stream complete for ${n}`),!0):(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_CHUNK,requestId:n,response:h}),!1)}else{if(T.isStreamApiError(s))return this.logger.error(`[BodhiExtClient] Stream API error for ${n}: ${s.response.status}`),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:n,response:s.response}),!1;if(T.isStreamError(s))return this.logger.error(`[BodhiExtClient] Stream error for ${n}:`,s.error.message),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_ERROR,requestId:n,error:{message:`stream error: ${JSON.stringify(s)}`,type:"extension_error"}}),!0}return!1})}async handleStreamTextRequest(e,t){const{requestId:r,request:i}=t;await this.handleGenericStreamRelay(e,r,i,T.BODHI_STREAM_TEXT_PORT,T.MESSAGE_TYPES.STREAM_TEXT_REQUEST,E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,(s,o,n)=>{switch(s.type){case T.MESSAGE_TYPES.STREAM_TEXT_START:return o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_START,requestId:n,status:s.status,headers:s.headers}),!1;case T.MESSAGE_TYPES.STREAM_TEXT_CHUNK:return o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK,requestId:n,chunk:s.chunk}),!1;case T.MESSAGE_TYPES.STREAM_TEXT_DONE:return this.logger.info(`[BodhiExtClient] Stream text complete for ${n}`),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_DONE,requestId:n}),!0;case T.MESSAGE_TYPES.STREAM_TEXT_ERROR:return this.logger.error(`[BodhiExtClient] Stream text error for ${n}:`,s.error.message),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,requestId:n,error:s.error}),!0}return!1})}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 a.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"),a.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(s=>"%"+("00"+s.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)}`}};S.STREAM_TIMEOUT=6e4;let I=S;const q="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>a.InMemoryStorage});exports.BodhiExtClient=I;exports.ChromeSessionStorageAdapter=P;exports.DEFAULT_API_TIMEOUT_MS=N;exports.DISCOVERY_ATTEMPTS=A;exports.DISCOVERY_ATTEMPT_TIMEOUT=X;exports.DISCOVERY_ATTEMPT_WAIT_MS=w;exports.DISCOVERY_TIMEOUT_MS=C;exports.EXT2EXT_CLIENT_ACTIONS=_;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=E;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT2EXT_CLIENT_STREAM_TEXT_PORT=y;exports.EXT_BUILD_MODE=q;exports.ExtUIClient=U;exports.isExtClientApiError=O;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("@bodhiapp/bodhi-js-core"),l=require("@bodhiapp/bodhi-browser-types"),E={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",EXT2EXT_CLIENT_STREAM_TEXT_REQUEST:"EXT2EXT_CLIENT_STREAM_TEXT_REQUEST",EXT2EXT_CLIENT_STREAM_TEXT_START:"EXT2EXT_CLIENT_STREAM_TEXT_START",EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:"EXT2EXT_CLIENT_STREAM_TEXT_CHUNK",EXT2EXT_CLIENT_STREAM_TEXT_DONE:"EXT2EXT_CLIENT_STREAM_TEXT_DONE",EXT2EXT_CLIENT_STREAM_TEXT_ERROR:"EXT2EXT_CLIENT_STREAM_TEXT_ERROR"},f="ext2ext-client-stream",y="ext2ext-client-stream-text",_={LOGIN:"login",LOGOUT:"logout",GET_AUTH_STATE:"getAuthState",DISCOVER_EXTENSION:"discoverBodhiExtension",GET_EXTENSION_ID:"get_extension_id",SET_EXTENSION_ID:"setExtensionId"},C=5e3,A=3,w=500,X=500,N=3e4;function O(m){return"error"in m}class P{async get(e){const r=(await chrome.storage.session.get(e))[e];return r!==void 0?String(r):null}async set(e){await chrome.storage.session.set(e)}async remove(e){await chrome.storage.session.remove(e)}}class M extends a.DirectClientBase{constructor(e,t){const r=a.createStoragePrefixWithNamespace(e.basePath,a.STORAGE_PREFIXES.EXT_DIRECT),n={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectExtClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new P,initialTokens:e.initialTokens};super(n,t)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated"&&!e?.exchange)return t;const r=e?.userRole??"scope_user_user",n=this._getRedirectUri();e?.onProgress?.("requesting");const s=a.generateCodeVerifier(),o=await a.generateCodeChallenge(s),i=a.generateCodeVerifier();await this._storageSet({[this.storageKeys.CODE_VERIFIER]:s,[this.storageKeys.STATE]:i});const u=a.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:n,scope:a.BASE_OAUTH_SCOPE,state:i,codeChallenge:o}),c=a.buildErrorUrl(n),h=new a.AccessRequestBuilder(this.authClientId).requestedRole(r);e?.requested&&h.requested(e.requested),e?.exchange&&h.exchange(!0);const d=await this.requestAccess(h.build()),{review_url:T}=a.unwrapResponse(d),g=a.buildReviewUrl(T,u,c);e?.onProgress?.("reviewing");const p=await this.launchReview(g);return e?.onProgress?.("authenticating"),this.completeOAuthRedirect(p)}launchReview(e){return new Promise((t,r)=>{chrome.identity.launchWebAuthFlow({url:e,interactive:!0},n=>{if(chrome.runtime.lastError){r(chrome.runtime.lastError);return}if(!n){r(a.createOperationError("oauth_error","No redirect URL received"));return}t(n)})})}async completeOAuthRedirect(e){const t=new URL(e),r=t.searchParams.get("error");if(r)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),t.searchParams.get("error_source")==="bodhi"&&a.throwAccessRequestDenialError(r==="access_denied"?"denied":r),a.createOperationError("oauth_error",t.searchParams.get("error_description")??r);const n=t.searchParams.get("code"),s=t.searchParams.get("state"),o=await this._storageGet(this.storageKeys.STATE);if(!s||s!==o)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","State mismatch");if(!n)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","No authorization code");await this.exchangeCodeForTokens(n);const i=await this.getAuthState();if(i.status!=="authenticated")throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","Login failed");return this.setAuthState(i),i}_getRedirectUri(){return chrome.identity.getRedirectURL("callback")}}class k{constructor(e={},t){this.state={type:"extension",extension:"not-initialized",extensionId:null,server:a.PENDING_EXTENSION_READY},this.extensionId=null,this.broadcastListenerActive=!1,this.config=e,this.logger=new a.Logger("ExtClient",e?.logLevel||"warn"),this.onStateChange=t??a.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===E.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 n=a.createExtensionStateNotInitialized();return this.setState(n),n}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??C,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 o={attempts:this.config.initParams?.extension?.attempts,attemptWaitMs:this.config.initParams?.extension?.attemptWaitMs,attemptTimeout:this.config.initParams?.extension?.attemptTimeout},i=await this.sendExtMessageWithTimeout(_.DISCOVER_EXTENSION,o,t);this.extensionId=i.extensionId,this.logger.info("Extension discovered:",this.extensionId)}this.setupBroadcastListener()}const n={type:"extension",extension:"ready",extensionId:this.extensionId,server:a.PENDING_EXTENSION_READY};let s=a.PENDING_EXTENSION_READY;if(e.testConnection)try{s=await this.getServerState(),this.logger.info("Server connectivity tested, state:",s.status)}catch(o){this.logger.error("Failed to get server state:",o),s=a.BACKEND_SERVER_NOT_REACHABLE}return this.setState({...n,server:s}),this.state}catch(n){this.logger.error("Failed to initialize extension:",n),this.extensionId=null;const s=a.createExtensionStateNotFound();return this.setState(s),this.state}}async sendExtMessageWithTimeout(e,t,r=1e4){const n=new Promise((s,o)=>setTimeout(()=>o(new Error("Timeout")),r));return Promise.race([this.sendExtRequest(e,t),n])}async sendExtRequest(e,t){try{const r=this.generateRequestId(),n=await chrome.runtime.sendMessage({type:E.EXT2EXT_CLIENT_REQUEST,requestId:r,request:{action:e,params:t}});if(!n)throw a.createOperationError("extension_error","No response from background script");if(n.type!==E.EXT2EXT_CLIENT_RESPONSE)throw a.createOperationError("extension_error","Invalid response type from background script");const s=n.response;if(l.isExtError(s)){const o=s.error.type||"extension_error";throw a.createOperationError(o,s.error.message)}return s}catch(r){throw r instanceof a.BodhiError?r:a.createOperationError("extension_error",r instanceof Error?r.message:"Unknown error occurred")}}async sendRawApiMessage(e,t,r,n,s){const o=this.generateRequestId();return await chrome.runtime.sendMessage({type:E.EXT2EXT_CLIENT_API_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:n,authenticated:s}})}async sendApiRequest(e,t,r,n,s){try{const o=new Promise((u,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)),i=await Promise.race([this.sendRawApiMessage(e,t,r,n,s),o]);if(O(i)){const u=i.error.type||"extension_error";throw new a.BodhiError(u,i.error.message)}return i.response}catch(o){if(o instanceof a.BodhiError)throw o;const i=o instanceof Error?o.message:String(o);throw new a.BodhiError("network_error",i)}}async login(e){return new Promise((t,r)=>{const n=async s=>{if(s&&typeof s=="object"&&"type"in s&&s.type==="EXT2EXT_CLIENT_BROADCAST"&&"event"in s&&s.event==="authStateChanged"){chrome.runtime.onMessage.removeListener(n);try{const o=await this.getAuthState();if(a.isAuthError(o)){r(a.createOperationError("auth_error",`Login failed: ${o.error?.message}`));return}if(o.status!=="authenticated"){r(a.createOperationError("auth_error","Login failed: User is not logged in"));return}this.setAuthState(o),t(o)}catch(o){r(o)}}};chrome.runtime.onMessage.addListener(n),this.sendExtRequest(_.LOGIN,e).catch(s=>{chrome.runtime.onMessage.removeListener(n),r(s)})})}async logout(){await this.sendExtRequest(_.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(_.GET_AUTH_STATE)).authState:a.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){let e;try{e=await this.sendApiRequest("GET","/bodhi/v1/info")}catch(s){const o=s instanceof Error?s.message:"Connection failed",i=s instanceof a.BodhiError?s.code:"extension_error";return{status:"not-reachable",version:null,error:{message:o,type:i}}}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",n={deployment:t.deployment??null,client_id:t.client_id??null};switch(t.status){case"ready":return{status:"ready",version:r,error:null,...n};case"setup":return{status:"setup",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Setup required",type:"extension_error"},...n};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"},...n};case"error":return{status:"error",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Server error",type:"extension_error"},...n};default:return{status:"not-reachable",version:null,error:{message:"Unknown server status",type:"extension_error"}}}}async*stream(e,t,r,n,s=!0){const o=this.generateRequestId();this.logger.debug("Starting stream",{method:e,endpoint:t,requestId:o});const i=chrome.runtime.connect({name:f}),c=new ReadableStream({start:h=>{i.onMessage.addListener(d=>{if(d.requestId===o)switch(d.type){case E.EXT2EXT_CLIENT_STREAM_DONE:this.logger.debug("Stream complete",{requestId:o}),h.close(),i.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_ERROR:this.logger.error("Stream error",{requestId:o,error:JSON.stringify(d.error)}),h.error(new a.BodhiError("extension_error",d.error.message)),i.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_API_ERROR:{const T=d;this.logger.error("Stream API error",{requestId:o,error:T.response.body?.error}),h.error(new a.BodhiApiError(T.response.status,T.response.body,T.response.body?.error?.message||"API error")),i.disconnect();break}case E.EXT2EXT_CLIENT_STREAM_CHUNK:{const T=d;l.isApiSuccessResponse(T.response)&&h.enqueue(T.response.body);break}}}),i.onDisconnect.addListener(()=>{this.logger.debug("Port disconnected",{requestId:o});try{h.error(new a.BodhiError("connection_closed","Connection closed unexpectedly"))}catch{}}),i.postMessage({type:E.EXT2EXT_CLIENT_STREAM_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:n,authenticated:s}})}}).getReader();try{for(;;){const{done:h,value:d}=await c.read();if(h){this.logger.debug("Stream iteration complete");break}yield d}}finally{c.releaseLock()}}async streamText(e,t,r,n,s=!0){const o=this.generateRequestId();return new Promise((i,u)=>{let c,h=!1;const d=new ReadableStream({start:g=>{c=g}}),T=chrome.runtime.connect({name:y});T.onMessage.addListener(g=>{if(g.requestId===o)switch(g.type){case E.EXT2EXT_CLIENT_STREAM_TEXT_START:{h=!0;async function*p(v){const x=v.getReader();try{for(;;){const{done:b,value:L}=await x.read();if(b)break;yield L}}finally{x.releaseLock()}}i({status:g.status,headers:g.headers,body:p(d)});break}case E.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:c.enqueue(g.chunk);break;case E.EXT2EXT_CLIENT_STREAM_TEXT_DONE:c.close(),T.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR:{const p=a.createOperationError("extension_error",g.error.message);h?c.error(p):u(p),T.disconnect();break}}}),T.onDisconnect.addListener(()=>{if(!h)u(a.createOperationError("extension_error","Connection closed unexpectedly"));else try{c.error(a.createOperationError("extension_error","Connection closed unexpectedly"))}catch{}}),T.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:n,authenticated:s}})})}get chat(){return this._chat??=new a.Chat(this)}get models(){return this._models??=new a.Models(this)}get embeddings(){return this._embeddings??=new a.Embeddings(this)}get mcps(){return this._mcps??=new a.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}serialize(){return{extensionId:this.extensionId??void 0}}async debug(){return{type:"ExtClient",state:this.state,authState:await this.getAuthState()}}}class U extends a.BaseFacadeClient{constructor(e,t,r){const n=t||{},s={basePath:n.basePath||"/",authServerUrl:n.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:n.logLevel||"warn",apiTimeoutMs:n.apiTimeoutMs,storage:n.storage,initialTokens:n.initialTokens,initParams:n.initParams};super(e,s,r)}createLogger(e){return new a.Logger("ExtUIClient",e.logLevel)}createStoragePrefix(e){return a.createStoragePrefixWithNamespace(e.basePath,a.STORAGE_PREFIXES.EXT)}createExtClient(e,t){return new k({authClientId:this.authClientId,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new M({authClientId:e,authServerUrl:t.authServerUrl,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs,storage:t.storage,initialTokens:t.initialTokens},r)}}const D=["ggedphdcbekjlomjaidbajglgihbeaon"],B=["bjdjhiombmfbcoeojijpfckljjghmjbf"],R="production",S=class S{constructor(e,t){this.isAuthenticating=!1,this.isExchanging=!1,this.state="setup",this.listenersInitialized=!1,this.refreshPromise=null,this.activeStreamPorts=new Map,this.authClientId=e,this.authServerUrl=t?.authServerUrl||"https://id.getbodhi.app/realms/bodhi",this.extensionId=t?.extensionId,this.logger=new a.Logger("BodhiExtClient",t?.logLevel||"warn"),this.attempts=t?.attempts??A,this.attemptWaitMs=t?.attemptWaitMs??w,this.attemptTimeout=t?.attemptTimeout??X,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),S.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",r);return S.base64UrlEncode(n)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=R!=="production"?D:B;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 n=setTimeout(()=>{this.logger.debug(`[Ext2Ext/Discovery] Timeout waiting for extension ${e}`),r(new Error("Timeout"))},this.attemptTimeout);try{const s={type:l.MESSAGE_TYPES.EXT_REQUEST,requestId:crypto.randomUUID(),request:{action:"get_extension_id"}};this.logger.debug(`[Ext2Ext/Discovery] Sending message to ${e}:`,s),chrome.runtime.sendMessage(e,s,o=>{if(clearTimeout(n),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}:`,o);const i=o;i&&i.type===l.MESSAGE_TYPES.EXT_RESPONSE?(this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`),t(!0)):(this.logger.error(`[Ext2Ext/Discovery] Invalid response from ${e}:`,o),r(new Error("Invalid response")))})}catch(s){this.logger.error(`[Ext2Ext/Discovery] Exception pinging ${e}:`,s),clearTimeout(n),r(s)}})}sleep(e){return new Promise(t=>setTimeout(t,e))}async discoverBodhiExtension(e){const{attempts:t,attemptWaitMs:r,attemptTimeout:n}=e;this.logger.info(`[Ext2Ext/Discovery] Starting discovery: ${t} attempts per ID, ${n}ms timeout, ${r}ms between attempts`);const s=this.getExtensionIdsForEnvironment();this.logger.debug(`[Ext2Ext/Discovery] Will try ${s.length} extension(s):`,s);for(const u of s){for(let c=1;c<=t;c++){this.logger.debug(`[Ext2Ext/Discovery] Trying ${u} - attempt ${c}/${t}`);try{return await this.pingExtension(u),this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${u} on attempt ${c}`),{success:!0,extensionId:u}}catch(h){this.logger.debug(`[Ext2Ext/Discovery] Attempt ${c} failed for ${u}: ${h instanceof Error?h.message:"Unknown error"}`),c<t&&await this.sleep(r)}}this.logger.warn(`[Ext2Ext/Discovery] ✗ Not found: ${u} after ${t} attempts`)}const o=s.join(", "),i=`Extension not found. Tried ${s.length} IDs with ${t} attempts each: ${o}`;return this.logger.error(`[Ext2Ext/Discovery] ${i}`),{success:!1,error:i}}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!==E.EXT2EXT_CLIENT_REQUEST&&e.type!==E.EXT2EXT_CLIENT_API_REQUEST?!1:this.state!=="ready"&&!(e.type===E.EXT2EXT_CLIENT_REQUEST&&e.request.action===_.DISCOVER_EXTENSION)?(e.type===E.EXT2EXT_CLIENT_REQUEST?r({type:E.EXT2EXT_CLIENT_RESPONSE,requestId:e.requestId,response:{error:{message:this.createErrorClientNotInitialized(e),type:"NOT_INITIALIZED"}}}):r({type:E.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 n=await this.handleAction(e);r(n)})(),!0)),this.registerStreamPortListener(f,E.EXT2EXT_CLIENT_STREAM_REQUEST,E.EXT2EXT_CLIENT_STREAM_ERROR,(e,t)=>this.handleStreamRequest(e,t)),this.registerStreamPortListener(y,E.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,(e,t)=>this.handleStreamTextRequest(e,t)),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:E.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 s=await this._getAccessTokenRaw();if(!s)return{type:E.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"auth_error"}};r={...r,Authorization:`Bearer ${s}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const n=await this.sendApiRequest(e.request.method,e.request.endpoint,e.request.body,r);return{type:E.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,response:n}}catch(r){return this.logger.error("[BodhiExtClient] API request failed:",r),{type:E.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:n,params:s}=r;this.logger.debug(`[BodhiExtClient] Handling action: ${n}`);try{let o={};switch(n){case _.DISCOVER_EXTENSION:{const i=s;await this.init(i),this.logger.info("[BodhiExtClient] Discovery successful:",{extensionId:this.extensionId,environment:R}),o={extensionId:this.extensionId,environment:R};break}case _.SET_EXTENSION_ID:{const{extensionId:i}=s;this.extensionId=i,this.state="ready",this.logger.info("[BodhiExtClient] Extension ID set:",{extensionId:i}),o={success:!0};break}case _.GET_EXTENSION_ID:{const i=await this.sendExtRequestRaw(l.EXT_ACTIONS.GET_EXTENSION_ID,s);return l.isExtError(i.response)?{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:i.response.error.message||`Extension request failed to get extension ID: ${JSON.stringify(i.response)}`,type:i.response.error.type}}}:{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:i.response}}case _.LOGIN:{const i=s;await this.login(i),this.broadcastAuthStateChange();break}case _.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case _.GET_AUTH_STATE:o={authState:await this.getAuthState()};break;default:return{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown action: ${n}`,type:"UNKNOWN_ACTION"}}}}return{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:o}}catch(o){return this.logger.error("[BodhiExtClient] Unexpected error:",o),{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:o instanceof Error?o.message:`Unexpected error: ${JSON.stringify(o)}`}}}}}async handleAction(e){switch(e.type){case E.EXT2EXT_CLIENT_API_REQUEST:return this.handleApiRequest(e);case E.EXT2EXT_CLIENT_REQUEST:return this.handleExtClientRequest(e);default:{const{requestId:t}=e;return this.logger.error("[BodhiExtClient] Unknown message type:",e.type),{type:E.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"&&!e?.exchange)){this.isAuthenticating=!0,this.isExchanging=e?.exchange??!1;try{if(!this.extensionId)throw new Error("Extension not discovered. Please detect Bodhi extension before login.");const r=e?.userRole??"scope_user_user",n=chrome.identity.getRedirectURL("callback"),s=S.generateCodeVerifier(),o=await S.generateCodeChallenge(s),i=S.generateCodeVerifier();await chrome.storage.session.set({codeVerifier:s,state:i,authInProgress:!0});const u=a.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:n,scope:a.BASE_OAUTH_SCOPE,state:i,codeChallenge:o}),c=a.buildErrorUrl(n),h=new a.AccessRequestBuilder(this.authClientId).requestedRole(r);e?.requested&&h.requested(e.requested),e?.exchange&&h.exchange(!0);const d=await this.requestAccess(h.build()),{review_url:T}=a.unwrapResponse(d),g=a.buildReviewUrl(T,u,c),p=await this.launchReview(g);await this.completeOAuthRedirect(p)}finally{this.isAuthenticating=!1,this.isExchanging=!1}}}async exchangeCodeForTokens(e){if((await this.getAuthState()).status==="authenticated"&&!this.isExchanging)return;const{codeVerifier:r}=await chrome.storage.session.get("codeVerifier"),n=chrome.identity.getRedirectURL("callback"),s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:n,client_id:this.authClientId,code_verifier:r})});if(!s.ok){const i=await s.text();throw new Error(`Token exchange failed: ${s.status} ${i}`)}const o=await s.json();await this.storeTokens({accessToken:o.access_token,refreshToken:o.refresh_token,idToken:o.id_token,expiresIn:o.expires_in})}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};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,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}}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,void 0,!0)}launchReview(e){return new Promise((t,r)=>{chrome.identity.launchWebAuthFlow({url:e,interactive:!0},async n=>{if(await chrome.storage.session.set({authInProgress:!1}),chrome.runtime.lastError){await chrome.storage.session.remove(["codeVerifier","state"]),r(chrome.runtime.lastError);return}if(!n){await chrome.storage.session.remove(["codeVerifier","state"]),r(a.createOperationError("oauth_error","No redirect URL received"));return}t(n)})})}async completeOAuthRedirect(e){const t=new URL(e),r=t.searchParams.get("error");if(r)throw await chrome.storage.session.remove(["codeVerifier","state"]),t.searchParams.get("error_source")==="bodhi"&&a.throwAccessRequestDenialError(r==="access_denied"?"denied":r),a.createOperationError("oauth_error",t.searchParams.get("error_description")??r);const n=t.searchParams.get("code"),s=t.searchParams.get("state"),{state:o}=await chrome.storage.session.get("state");if(!s||s!==o)throw await chrome.storage.session.remove(["codeVerifier","state"]),a.createOperationError("oauth_error","State mismatch");if(!n)throw await chrome.storage.session.remove(["codeVerifier","state"]),a.createOperationError("oauth_error","No authorization code");if(await this.exchangeCodeForTokens(n),await chrome.storage.session.remove(["codeVerifier","state"]),(await this.getAuthState()).status!=="authenticated")throw a.createOperationError("oauth_error","Login failed")}async sendExtRequest(e,t){const r=await this.sendExtRequestRaw(e,t);if(l.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,n,s=!1){if(!this.extensionId)throw new a.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}:"");let o=n;if(s){const c=await this._getAccessTokenRaw();c&&(o={...n,Authorization:`Bearer ${c}`})}const i=crypto.randomUUID(),u={type:l.MESSAGE_TYPES.API_REQUEST,requestId:i,request:{method:e,endpoint:t,body:r,headers:o}};return this.logger.debug(`[BodhiExtClient] Request ID: ${i}, Extension: ${this.extensionId}`),new Promise((c,h)=>{try{chrome.runtime.sendMessage(this.extensionId,u,d=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${i}:`,chrome.runtime.lastError),h(new a.BodhiError("extension_error",chrome.runtime.lastError.message??"Chrome runtime error"));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${i}:`,d),!d){this.logger.error(`[BodhiExtClient] No response received for request ${i}`),h(new a.BodhiError("extension_error","No response from extension"));return}d.type===l.MESSAGE_TYPES.API_RESPONSE&&d.requestId===i?"error"in d?(this.logger.error(`[BodhiExtClient] API error for ${i}:`,d.error),h(new a.BodhiError(d.error.type||"extension_error",d.error.message))):(this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${i}`),c(d.response)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${i}:`,d),h(new a.BodhiError("extension_error","Invalid response format")))})}catch(d){this.logger.error(`[BodhiExtClient] Exception sending message for ${i}:`,d),h(d)}})}async sendExtRequestRaw(e,t){if(!this.extensionId)throw new a.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(),n={type:l.MESSAGE_TYPES.EXT_REQUEST,requestId:r,request:{action:e,params:t}};return this.logger.debug(`[BodhiExtClient] Request ID: ${r}, Extension: ${this.extensionId}`),new Promise((s,o)=>{try{chrome.runtime.sendMessage(this.extensionId,n,i=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${r}:`,chrome.runtime.lastError),o(new Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${r}:`,i),!i){this.logger.error(`[BodhiExtClient] No response received for request ${r}`),o(new Error("No response from extension"));return}i.type===l.MESSAGE_TYPES.EXT_RESPONSE&&i.requestId===r?(this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`),s(i)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${r}:`,i),o(new a.BodhiError("extension_error","Invalid response format")))})}catch(i){this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`,i),o(i)}})}registerStreamPortListener(e,t,r,n){chrome.runtime.onConnect.addListener(s=>{s.name===e&&(this.logger.info(`[BodhiExtClient] Port connected: ${e}`),s.onMessage.addListener(async o=>{if(o.type!==t){this.logger.warn(`[BodhiExtClient] Unknown message type on ${e}:`,o.type),s.postMessage({type:r,requestId:o.requestId,error:{message:"Unknown message type",type:"extension_error"}});return}await n(s,o)}),s.onDisconnect.addListener(()=>{this.logger.info(`[BodhiExtClient] Port disconnected: ${e}`)}))})}async handleGenericStreamRelay(e,t,r,n,s,o,i){if(this.logger.debug("[BodhiExtClient] Processing stream relay:",{requestId:t,method:r.method,endpoint:r.endpoint,bodhiPortName:n}),!this.extensionId){e.postMessage({type:o,requestId:t,error:{message:"Client not initialized (no extensionId)",type:"extension_error"}});return}try{let u={...r.headers};if(r.authenticated!==!1){const T=await this._getAccessTokenRaw();if(!T){e.postMessage({type:o,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"extension_error"}});return}u={...u,Authorization:`Bearer ${T}`},this.logger.debug("[BodhiExtClient] Injected auth token for stream relay")}const c=chrome.runtime.connect(this.extensionId,{name:n});this.activeStreamPorts.set(t,c);const h=setTimeout(()=>{this.activeStreamPorts.has(t)&&(this.logger.error(`[BodhiExtClient] Stream timeout for ${t}`),e.postMessage({type:o,requestId:t,error:{message:"Stream request timed out",type:"timeout_error"}}),this.cleanupStreamPort(t))},S.STREAM_TIMEOUT);c.onMessage.addListener(T=>{i(T,e,t)&&(clearTimeout(h),this.cleanupStreamPort(t))}),c.onDisconnect.addListener(()=>{clearTimeout(h),this.activeStreamPorts.has(t)&&(this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${t}`),e.postMessage({type:o,requestId:t,error:{message:"Connection to Bodhi extension closed unexpectedly",type:"network_error"}}),this.activeStreamPorts.delete(t))});const d={type:s,requestId:t,request:{method:r.method,endpoint:r.endpoint,body:r.body,headers:u}};this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",d),c.postMessage(d)}catch(u){const c=u;this.logger.error("[BodhiExtClient] Stream relay error:",JSON.stringify(c.message)),e.postMessage({type:o,requestId:t,error:{message:`uncaught error: ${JSON.stringify({error:c,message:c.message})}`,type:"extension_error"}})}}cleanupStreamPort(e){const t=this.activeStreamPorts.get(e);if(t){try{t.disconnect()}catch{}this.activeStreamPorts.delete(e)}}async handleStreamRequest(e,t){const{requestId:r,request:n}=t;await this.handleGenericStreamRelay(e,r,n,l.BODHI_STREAM_PORT,l.MESSAGE_TYPES.STREAM_REQUEST,E.EXT2EXT_CLIENT_STREAM_ERROR,(s,o,i)=>{if(l.isStreamChunk(s)){const u=s.response,c=u.body;return u.status>=400?(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:i,response:u}),!1):c?.done?(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_DONE,requestId:i}),this.logger.info(`[BodhiExtClient] Stream complete for ${i}`),!0):(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_CHUNK,requestId:i,response:u}),!1)}else{if(l.isStreamApiError(s))return this.logger.error(`[BodhiExtClient] Stream API error for ${i}: ${s.response.status}`),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:i,response:s.response}),!1;if(l.isStreamError(s))return this.logger.error(`[BodhiExtClient] Stream error for ${i}:`,s.error.message),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_ERROR,requestId:i,error:{message:`stream error: ${JSON.stringify(s)}`,type:"extension_error"}}),!0}return!1})}async handleStreamTextRequest(e,t){const{requestId:r,request:n}=t;await this.handleGenericStreamRelay(e,r,n,l.BODHI_STREAM_TEXT_PORT,l.MESSAGE_TYPES.STREAM_TEXT_REQUEST,E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,(s,o,i)=>{switch(s.type){case l.MESSAGE_TYPES.STREAM_TEXT_START:return o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_START,requestId:i,status:s.status,headers:s.headers}),!1;case l.MESSAGE_TYPES.STREAM_TEXT_CHUNK:return o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK,requestId:i,chunk:s.chunk}),!1;case l.MESSAGE_TYPES.STREAM_TEXT_DONE:return this.logger.info(`[BodhiExtClient] Stream text complete for ${i}`),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_DONE,requestId:i}),!0;case l.MESSAGE_TYPES.STREAM_TEXT_ERROR:return this.logger.error(`[BodhiExtClient] Stream text error for ${i}:`,s.error.message),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,requestId:i,error:s.error}),!0}return!1})}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 a.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"),a.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,"/"),n=decodeURIComponent(atob(r).split("").map(s=>"%"+("00"+s.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(n)}createErrorClientNotInitialized(e){return`Client not initialized. Extension discovery not triggered nor extensionId set, cannot handle request: ${JSON.stringify(e)}`}};S.STREAM_TIMEOUT=6e4;let I=S;const q="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>a.InMemoryStorage});exports.BodhiExtClient=I;exports.ChromeSessionStorageAdapter=P;exports.DEFAULT_API_TIMEOUT_MS=N;exports.DISCOVERY_ATTEMPTS=A;exports.DISCOVERY_ATTEMPT_TIMEOUT=X;exports.DISCOVERY_ATTEMPT_WAIT_MS=w;exports.DISCOVERY_TIMEOUT_MS=C;exports.EXT2EXT_CLIENT_ACTIONS=_;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=E;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT2EXT_CLIENT_STREAM_TEXT_PORT=y;exports.EXT_BUILD_MODE=q;exports.ExtUIClient=U;exports.isExtClientApiError=O;
@@ -1,6 +1,6 @@
1
- import { DirectClientBase as z, createStoragePrefixWithNamespace as X, STORAGE_PREFIXES as N, generateCodeVerifier as w, generateCodeChallenge as V, buildAuthorizeUrl as v, BASE_OAUTH_SCOPE as L, buildErrorUrl as b, AccessRequestBuilder as P, unwrapResponse as k, buildReviewUrl as O, createOperationError as d, throwAccessRequestDenialError as M, PENDING_EXTENSION_READY as f, Logger as x, NOOP_STATE_CALLBACK as Q, createExtensionStateNotInitialized as G, BACKEND_SERVER_NOT_REACHABLE as K, createExtensionStateNotFound as F, BodhiError as T, INITIAL_AUTH_STATE as H, BodhiApiError as W, Chat as J, Models as Y, Embeddings as j, Mcps as Z, isAuthError as ee, BaseFacadeClient as te, refreshAccessToken as re } from "@bodhiapp/bodhi-js-core";
1
+ import { DirectClientBase as V, createStoragePrefixWithNamespace as X, STORAGE_PREFIXES as N, generateCodeVerifier as w, generateCodeChallenge as z, buildAuthorizeUrl as v, BASE_OAUTH_SCOPE as L, buildErrorUrl as b, AccessRequestBuilder as k, unwrapResponse as P, buildReviewUrl as O, createOperationError as l, throwAccessRequestDenialError as M, PENDING_EXTENSION_READY as f, Logger as y, NOOP_STATE_CALLBACK as Q, createExtensionStateNotInitialized as G, BACKEND_SERVER_NOT_REACHABLE as K, createExtensionStateNotFound as F, BodhiError as T, INITIAL_AUTH_STATE as H, BodhiApiError as W, Chat as J, Models as Y, Embeddings as j, Mcps as Z, isAuthError as ee, BaseFacadeClient as te, refreshAccessToken as re } from "@bodhiapp/bodhi-js-core";
2
2
  import { InMemoryStorage as Ae } from "@bodhiapp/bodhi-js-core";
3
- import { isExtError as y, isApiSuccessResponse as se, MESSAGE_TYPES as _, EXT_ACTIONS as ie, BODHI_STREAM_PORT as oe, isStreamChunk as ne, isStreamApiError as ae, isStreamError as he, BODHI_STREAM_TEXT_PORT as ce } from "@bodhiapp/bodhi-browser-types";
3
+ import { isExtError as x, isApiSuccessResponse as se, MESSAGE_TYPES as _, EXT_ACTIONS as ie, BODHI_STREAM_PORT as ne, isStreamChunk as oe, isStreamApiError as ae, isStreamError as ce, BODHI_STREAM_TEXT_PORT as he } from "@bodhiapp/bodhi-browser-types";
4
4
  const a = {
5
5
  EXT2EXT_CLIENT_REQUEST: "EXT2EXT_CLIENT_REQUEST",
6
6
  EXT2EXT_CLIENT_RESPONSE: "EXT2EXT_CLIENT_RESPONSE",
@@ -42,7 +42,7 @@ class _e {
42
42
  await chrome.storage.session.remove(e);
43
43
  }
44
44
  }
45
- class me extends z {
45
+ class me extends V {
46
46
  constructor(e, t) {
47
47
  const r = X(
48
48
  e.basePath,
@@ -64,24 +64,24 @@ class me extends z {
64
64
  // ============================================================================
65
65
  async login(e) {
66
66
  const t = await this.getAuthState();
67
- if (t.status === "authenticated" && !e?.reauthorize)
67
+ if (t.status === "authenticated" && !e?.exchange)
68
68
  return t;
69
69
  const r = e?.userRole ?? "scope_user_user", o = this._getRedirectUri();
70
70
  e?.onProgress?.("requesting");
71
- const s = w(), i = await V(s), n = w();
71
+ const s = w(), i = await z(s), n = w();
72
72
  await this._storageSet({
73
73
  [this.storageKeys.CODE_VERIFIER]: s,
74
74
  [this.storageKeys.STATE]: n
75
75
  });
76
- const c = v(this.authEndpoints, {
76
+ const u = v(this.authEndpoints, {
77
77
  clientId: this.authClientId,
78
78
  redirectUri: o,
79
79
  scope: L,
80
80
  state: n,
81
81
  codeChallenge: i
82
- }), h = b(o), E = new P(this.authClientId).requestedRole(r);
83
- e?.requested && E.requested(e.requested);
84
- const l = await this.requestAccess(E.build()), { review_url: u } = k(l), g = O(u, c, h);
82
+ }), c = b(o), h = new k(this.authClientId).requestedRole(r);
83
+ e?.requested && h.requested(e.requested), e?.exchange && h.exchange(!0);
84
+ const E = await this.requestAccess(h.build()), { review_url: d } = P(E), g = O(d, u, c);
85
85
  e?.onProgress?.("reviewing");
86
86
  const S = await this.launchReview(g);
87
87
  return e?.onProgress?.("authenticating"), this.completeOAuthRedirect(S);
@@ -94,7 +94,7 @@ class me extends z {
94
94
  return;
95
95
  }
96
96
  if (!o) {
97
- r(d("oauth_error", "No redirect URL received"));
97
+ r(l("oauth_error", "No redirect URL received"));
98
98
  return;
99
99
  }
100
100
  t(o);
@@ -104,16 +104,16 @@ class me extends z {
104
104
  async completeOAuthRedirect(e) {
105
105
  const t = new URL(e), r = t.searchParams.get("error");
106
106
  if (r)
107
- throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), t.searchParams.get("error_source") === "bodhi" && M(r === "access_denied" ? "denied" : r), d("oauth_error", t.searchParams.get("error_description") ?? r);
107
+ throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), t.searchParams.get("error_source") === "bodhi" && M(r === "access_denied" ? "denied" : r), l("oauth_error", t.searchParams.get("error_description") ?? r);
108
108
  const o = t.searchParams.get("code"), s = t.searchParams.get("state"), i = await this._storageGet(this.storageKeys.STATE);
109
109
  if (!s || s !== i)
110
- throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), d("oauth_error", "State mismatch");
110
+ throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), l("oauth_error", "State mismatch");
111
111
  if (!o)
112
- throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), d("oauth_error", "No authorization code");
112
+ throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), l("oauth_error", "No authorization code");
113
113
  await this.exchangeCodeForTokens(o);
114
114
  const n = await this.getAuthState();
115
115
  if (n.status !== "authenticated")
116
- throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), d("oauth_error", "Login failed");
116
+ throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), l("oauth_error", "Login failed");
117
117
  return this.setAuthState(n), n;
118
118
  }
119
119
  _getRedirectUri() {
@@ -127,7 +127,7 @@ class pe {
127
127
  extension: "not-initialized",
128
128
  extensionId: null,
129
129
  server: f
130
- }, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new x("ExtClient", e?.logLevel || "warn"), this.onStateChange = t ?? Q, this.apiTimeoutMs = e.apiTimeoutMs ?? Te, this.authClientId = e.authClientId ?? "";
130
+ }, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new y("ExtClient", e?.logLevel || "warn"), this.onStateChange = t ?? Q, this.apiTimeoutMs = e.apiTimeoutMs ?? Te, this.authClientId = e.authClientId ?? "";
131
131
  }
132
132
  /**
133
133
  * Set client state and notify callback
@@ -268,20 +268,20 @@ class pe {
268
268
  }
269
269
  });
270
270
  if (!o)
271
- throw d("extension_error", "No response from background script");
271
+ throw l("extension_error", "No response from background script");
272
272
  if (o.type !== a.EXT2EXT_CLIENT_RESPONSE)
273
- throw d(
273
+ throw l(
274
274
  "extension_error",
275
275
  "Invalid response type from background script"
276
276
  );
277
277
  const s = o.response;
278
- if (y(s)) {
278
+ if (x(s)) {
279
279
  const i = s.error.type || "extension_error";
280
- throw d(i, s.error.message);
280
+ throw l(i, s.error.message);
281
281
  }
282
282
  return s;
283
283
  } catch (r) {
284
- throw r instanceof T ? r : d(
284
+ throw r instanceof T ? r : l(
285
285
  "extension_error",
286
286
  r instanceof Error ? r.message : "Unknown error occurred"
287
287
  );
@@ -312,8 +312,8 @@ class pe {
312
312
  async sendApiRequest(e, t, r, o, s) {
313
313
  try {
314
314
  const i = new Promise(
315
- (c, h) => setTimeout(
316
- () => h(
315
+ (u, c) => setTimeout(
316
+ () => c(
317
317
  new Error(
318
318
  `[bodhi-js-sdk/ext] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`
319
319
  )
@@ -325,8 +325,8 @@ class pe {
325
325
  i
326
326
  ]);
327
327
  if (ge(n)) {
328
- const c = n.error.type || "extension_error";
329
- throw new T(c, n.error.message);
328
+ const u = n.error.type || "extension_error";
329
+ throw new T(u, n.error.message);
330
330
  }
331
331
  return n.response;
332
332
  } catch (i) {
@@ -351,12 +351,12 @@ class pe {
351
351
  const i = await this.getAuthState();
352
352
  if (ee(i)) {
353
353
  r(
354
- d("auth_error", `Login failed: ${i.error?.message}`)
354
+ l("auth_error", `Login failed: ${i.error?.message}`)
355
355
  );
356
356
  return;
357
357
  }
358
358
  if (i.status !== "authenticated") {
359
- r(d("auth_error", "Login failed: User is not logged in"));
359
+ r(l("auth_error", "Login failed: User is not logged in"));
360
360
  return;
361
361
  }
362
362
  this.setAuthState(i), t(i);
@@ -472,49 +472,49 @@ class pe {
472
472
  endpoint: t,
473
473
  requestId: i
474
474
  });
475
- const n = chrome.runtime.connect({ name: U }), h = new ReadableStream({
476
- start: (E) => {
477
- n.onMessage.addListener((l) => {
478
- if (l.requestId === i)
479
- switch (l.type) {
475
+ const n = chrome.runtime.connect({ name: U }), c = new ReadableStream({
476
+ start: (h) => {
477
+ n.onMessage.addListener((E) => {
478
+ if (E.requestId === i)
479
+ switch (E.type) {
480
480
  case a.EXT2EXT_CLIENT_STREAM_DONE:
481
- this.logger.debug("Stream complete", { requestId: i }), E.close(), n.disconnect();
481
+ this.logger.debug("Stream complete", { requestId: i }), h.close(), n.disconnect();
482
482
  break;
483
483
  case a.EXT2EXT_CLIENT_STREAM_ERROR:
484
484
  this.logger.error("Stream error", {
485
485
  requestId: i,
486
- error: JSON.stringify(l.error)
487
- }), E.error(
486
+ error: JSON.stringify(E.error)
487
+ }), h.error(
488
488
  new T(
489
489
  "extension_error",
490
- l.error.message
490
+ E.error.message
491
491
  )
492
492
  ), n.disconnect();
493
493
  break;
494
494
  case a.EXT2EXT_CLIENT_STREAM_API_ERROR: {
495
- const u = l;
495
+ const d = E;
496
496
  this.logger.error("Stream API error", {
497
497
  requestId: i,
498
- error: u.response.body?.error
499
- }), E.error(
498
+ error: d.response.body?.error
499
+ }), h.error(
500
500
  new W(
501
- u.response.status,
502
- u.response.body,
503
- u.response.body?.error?.message || "API error"
501
+ d.response.status,
502
+ d.response.body,
503
+ d.response.body?.error?.message || "API error"
504
504
  )
505
505
  ), n.disconnect();
506
506
  break;
507
507
  }
508
508
  case a.EXT2EXT_CLIENT_STREAM_CHUNK: {
509
- const u = l;
510
- se(u.response) && E.enqueue(u.response.body);
509
+ const d = E;
510
+ se(d.response) && h.enqueue(d.response.body);
511
511
  break;
512
512
  }
513
513
  }
514
514
  }), n.onDisconnect.addListener(() => {
515
515
  this.logger.debug("Port disconnected", { requestId: i });
516
516
  try {
517
- E.error(new T("connection_closed", "Connection closed unexpectedly"));
517
+ h.error(new T("connection_closed", "Connection closed unexpectedly"));
518
518
  } catch {
519
519
  }
520
520
  }), n.postMessage({
@@ -526,15 +526,15 @@ class pe {
526
526
  }).getReader();
527
527
  try {
528
528
  for (; ; ) {
529
- const { done: E, value: l } = await h.read();
530
- if (E) {
529
+ const { done: h, value: E } = await c.read();
530
+ if (h) {
531
531
  this.logger.debug("Stream iteration complete");
532
532
  break;
533
533
  }
534
- yield l;
534
+ yield E;
535
535
  }
536
536
  } finally {
537
- h.releaseLock();
537
+ c.releaseLock();
538
538
  }
539
539
  }
540
540
  /**
@@ -543,18 +543,18 @@ class pe {
543
543
  */
544
544
  async streamText(e, t, r, o, s = !0) {
545
545
  const i = this.generateRequestId();
546
- return new Promise((n, c) => {
547
- let h, E = !1;
548
- const l = new ReadableStream({
546
+ return new Promise((n, u) => {
547
+ let c, h = !1;
548
+ const E = new ReadableStream({
549
549
  start: (g) => {
550
- h = g;
550
+ c = g;
551
551
  }
552
- }), u = chrome.runtime.connect({ name: D });
553
- u.onMessage.addListener((g) => {
552
+ }), d = chrome.runtime.connect({ name: D });
553
+ d.onMessage.addListener((g) => {
554
554
  if (g.requestId === i)
555
555
  switch (g.type) {
556
556
  case a.EXT2EXT_CLIENT_STREAM_TEXT_START: {
557
- E = !0;
557
+ h = !0;
558
558
  async function* S(q) {
559
559
  const C = q.getReader();
560
560
  try {
@@ -570,33 +570,33 @@ class pe {
570
570
  n({
571
571
  status: g.status,
572
572
  headers: g.headers,
573
- body: S(l)
573
+ body: S(E)
574
574
  });
575
575
  break;
576
576
  }
577
577
  case a.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:
578
- h.enqueue(g.chunk);
578
+ c.enqueue(g.chunk);
579
579
  break;
580
580
  case a.EXT2EXT_CLIENT_STREAM_TEXT_DONE:
581
- h.close(), u.disconnect();
581
+ c.close(), d.disconnect();
582
582
  break;
583
583
  case a.EXT2EXT_CLIENT_STREAM_TEXT_ERROR: {
584
- const S = d("extension_error", g.error.message);
585
- E ? h.error(S) : c(S), u.disconnect();
584
+ const S = l("extension_error", g.error.message);
585
+ h ? c.error(S) : u(S), d.disconnect();
586
586
  break;
587
587
  }
588
588
  }
589
- }), u.onDisconnect.addListener(() => {
590
- if (!E)
591
- c(d("extension_error", "Connection closed unexpectedly"));
589
+ }), d.onDisconnect.addListener(() => {
590
+ if (!h)
591
+ u(l("extension_error", "Connection closed unexpectedly"));
592
592
  else
593
593
  try {
594
- h.error(
595
- d("extension_error", "Connection closed unexpectedly")
594
+ c.error(
595
+ l("extension_error", "Connection closed unexpectedly")
596
596
  );
597
597
  } catch {
598
598
  }
599
- }), u.postMessage({
599
+ }), d.postMessage({
600
600
  type: a.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,
601
601
  requestId: i,
602
602
  request: { method: e, endpoint: t, body: r, headers: o, authenticated: s }
@@ -647,7 +647,7 @@ class pe {
647
647
  };
648
648
  }
649
649
  }
650
- class ye extends te {
650
+ class xe extends te {
651
651
  constructor(e, t, r) {
652
652
  const o = t || {}, s = {
653
653
  basePath: o.basePath || "/",
@@ -661,7 +661,7 @@ class ye extends te {
661
661
  super(e, s, r);
662
662
  }
663
663
  createLogger(e) {
664
- return new x("ExtUIClient", e.logLevel);
664
+ return new y("ExtUIClient", e.logLevel);
665
665
  }
666
666
  createStoragePrefix(e) {
667
667
  return X(e.basePath, N.EXT);
@@ -697,7 +697,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
697
697
  // Constructor
698
698
  // ============================================================================
699
699
  constructor(e, t) {
700
- this.isAuthenticating = !1, this.isReauthorizing = !1, this.state = "setup", this.listenersInitialized = !1, this.refreshPromise = null, this.activeStreamPorts = /* @__PURE__ */ new Map(), this.authClientId = e, this.authServerUrl = t?.authServerUrl || "https://id.getbodhi.app/realms/bodhi", this.extensionId = t?.extensionId, this.logger = new x("BodhiExtClient", t?.logLevel || "warn"), this.attempts = t?.attempts ?? ue, this.attemptWaitMs = t?.attemptWaitMs ?? de, this.attemptTimeout = t?.attemptTimeout ?? le, this.authEndpoints = {
700
+ this.isAuthenticating = !1, this.isExchanging = !1, this.state = "setup", this.listenersInitialized = !1, this.refreshPromise = null, this.activeStreamPorts = /* @__PURE__ */ new Map(), this.authClientId = e, this.authServerUrl = t?.authServerUrl || "https://id.getbodhi.app/realms/bodhi", this.extensionId = t?.extensionId, this.logger = new y("BodhiExtClient", t?.logLevel || "warn"), this.attempts = t?.attempts ?? ue, this.attemptWaitMs = t?.attemptWaitMs ?? de, this.attemptTimeout = t?.attemptTimeout ?? le, this.authEndpoints = {
701
701
  authorize: `${this.authServerUrl}/protocol/openid-connect/auth`,
702
702
  token: `${this.authServerUrl}/protocol/openid-connect/token`,
703
703
  userinfo: `${this.authServerUrl}/protocol/openid-connect/userinfo`,
@@ -799,24 +799,24 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
799
799
  `[Ext2Ext/Discovery] Will try ${s.length} extension(s):`,
800
800
  s
801
801
  );
802
- for (const c of s) {
803
- for (let h = 1; h <= t; h++) {
802
+ for (const u of s) {
803
+ for (let c = 1; c <= t; c++) {
804
804
  this.logger.debug(
805
- `[Ext2Ext/Discovery] Trying ${c} - attempt ${h}/${t}`
805
+ `[Ext2Ext/Discovery] Trying ${u} - attempt ${c}/${t}`
806
806
  );
807
807
  try {
808
- return await this.pingExtension(c), this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${c} on attempt ${h}`), {
808
+ return await this.pingExtension(u), this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${u} on attempt ${c}`), {
809
809
  success: !0,
810
- extensionId: c
810
+ extensionId: u
811
811
  };
812
- } catch (E) {
812
+ } catch (h) {
813
813
  this.logger.debug(
814
- `[Ext2Ext/Discovery] Attempt ${h} failed for ${c}: ${E instanceof Error ? E.message : "Unknown error"}`
815
- ), h < t && await this.sleep(r);
814
+ `[Ext2Ext/Discovery] Attempt ${c} failed for ${u}: ${h instanceof Error ? h.message : "Unknown error"}`
815
+ ), c < t && await this.sleep(r);
816
816
  }
817
817
  }
818
818
  this.logger.warn(
819
- `[Ext2Ext/Discovery] ✗ Not found: ${c} after ${t} attempts`
819
+ `[Ext2Ext/Discovery] ✗ Not found: ${u} after ${t} attempts`
820
820
  );
821
821
  }
822
822
  const i = s.join(", "), n = `Extension not found. Tried ${s.length} IDs with ${t} attempts each: ${i}`;
@@ -989,7 +989,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
989
989
  }
990
990
  case m.GET_EXTENSION_ID: {
991
991
  const n = await this.sendExtRequestRaw(ie.GET_EXTENSION_ID, s);
992
- return y(n.response) ? {
992
+ return x(n.response) ? {
993
993
  type: a.EXT2EXT_CLIENT_RESPONSE,
994
994
  requestId: t,
995
995
  response: {
@@ -1072,25 +1072,25 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1072
1072
  * @throws Error if login fails
1073
1073
  */
1074
1074
  async login(e) {
1075
- if (!(this.isAuthenticating || (await this.getAuthState()).status === "authenticated" && !e?.reauthorize)) {
1076
- this.isAuthenticating = !0, this.isReauthorizing = e?.reauthorize ?? !1;
1075
+ if (!(this.isAuthenticating || (await this.getAuthState()).status === "authenticated" && !e?.exchange)) {
1076
+ this.isAuthenticating = !0, this.isExchanging = e?.exchange ?? !1;
1077
1077
  try {
1078
1078
  if (!this.extensionId)
1079
1079
  throw new Error("Extension not discovered. Please detect Bodhi extension before login.");
1080
1080
  const r = e?.userRole ?? "scope_user_user", o = chrome.identity.getRedirectURL("callback"), s = p.generateCodeVerifier(), i = await p.generateCodeChallenge(s), n = p.generateCodeVerifier();
1081
1081
  await chrome.storage.session.set({ codeVerifier: s, state: n, authInProgress: !0 });
1082
- const c = v(this.authEndpoints, {
1082
+ const u = v(this.authEndpoints, {
1083
1083
  clientId: this.authClientId,
1084
1084
  redirectUri: o,
1085
1085
  scope: L,
1086
1086
  state: n,
1087
1087
  codeChallenge: i
1088
- }), h = b(o), E = new P(this.authClientId).requestedRole(r);
1089
- e?.requested && E.requested(e.requested);
1090
- const l = await this.requestAccess(E.build()), { review_url: u } = k(l), g = O(u, c, h), S = await this.launchReview(g);
1088
+ }), c = b(o), h = new k(this.authClientId).requestedRole(r);
1089
+ e?.requested && h.requested(e.requested), e?.exchange && h.exchange(!0);
1090
+ const E = await this.requestAccess(h.build()), { review_url: d } = P(E), g = O(d, u, c), S = await this.launchReview(g);
1091
1091
  await this.completeOAuthRedirect(S);
1092
1092
  } finally {
1093
- this.isAuthenticating = !1, this.isReauthorizing = !1;
1093
+ this.isAuthenticating = !1, this.isExchanging = !1;
1094
1094
  }
1095
1095
  }
1096
1096
  }
@@ -1099,7 +1099,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1099
1099
  * @param code Authorization code from OAuth callback
1100
1100
  */
1101
1101
  async exchangeCodeForTokens(e) {
1102
- if ((await this.getAuthState()).status === "authenticated" && !this.isReauthorizing)
1102
+ if ((await this.getAuthState()).status === "authenticated" && !this.isExchanging)
1103
1103
  return;
1104
1104
  const { codeVerifier: r } = await chrome.storage.session.get("codeVerifier"), o = chrome.identity.getRedirectURL("callback"), s = await fetch(this.authEndpoints.token, {
1105
1105
  method: "POST",
@@ -1203,7 +1203,9 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1203
1203
  return this.sendApiRequest(
1204
1204
  "POST",
1205
1205
  "/bodhi/v1/apps/request-access",
1206
- e
1206
+ e,
1207
+ void 0,
1208
+ !0
1207
1209
  );
1208
1210
  }
1209
1211
  launchReview(e) {
@@ -1214,7 +1216,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1214
1216
  return;
1215
1217
  }
1216
1218
  if (!o) {
1217
- await chrome.storage.session.remove(["codeVerifier", "state"]), r(d("oauth_error", "No redirect URL received"));
1219
+ await chrome.storage.session.remove(["codeVerifier", "state"]), r(l("oauth_error", "No redirect URL received"));
1218
1220
  return;
1219
1221
  }
1220
1222
  t(o);
@@ -1224,14 +1226,14 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1224
1226
  async completeOAuthRedirect(e) {
1225
1227
  const t = new URL(e), r = t.searchParams.get("error");
1226
1228
  if (r)
1227
- throw await chrome.storage.session.remove(["codeVerifier", "state"]), t.searchParams.get("error_source") === "bodhi" && M(r === "access_denied" ? "denied" : r), d("oauth_error", t.searchParams.get("error_description") ?? r);
1229
+ throw await chrome.storage.session.remove(["codeVerifier", "state"]), t.searchParams.get("error_source") === "bodhi" && M(r === "access_denied" ? "denied" : r), l("oauth_error", t.searchParams.get("error_description") ?? r);
1228
1230
  const o = t.searchParams.get("code"), s = t.searchParams.get("state"), { state: i } = await chrome.storage.session.get("state");
1229
1231
  if (!s || s !== i)
1230
- throw await chrome.storage.session.remove(["codeVerifier", "state"]), d("oauth_error", "State mismatch");
1232
+ throw await chrome.storage.session.remove(["codeVerifier", "state"]), l("oauth_error", "State mismatch");
1231
1233
  if (!o)
1232
- throw await chrome.storage.session.remove(["codeVerifier", "state"]), d("oauth_error", "No authorization code");
1234
+ throw await chrome.storage.session.remove(["codeVerifier", "state"]), l("oauth_error", "No authorization code");
1233
1235
  if (await this.exchangeCodeForTokens(o), await chrome.storage.session.remove(["codeVerifier", "state"]), (await this.getAuthState()).status !== "authenticated")
1234
- throw d("oauth_error", "Login failed");
1236
+ throw l("oauth_error", "Login failed");
1235
1237
  }
1236
1238
  // Ext2Ext Communication (Private Methods)
1237
1239
  // ============================================================================
@@ -1243,7 +1245,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1243
1245
  */
1244
1246
  async sendExtRequest(e, t) {
1245
1247
  const r = await this.sendExtRequestRaw(e, t);
1246
- if (y(r.response))
1248
+ if (x(r.response))
1247
1249
  throw this.logger.error("[BodhiExtClient] Extension error:", r.response.error), new Error(
1248
1250
  r.response.error.message || `Extension request failed: ${JSON.stringify(r.response)}`
1249
1251
  );
@@ -1255,9 +1257,10 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1255
1257
  * @param endpoint API endpoint path
1256
1258
  * @param body Optional request body
1257
1259
  * @param headers Optional headers
1260
+ * @param authenticated Attach the current token when one exists (safe: skipped if none)
1258
1261
  * @returns API response from LLM server via bodhi-browser-ext
1259
1262
  */
1260
- async sendApiRequest(e, t, r, o) {
1263
+ async sendApiRequest(e, t, r, o, s = !1) {
1261
1264
  if (!this.extensionId)
1262
1265
  throw new T(
1263
1266
  "not_initialized",
@@ -1267,24 +1270,29 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1267
1270
  `[BodhiExtClient] Sending API_REQUEST: method=${e}, endpoint=${t}`,
1268
1271
  r ? { body: r } : ""
1269
1272
  );
1270
- const s = crypto.randomUUID(), i = {
1273
+ let i = o;
1274
+ if (s) {
1275
+ const c = await this._getAccessTokenRaw();
1276
+ c && (i = { ...o, Authorization: `Bearer ${c}` });
1277
+ }
1278
+ const n = crypto.randomUUID(), u = {
1271
1279
  type: _.API_REQUEST,
1272
- requestId: s,
1280
+ requestId: n,
1273
1281
  request: {
1274
1282
  method: e,
1275
1283
  endpoint: t,
1276
1284
  body: r,
1277
- headers: o
1285
+ headers: i
1278
1286
  }
1279
1287
  };
1280
- return this.logger.debug(`[BodhiExtClient] Request ID: ${s}, Extension: ${this.extensionId}`), new Promise((n, c) => {
1288
+ return this.logger.debug(`[BodhiExtClient] Request ID: ${n}, Extension: ${this.extensionId}`), new Promise((c, h) => {
1281
1289
  try {
1282
- chrome.runtime.sendMessage(this.extensionId, i, (h) => {
1290
+ chrome.runtime.sendMessage(this.extensionId, u, (E) => {
1283
1291
  if (chrome.runtime.lastError) {
1284
1292
  this.logger.error(
1285
- `[BodhiExtClient] Chrome runtime error for request ${s}:`,
1293
+ `[BodhiExtClient] Chrome runtime error for request ${n}:`,
1286
1294
  chrome.runtime.lastError
1287
- ), c(
1295
+ ), h(
1288
1296
  new T(
1289
1297
  "extension_error",
1290
1298
  chrome.runtime.lastError.message ?? "Chrome runtime error"
@@ -1292,19 +1300,19 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1292
1300
  );
1293
1301
  return;
1294
1302
  }
1295
- if (this.logger.debug(`[BodhiExtClient] Response for request ${s}:`, h), !h) {
1296
- this.logger.error(`[BodhiExtClient] No response received for request ${s}`), c(new T("extension_error", "No response from extension"));
1303
+ if (this.logger.debug(`[BodhiExtClient] Response for request ${n}:`, E), !E) {
1304
+ this.logger.error(`[BodhiExtClient] No response received for request ${n}`), h(new T("extension_error", "No response from extension"));
1297
1305
  return;
1298
1306
  }
1299
- h.type === _.API_RESPONSE && h.requestId === s ? "error" in h ? (this.logger.error(`[BodhiExtClient] API error for ${s}:`, h.error), c(
1300
- new T(h.error.type || "extension_error", h.error.message)
1301
- )) : (this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${s}`), n(h.response)) : (this.logger.error(
1302
- `[BodhiExtClient] Invalid response format for ${s}:`,
1303
- h
1304
- ), c(new T("extension_error", "Invalid response format")));
1307
+ E.type === _.API_RESPONSE && E.requestId === n ? "error" in E ? (this.logger.error(`[BodhiExtClient] API error for ${n}:`, E.error), h(
1308
+ new T(E.error.type || "extension_error", E.error.message)
1309
+ )) : (this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${n}`), c(E.response)) : (this.logger.error(
1310
+ `[BodhiExtClient] Invalid response format for ${n}:`,
1311
+ E
1312
+ ), h(new T("extension_error", "Invalid response format")));
1305
1313
  });
1306
- } catch (h) {
1307
- this.logger.error(`[BodhiExtClient] Exception sending message for ${s}:`, h), c(h);
1314
+ } catch (E) {
1315
+ this.logger.error(`[BodhiExtClient] Exception sending message for ${n}:`, E), h(E);
1308
1316
  }
1309
1317
  });
1310
1318
  }
@@ -1400,10 +1408,10 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1400
1408
  return;
1401
1409
  }
1402
1410
  try {
1403
- let c = { ...r.headers };
1411
+ let u = { ...r.headers };
1404
1412
  if (r.authenticated !== !1) {
1405
- const u = await this._getAccessTokenRaw();
1406
- if (!u) {
1413
+ const d = await this._getAccessTokenRaw();
1414
+ if (!d) {
1407
1415
  e.postMessage({
1408
1416
  type: i,
1409
1417
  requestId: t,
@@ -1411,21 +1419,21 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1411
1419
  });
1412
1420
  return;
1413
1421
  }
1414
- c = { ...c, Authorization: `Bearer ${u}` }, this.logger.debug("[BodhiExtClient] Injected auth token for stream relay");
1422
+ u = { ...u, Authorization: `Bearer ${d}` }, this.logger.debug("[BodhiExtClient] Injected auth token for stream relay");
1415
1423
  }
1416
- const h = chrome.runtime.connect(this.extensionId, { name: o });
1417
- this.activeStreamPorts.set(t, h);
1418
- const E = setTimeout(() => {
1424
+ const c = chrome.runtime.connect(this.extensionId, { name: o });
1425
+ this.activeStreamPorts.set(t, c);
1426
+ const h = setTimeout(() => {
1419
1427
  this.activeStreamPorts.has(t) && (this.logger.error(`[BodhiExtClient] Stream timeout for ${t}`), e.postMessage({
1420
1428
  type: i,
1421
1429
  requestId: t,
1422
1430
  error: { message: "Stream request timed out", type: "timeout_error" }
1423
1431
  }), this.cleanupStreamPort(t));
1424
1432
  }, p.STREAM_TIMEOUT);
1425
- h.onMessage.addListener((u) => {
1426
- n(u, e, t) && (clearTimeout(E), this.cleanupStreamPort(t));
1427
- }), h.onDisconnect.addListener(() => {
1428
- clearTimeout(E), this.activeStreamPorts.has(t) && (this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${t}`), e.postMessage({
1433
+ c.onMessage.addListener((d) => {
1434
+ n(d, e, t) && (clearTimeout(h), this.cleanupStreamPort(t));
1435
+ }), c.onDisconnect.addListener(() => {
1436
+ clearTimeout(h), this.activeStreamPorts.has(t) && (this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${t}`), e.postMessage({
1429
1437
  type: i,
1430
1438
  requestId: t,
1431
1439
  error: {
@@ -1434,24 +1442,24 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1434
1442
  }
1435
1443
  }), this.activeStreamPorts.delete(t));
1436
1444
  });
1437
- const l = {
1445
+ const E = {
1438
1446
  type: s,
1439
1447
  requestId: t,
1440
1448
  request: {
1441
1449
  method: r.method,
1442
1450
  endpoint: r.endpoint,
1443
1451
  body: r.body,
1444
- headers: c
1452
+ headers: u
1445
1453
  }
1446
1454
  };
1447
- this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:", l), h.postMessage(l);
1448
- } catch (c) {
1449
- const h = c;
1450
- this.logger.error("[BodhiExtClient] Stream relay error:", JSON.stringify(h.message)), e.postMessage({
1455
+ this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:", E), c.postMessage(E);
1456
+ } catch (u) {
1457
+ const c = u;
1458
+ this.logger.error("[BodhiExtClient] Stream relay error:", JSON.stringify(c.message)), e.postMessage({
1451
1459
  type: i,
1452
1460
  requestId: t,
1453
1461
  error: {
1454
- message: `uncaught error: ${JSON.stringify({ error: h, message: h.message })}`,
1462
+ message: `uncaught error: ${JSON.stringify({ error: c, message: c.message })}`,
1455
1463
  type: "extension_error"
1456
1464
  }
1457
1465
  });
@@ -1479,23 +1487,23 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1479
1487
  e,
1480
1488
  r,
1481
1489
  o,
1482
- oe,
1490
+ ne,
1483
1491
  _.STREAM_REQUEST,
1484
1492
  a.EXT2EXT_CLIENT_STREAM_ERROR,
1485
1493
  (s, i, n) => {
1486
- if (ne(s)) {
1487
- const c = s.response, h = c.body;
1488
- return c.status >= 400 ? (i.postMessage({
1494
+ if (oe(s)) {
1495
+ const u = s.response, c = u.body;
1496
+ return u.status >= 400 ? (i.postMessage({
1489
1497
  type: a.EXT2EXT_CLIENT_STREAM_API_ERROR,
1490
1498
  requestId: n,
1491
- response: c
1492
- }), !1) : h?.done ? (i.postMessage({
1499
+ response: u
1500
+ }), !1) : c?.done ? (i.postMessage({
1493
1501
  type: a.EXT2EXT_CLIENT_STREAM_DONE,
1494
1502
  requestId: n
1495
1503
  }), this.logger.info(`[BodhiExtClient] Stream complete for ${n}`), !0) : (i.postMessage({
1496
1504
  type: a.EXT2EXT_CLIENT_STREAM_CHUNK,
1497
1505
  requestId: n,
1498
- response: c
1506
+ response: u
1499
1507
  }), !1);
1500
1508
  } else {
1501
1509
  if (ae(s))
@@ -1506,7 +1514,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1506
1514
  requestId: n,
1507
1515
  response: s.response
1508
1516
  }), !1;
1509
- if (he(s))
1517
+ if (ce(s))
1510
1518
  return this.logger.error(
1511
1519
  `[BodhiExtClient] Stream error for ${n}:`,
1512
1520
  s.error.message
@@ -1532,7 +1540,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1532
1540
  e,
1533
1541
  r,
1534
1542
  o,
1535
- ce,
1543
+ he,
1536
1544
  _.STREAM_TEXT_REQUEST,
1537
1545
  a.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,
1538
1546
  (s, i, n) => {
@@ -1626,7 +1634,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1626
1634
  } catch (t) {
1627
1635
  this.logger.warn("Token refresh failed:", t);
1628
1636
  }
1629
- throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), d(
1637
+ throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), l(
1630
1638
  "auth_error",
1631
1639
  "Access token expired and unable to refresh. Try logging out and logging in again."
1632
1640
  );
@@ -1665,7 +1673,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
1665
1673
  };
1666
1674
  p.STREAM_TIMEOUT = 6e4;
1667
1675
  let A = p;
1668
- const xe = "production";
1676
+ const ye = "production";
1669
1677
  export {
1670
1678
  A as BodhiExtClient,
1671
1679
  _e as ChromeSessionStorageAdapter,
@@ -1678,8 +1686,8 @@ export {
1678
1686
  a as EXT2EXT_CLIENT_MESSAGE_TYPES,
1679
1687
  U as EXT2EXT_CLIENT_STREAM_PORT,
1680
1688
  D as EXT2EXT_CLIENT_STREAM_TEXT_PORT,
1681
- xe as EXT_BUILD_MODE,
1682
- ye as ExtUIClient,
1689
+ ye as EXT_BUILD_MODE,
1690
+ xe as ExtUIClient,
1683
1691
  Ae as InMemoryStorage,
1684
1692
  ge as isExtClientApiError
1685
1693
  };
@@ -17,7 +17,7 @@ export type { ExtClientApiError, ExtClientApiResponseMessage } from './messages'
17
17
  export declare class BodhiExtClient {
18
18
  private extensionId?;
19
19
  private isAuthenticating;
20
- private isReauthorizing;
20
+ private isExchanging;
21
21
  private authClientId;
22
22
  private authServerUrl;
23
23
  private logger;
@@ -137,6 +137,7 @@ export declare class BodhiExtClient {
137
137
  * @param endpoint API endpoint path
138
138
  * @param body Optional request body
139
139
  * @param headers Optional headers
140
+ * @param authenticated Attach the current token when one exists (safe: skipped if none)
140
141
  * @returns API response from LLM server via bodhi-browser-ext
141
142
  */
142
143
  private sendApiRequest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js-ext",
3
- "version": "0.0.43",
3
+ "version": "0.0.44",
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.43",
41
- "@bodhiapp/bodhi-js-core": "0.0.43",
42
- "@bodhiapp/ts-client": "0.1.37"
40
+ "@bodhiapp/bodhi-browser-types": "0.0.44",
41
+ "@bodhiapp/bodhi-js-core": "0.0.44",
42
+ "@bodhiapp/ts-client": "0.1.38"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@eslint/js": "^9.23.0",