@bodhiapp/bodhi-js-core 0.0.30 → 0.0.32

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,11 +1,11 @@
1
1
  import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, DeploymentMode, PingResponse } from '@bodhiapp/ts-client';
2
2
  import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
3
- import { IDirectClient } from './interface';
3
+ import { IDirectClient, StreamTextResult } from './interface';
4
4
  import { Logger } from './logger';
5
5
  import { Chat, Models, Embeddings, Mcps } from './openai-client-compat';
6
6
  import { OAuthEndpoints, RefreshTokenResponse } from './oauth';
7
7
  import { StorageKeys } from './storage';
8
- import { AuthState, BackendServerState, ClientState, DirectState, InitParams, LogLevel, SerializedDirectState, StateChangeCallback } from './types';
8
+ import { AuthState, BackendServerState, ClientState, DirectState, IStorage, InitialTokens, InitParams, LogLevel, SerializedDirectState, StateChangeCallback } from './types';
9
9
  /**
10
10
  * DirectClientBase - Abstract base class for DirectClient implementations
11
11
  *
@@ -42,6 +42,8 @@ export interface DirectClientBaseConfig {
42
42
  logLevel: LogLevel;
43
43
  loggerPrefix: string;
44
44
  apiTimeoutMs?: number;
45
+ storage?: IStorage;
46
+ initialTokens?: InitialTokens;
45
47
  }
46
48
  /**
47
49
  * DirectClientBase - Abstract base implementing common HTTP/streaming logic
@@ -52,11 +54,13 @@ export declare abstract class DirectClientBase implements IDirectClient {
52
54
  protected authClientId: string;
53
55
  protected authServerUrl: string;
54
56
  protected authEndpoints: OAuthEndpoints;
57
+ protected storage: IStorage | null;
55
58
  protected storageKeys: StorageKeys;
56
59
  protected state: DirectState;
57
60
  private onStateChange;
58
61
  private refreshPromise;
59
62
  private apiTimeoutMs;
63
+ private initialTokens;
60
64
  private _chat;
61
65
  private _models;
62
66
  private _embeddings;
@@ -90,6 +94,7 @@ export declare abstract class DirectClientBase implements IDirectClient {
90
94
  */
91
95
  getServerState(): Promise<BackendServerState>;
92
96
  stream<TReq = unknown, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): AsyncGenerator<TRes>;
97
+ streamText(method: string, endpoint: string, body?: unknown, headers?: Record<string, string>, authenticated?: boolean): Promise<StreamTextResult>;
93
98
  get chat(): Chat;
94
99
  get models(): Models;
95
100
  get embeddings(): Embeddings;
@@ -113,9 +118,9 @@ export declare abstract class DirectClientBase implements IDirectClient {
113
118
  timeoutMs?: number;
114
119
  }): Promise<AccessRequestStatusResponse>;
115
120
  abstract login(): Promise<AuthState>;
116
- abstract logout(): Promise<AuthState>;
117
121
  protected abstract performOAuthPkce(scope: string): Promise<AuthState>;
118
122
  getAuthState(): Promise<AuthState>;
123
+ logout(): Promise<AuthState>;
119
124
  protected _getAccessTokenRaw(): Promise<string | null>;
120
125
  /**
121
126
  * Try to refresh access token using refresh token
@@ -133,8 +138,9 @@ export declare abstract class DirectClientBase implements IDirectClient {
133
138
  protected exchangeCodeForTokens(code: string): Promise<void>;
134
139
  protected revokeRefreshToken(): Promise<void>;
135
140
  protected clearAuthStorage(): Promise<void>;
136
- protected abstract _storageGet(key: string): Promise<string | null>;
137
- protected abstract _storageSet(items: Record<string, string | number>): Promise<void>;
138
- protected abstract _storageRemove(keys: string[]): Promise<void>;
141
+ private _bootstrapInitialTokens;
142
+ protected _storageGet(key: string): Promise<string | null>;
143
+ protected _storageSet(items: Record<string, string | number>): Promise<void>;
144
+ protected _storageRemove(keys: string[]): Promise<void>;
139
145
  protected abstract _getRedirectUri(): string;
140
146
  }
@@ -1,9 +1,10 @@
1
1
  import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, PingResponse } from '@bodhiapp/ts-client';
2
2
  import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
3
- import { IConnectionClient, IExtensionClient } from './interface';
3
+ import { IConnectionClient, IExtensionClient, StreamTextResult } from './interface';
4
4
  import { Logger } from './logger';
5
5
  import { BodhiClientUserPrefsManager } from './storage';
6
6
  import { Chat, Models, Embeddings, Mcps } from './openai-client-compat';
7
+ import { McpTransportConfig } from './mcp-fetch';
7
8
  import { AuthState, BackendServerState, ClientState, ConnectionMode, DirectState, ExtensionState, InitParams, LoginOptions, SerializedClientState, SerializedDirectState, SerializedExtensionState, StateChange, StateChangeCallback } from './types';
8
9
  /**
9
10
  * Base facade client with common delegation logic
@@ -112,10 +113,20 @@ export declare abstract class BaseFacadeClient<TConfig, TExtClient extends IExte
112
113
  pingApi(): Promise<ApiResponse<PingResponse>>;
113
114
  getServerState(): Promise<BackendServerState>;
114
115
  stream<TReq = unknown, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): AsyncGenerator<TRes>;
116
+ streamText(method: string, endpoint: string, body?: unknown, headers?: Record<string, string>, authenticated?: boolean): Promise<StreamTextResult>;
115
117
  get chat(): Chat;
116
118
  get models(): Models;
117
119
  get embeddings(): Embeddings;
118
120
  get mcps(): Mcps;
121
+ /**
122
+ * Create transport config for MCP StreamableHTTPClientTransport.
123
+ * Handles connection mode transparently — direct mode uses standard fetch with
124
+ * Bearer token, extension mode routes through the extension's message passing.
125
+ *
126
+ * @param mcp_path - Relative proxy path from Mcp.path (e.g. '/bodhi/v1/apps/mcps/{id}/mcp')
127
+ * @returns McpTransportConfig with url and fetch function for StreamableHTTPClientTransport
128
+ */
129
+ createMcpTransportConfig(mcp_path: string): McpTransportConfig;
119
130
  getConnectionMode(): ConnectionMode | null;
120
131
  setConnectionMode(mode: ConnectionMode): Promise<ClientState>;
121
132
  /**
package/dist/index.d.ts CHANGED
@@ -20,4 +20,5 @@ export * from './oauth';
20
20
  export * from './direct-client-base';
21
21
  export * from './facade-client-base';
22
22
  export * from './openai-client-compat';
23
+ export { createDirectMcpFetch, createExtensionMcpFetch } from './mcp-fetch';
23
24
  export { BUILD_MODE as CORE_BUILD_MODE } from './build-info';
@@ -2,6 +2,17 @@ import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestRe
2
2
  import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
3
3
  import { AuthState, BackendServerState, ClientState, ConnectionMode, DirectState, ExtensionState, InitParams, LoginOptions, StateChangeCallback } from './types';
4
4
  import { Chat, Models, Embeddings, Mcps } from './openai-client-compat';
5
+ import { McpTransportConfig } from './mcp-fetch';
6
+ /**
7
+ * Result of a raw text streaming request
8
+ * Unlike stream() which parses SSE/JSON, streamText() forwards raw response text
9
+ * without any parsing. Non-2xx responses are returned as data (not thrown).
10
+ */
11
+ export interface StreamTextResult {
12
+ status: number;
13
+ headers: Record<string, string>;
14
+ body: AsyncGenerator<string>;
15
+ }
5
16
  /**
6
17
  * ConnectionClient - Base interface for all client implementations
7
18
  *
@@ -68,6 +79,17 @@ export interface IConnectionClient<IParams = unknown, SerialState = unknown> {
68
79
  * @returns AsyncGenerator yielding response chunks
69
80
  */
70
81
  stream<TReq = unknown, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): AsyncGenerator<TRes>;
82
+ /**
83
+ * Raw text streaming request
84
+ * - DirectClient: fetch + ReadableStream without SSE parsing
85
+ * - IExtensionClient: window.bodhiext.sendStreamText or chrome.runtime equivalent
86
+ *
87
+ * Returns status, headers, and async generator of raw text chunks.
88
+ * Non-2xx responses are returned as data (not thrown).
89
+ *
90
+ * @returns Promise<StreamTextResult> with status, headers, and body generator
91
+ */
92
+ streamText(method: string, endpoint: string, body?: unknown, headers?: Record<string, string>, authenticated?: boolean): Promise<StreamTextResult>;
71
93
  /**
72
94
  * Login via OAuth
73
95
  * - IExtensionClient: Delegates to extension (chrome.identity or browser redirect)
@@ -240,6 +262,12 @@ export type UIClient = IConnectionClient<InitParams> & {
240
262
  * @throws Error if connection mode is not 'extension'
241
263
  */
242
264
  sendExtRequest<TParams = void, TRes = unknown>(action: string, params?: TParams): Promise<TRes>;
265
+ /**
266
+ * Create transport config for MCP StreamableHTTPClientTransport.
267
+ * Handles connection mode transparently.
268
+ * @param mcp_path - Relative proxy path from Mcp.path (e.g. '/bodhi/v1/apps/mcps/{id}/mcp')
269
+ */
270
+ createMcpTransportConfig(mcp_path: string): McpTransportConfig;
243
271
  };
244
272
  /**
245
273
  * Web-specific UIClient interface with OAuth callback handling
@@ -0,0 +1,23 @@
1
+ import { StreamTextResult } from './interface';
2
+ /**
3
+ * Standard fetch signature compatible with @modelcontextprotocol/sdk's FetchLike
4
+ */
5
+ export type McpFetchLike = (url: string | URL, init?: RequestInit) => Promise<Response>;
6
+ /**
7
+ * Configuration returned by createMcpTransportConfig for creating MCP transports
8
+ */
9
+ export interface McpTransportConfig {
10
+ url: URL;
11
+ fetch: McpFetchLike;
12
+ }
13
+ /**
14
+ * Creates a FetchLike for direct mode — standard fetch with Bearer token injection.
15
+ */
16
+ export declare function createDirectMcpFetch(getToken: () => Promise<string | null>): McpFetchLike;
17
+ /**
18
+ * Creates a FetchLike for extension mode — routes HTTP through the Bodhi SDK client's
19
+ * streamText method which forwards raw response text without any parsing.
20
+ */
21
+ export declare function createExtensionMcpFetch(client: {
22
+ streamText(method: string, endpoint: string, body?: unknown, headers?: Record<string, string>, authenticated?: boolean): Promise<StreamTextResult>;
23
+ }): McpFetchLike;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@modelcontextprotocol/sdk/client/index.js"),s=require("@modelcontextprotocol/sdk/client/streamableHttp.js");async function l(r,a,e){const t=r.createMcpTransportConfig(a),n=new s.StreamableHTTPClientTransport(t.url,{fetch:t.fetch}),c=new o.Client({name:e?.name??"bodhi-mcp-client",version:e?.version??"1.0.0"});try{await c.connect(n)}catch(i){throw await n.close().catch(()=>{}),i}return c}exports.createMcpClient=l;
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { McpTransportConfig } from './mcp-fetch';
3
+ /** Any client that provides createMcpTransportConfig (UIClient, CliClient, etc.) */
4
+ export interface McpTransportProvider {
5
+ createMcpTransportConfig(mcp_path: string): McpTransportConfig;
6
+ }
7
+ export interface CreateMcpClientOptions {
8
+ name?: string;
9
+ version?: string;
10
+ }
11
+ /**
12
+ * Create a connected MCP Client for a given proxy path.
13
+ *
14
+ * @param client - Any Bodhi client with createMcpTransportConfig (UIClient, CliClient, etc.)
15
+ * @param mcp_path - MCP proxy path from Mcp.path (e.g. '/bodhi/v1/apps/mcps/{id}/mcp')
16
+ * @param options - Optional client name and version
17
+ * @returns Connected @modelcontextprotocol/sdk Client ready for listTools(), callTool(), etc.
18
+ */
19
+ export declare function createMcpClient(client: McpTransportProvider, mcp_path: string, options?: CreateMcpClientOptions): Promise<Client>;
@@ -0,0 +1,18 @@
1
+ import { Client as i } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StreamableHTTPClientTransport as m } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
+ async function l(r, o, t) {
4
+ const e = r.createMcpTransportConfig(o), n = new m(e.url, { fetch: e.fetch }), c = new i({
5
+ name: t?.name ?? "bodhi-mcp-client",
6
+ version: t?.version ?? "1.0.0"
7
+ });
8
+ try {
9
+ await c.connect(n);
10
+ } catch (a) {
11
+ throw await n.close().catch(() => {
12
+ }), a;
13
+ }
14
+ return c;
15
+ }
16
+ export {
17
+ l as createMcpClient
18
+ };
@@ -1,4 +1,4 @@
1
- import { CreateChatCompletionRequest, CreateChatCompletionResponse, CreateChatCompletionStreamResponse, CreateEmbeddingRequest, CreateEmbeddingResponse, Model, ListMcpsResponse, McpToolsResponse } from '@bodhiapp/ts-client';
1
+ import { CreateChatCompletionRequest, CreateChatCompletionResponse, CreateChatCompletionStreamResponse, CreateEmbeddingRequest, CreateEmbeddingResponse, Model, ListMcpsResponse } from '@bodhiapp/ts-client';
2
2
  import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
3
3
  /**
4
4
  * Minimal client interface required by resource classes
@@ -68,16 +68,4 @@ export declare class Mcps extends APIResource {
68
68
  * List available MCP servers
69
69
  */
70
70
  list(): Promise<ListMcpsResponse>;
71
- /**
72
- * List tools for a specific MCP server
73
- */
74
- listTools(mcpId: string): Promise<McpToolsResponse>;
75
- /**
76
- * Refresh tools for a specific MCP server
77
- */
78
- refreshTools(mcpId: string): Promise<McpToolsResponse>;
79
- /**
80
- * Execute a tool on an MCP server
81
- */
82
- executeTool(mcpId: string, toolName: string, params: Record<string, unknown>): Promise<unknown>;
83
71
  }
@@ -18,6 +18,9 @@ export interface AuthState {
18
18
  user: UserInfo | null;
19
19
  accessToken: string | null;
20
20
  error: AuthError | null;
21
+ refreshToken: string | null;
22
+ expiresAt: number | null;
23
+ isTokenRefresh: boolean;
21
24
  }
22
25
  /**
23
26
  * Helper to check if authenticated
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("@bodhiapp/bodhi-browser-types"),y=(e,r,t)=>{const n=r?.error?.message||`HTTP ${e}`;return new i.BodhiApiError(e,r,n,t)},o=(e,r)=>new i.BodhiError(e,r);function p(e){throw e==="denied"?o("access_request_denied","Access request was denied"):e==="expired"?o("access_request_expired","Access request expired"):o("access_request_failed",`Access request failed: ${e}`)}const s={NOT_REACHABLE:{message:"server is not reachable on given url",type:"network_error"},SERVER_NOT_READY:{message:"server is not in ready state, configure to complete setup",type:"extension_error"}},E={status:"not-reachable",version:null,error:s.NOT_REACHABLE},u={status:"pending-extension-ready",version:null,error:null},d={status:"not-connected",version:null,error:null};function c(e){return e.status==="ready"}function I(e,r="unknown",t=s.SERVER_NOT_READY,n,A){return{status:e,version:r,error:t,deployment:n??null,client_id:A??null}}function a(e){return e.type==="extension"}function l(e){return e.type==="direct"}function O(e){return typeof e.server=="object"&&e.server.status!=="not-connected"&&c(e.server)}function _(e){return e.url!==null}const f={type:"direct",url:null,server:d};function v(e,r="unknown"){return{type:"direct",server:{status:"ready",version:r,error:null},url:e}}function D(e){return{type:"direct",server:E,url:e}}function x(e,r){return{type:"direct",server:r,url:e}}const R={type:"extension",extension:"not-initialized",extensionId:null,server:u},N={type:"extension",extension:"not-found",extensionId:null,server:u};function h(e){return e.extension==="ready"&&e.server.status!=="pending-extension-ready"&&c(e.server)}function T(e){return e.extension==="ready"}function C(){return R}function b(){return N}function g(e){return a(e)?T(e):_(e)}function B(e){return e.server}function L(e){return a(e)?e.extensionId??void 0:void 0}function w(e){return l(e)?e.url??void 0:void 0}function q(e){return e.status==="authenticated"}function k(e){return e.status==="loading"}function P(e){return e.status==="error"}const V={status:"idle",user:null,accessToken:null,error:null},m=()=>{};function H(e,r){return{kind:"event",type:e,payload:r}}function S(e,r,t){return{kind:"response",type:r,requestId:e,payload:t}}function K(e,r){return{kind:"error",requestId:e,error:r}}function U(e,r){const t=r[e.type];if(!t)return null;const n=t(e.payload);return S(e.requestId,e.type,n)}Object.defineProperty(exports,"BodhiApiError",{enumerable:!0,get:()=>i.BodhiApiError});Object.defineProperty(exports,"BodhiError",{enumerable:!0,get:()=>i.BodhiError});Object.defineProperty(exports,"unwrapResponse",{enumerable:!0,get:()=>i.unwrapResponse});exports.BACKEND_SERVER_NOT_CONNECTED=d;exports.BACKEND_SERVER_NOT_REACHABLE=E;exports.DIRECT_STATE_NOT_INITIALIZED=f;exports.EXTENSION_STATE_NOT_FOUND=N;exports.EXTENSION_STATE_NOT_INITIALIZED=R;exports.INITIAL_AUTH_STATE=V;exports.NOOP_STATE_CALLBACK=m;exports.PENDING_EXTENSION_READY=u;exports.SERVER_ERROR_CODES=s;exports.backendServerNotReady=I;exports.buildError=K;exports.buildEvent=H;exports.buildResponse=S;exports.createApiError=y;exports.createDirectStateNotReachable=D;exports.createDirectStateNotReady=x;exports.createDirectStateReady=v;exports.createExtensionStateNotFound=b;exports.createExtensionStateNotInitialized=C;exports.createOperationError=o;exports.getBackendServerState=B;exports.getExtensionId=L;exports.getServerUrl=w;exports.handleRequest=U;exports.isAuthError=P;exports.isAuthLoading=k;exports.isAuthenticated=q;exports.isClientReady=g;exports.isDirectClientReady=_;exports.isDirectServerReady=O;exports.isDirectState=l;exports.isExtensionClientReady=T;exports.isExtensionServerReady=h;exports.isExtensionState=a;exports.isServerReady=c;exports.throwAccessRequestDenialError=p;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("@bodhiapp/bodhi-browser-types"),y=(e,r,t)=>{const n=r?.error?.message||`HTTP ${e}`;return new i.BodhiApiError(e,r,n,t)},o=(e,r)=>new i.BodhiError(e,r);function p(e){throw e==="denied"?o("access_request_denied","Access request was denied"):e==="expired"?o("access_request_expired","Access request expired"):o("access_request_failed",`Access request failed: ${e}`)}const s={NOT_REACHABLE:{message:"server is not reachable on given url",type:"network_error"},SERVER_NOT_READY:{message:"server is not in ready state, configure to complete setup",type:"extension_error"}},E={status:"not-reachable",version:null,error:s.NOT_REACHABLE},u={status:"pending-extension-ready",version:null,error:null},d={status:"not-connected",version:null,error:null};function c(e){return e.status==="ready"}function f(e,r="unknown",t=s.SERVER_NOT_READY,n,A){return{status:e,version:r,error:t,deployment:n??null,client_id:A??null}}function a(e){return e.type==="extension"}function l(e){return e.type==="direct"}function I(e){return typeof e.server=="object"&&e.server.status!=="not-connected"&&c(e.server)}function R(e){return e.url!==null}const O={type:"direct",url:null,server:d};function v(e,r="unknown"){return{type:"direct",server:{status:"ready",version:r,error:null},url:e}}function D(e){return{type:"direct",server:E,url:e}}function h(e,r){return{type:"direct",server:r,url:e}}const _={type:"extension",extension:"not-initialized",extensionId:null,server:u},S={type:"extension",extension:"not-found",extensionId:null,server:u};function x(e){return e.extension==="ready"&&e.server.status!=="pending-extension-ready"&&c(e.server)}function T(e){return e.extension==="ready"}function C(){return _}function g(){return S}function b(e){return a(e)?T(e):R(e)}function B(e){return e.server}function L(e){return a(e)?e.extensionId??void 0:void 0}function w(e){return l(e)?e.url??void 0:void 0}function k(e){return e.status==="authenticated"}function q(e){return e.status==="loading"}function m(e){return e.status==="error"}const P={status:"idle",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};class V{constructor(){this.store=new Map}async get(r){return this.store.get(r)??null}async set(r){Object.entries(r).forEach(([t,n])=>{this.store.set(t,String(n))})}async remove(r){r.forEach(t=>this.store.delete(t))}}const H=()=>{};function j(e,r){return{kind:"event",type:e,payload:r}}function N(e,r,t){return{kind:"response",type:r,requestId:e,payload:t}}function K(e,r){return{kind:"error",requestId:e,error:r}}function U(e,r){const t=r[e.type];if(!t)return null;const n=t(e.payload);return N(e.requestId,e.type,n)}Object.defineProperty(exports,"BodhiApiError",{enumerable:!0,get:()=>i.BodhiApiError});Object.defineProperty(exports,"BodhiError",{enumerable:!0,get:()=>i.BodhiError});Object.defineProperty(exports,"unwrapResponse",{enumerable:!0,get:()=>i.unwrapResponse});exports.BACKEND_SERVER_NOT_CONNECTED=d;exports.BACKEND_SERVER_NOT_REACHABLE=E;exports.DIRECT_STATE_NOT_INITIALIZED=O;exports.EXTENSION_STATE_NOT_FOUND=S;exports.EXTENSION_STATE_NOT_INITIALIZED=_;exports.INITIAL_AUTH_STATE=P;exports.InMemoryStorage=V;exports.NOOP_STATE_CALLBACK=H;exports.PENDING_EXTENSION_READY=u;exports.SERVER_ERROR_CODES=s;exports.backendServerNotReady=f;exports.buildError=K;exports.buildEvent=j;exports.buildResponse=N;exports.createApiError=y;exports.createDirectStateNotReachable=D;exports.createDirectStateNotReady=h;exports.createDirectStateReady=v;exports.createExtensionStateNotFound=g;exports.createExtensionStateNotInitialized=C;exports.createOperationError=o;exports.getBackendServerState=B;exports.getExtensionId=L;exports.getServerUrl=w;exports.handleRequest=U;exports.isAuthError=m;exports.isAuthLoading=q;exports.isAuthenticated=k;exports.isClientReady=b;exports.isDirectClientReady=R;exports.isDirectServerReady=I;exports.isDirectState=l;exports.isExtensionClientReady=T;exports.isExtensionServerReady=x;exports.isExtensionState=a;exports.isServerReady=c;exports.throwAccessRequestDenialError=p;
@@ -10,7 +10,7 @@ export type { BackendServerState, ClientState, ConnectionMode, DirectState, Exte
10
10
  export { INITIAL_AUTH_STATE, isAuthError, isAuthLoading, isAuthenticated } from './auth';
11
11
  export type { AuthError, AuthState, AuthStatus } from './auth';
12
12
  export type { Tokens, UserInfo } from './user-info';
13
- export type { UserScope } from '@bodhiapp/ts-client';
13
+ export type { FlowType, RequestedResourcesV1, UserScope } from '@bodhiapp/ts-client';
14
14
  export type { ClientConfig, DiscoveryResult, LogLevel } from './config';
15
15
  export type LoginProgressStage = 'requesting' | 'reviewing' | 'authenticating';
16
16
  export type LoginProgressCallback = (stage: LoginProgressStage) => void;
@@ -24,6 +24,9 @@ export interface LoginOptions {
24
24
  pollTimeoutMs?: number;
25
25
  }
26
26
  export type { BrowserInfo, OSInfo } from './platform';
27
+ export { InMemoryStorage } from './storage';
28
+ export type { IStorage, InitialTokens } from './storage';
27
29
  export { NOOP_STATE_CALLBACK } from './callback';
28
30
  export type { AuthStateChange, ClientStateChange, StateChange, StateChangeCallback, } from './callback';
29
31
  export { buildError, buildEvent, buildResponse, handleRequest } from '../onboarding/protocol-utils';
32
+ export type { McpFetchLike, McpTransportConfig } from '../mcp-fetch';
@@ -1,8 +1,8 @@
1
- import { BodhiApiError as a, BodhiError as l } from "@bodhiapp/bodhi-browser-types";
2
- import { BodhiApiError as Z, BodhiError as $, unwrapResponse as j } from "@bodhiapp/bodhi-browser-types";
1
+ import { BodhiApiError as d, BodhiError as l } from "@bodhiapp/bodhi-browser-types";
2
+ import { BodhiApiError as M, BodhiError as Z, unwrapResponse as $ } from "@bodhiapp/bodhi-browser-types";
3
3
  const v = (e, r, n) => {
4
4
  const t = r?.error?.message || `HTTP ${e}`;
5
- return new a(e, r, t, n);
5
+ return new d(e, r, t, n);
6
6
  }, o = (e, r) => new l(e, r);
7
7
  function S(e) {
8
8
  throw e === "denied" ? o("access_request_denied", "Access request was denied") : e === "expired" ? o("access_request_expired", "Access request expired") : o("access_request_failed", `Access request failed: ${e}`);
@@ -24,7 +24,7 @@ const i = {
24
24
  status: "pending-extension-ready",
25
25
  version: null,
26
26
  error: null
27
- }, _ = {
27
+ }, f = {
28
28
  status: "not-connected",
29
29
  version: null,
30
30
  error: null
@@ -32,39 +32,39 @@ const i = {
32
32
  function u(e) {
33
33
  return e.status === "ready";
34
34
  }
35
- function x(e, r = "unknown", n = i.SERVER_NOT_READY, t, d) {
35
+ function h(e, r = "unknown", n = i.SERVER_NOT_READY, t, a) {
36
36
  return {
37
37
  status: e,
38
38
  version: r,
39
39
  error: n,
40
40
  deployment: t ?? null,
41
- client_id: d ?? null
41
+ client_id: a ?? null
42
42
  };
43
43
  }
44
44
  function c(e) {
45
45
  return e.type === "extension";
46
46
  }
47
- function f(e) {
47
+ function _(e) {
48
48
  return e.type === "direct";
49
49
  }
50
- function I(e) {
50
+ function x(e) {
51
51
  return typeof e.server == "object" && e.server.status !== "not-connected" && u(e.server);
52
52
  }
53
53
  function p(e) {
54
54
  return e.url !== null;
55
55
  }
56
- const D = {
56
+ const I = {
57
57
  type: "direct",
58
58
  url: null,
59
- server: _
59
+ server: f
60
60
  };
61
61
  function O(e, r = "unknown") {
62
62
  return { type: "direct", server: { status: "ready", version: r, error: null }, url: e };
63
63
  }
64
- function h(e) {
64
+ function D(e) {
65
65
  return { type: "direct", server: E, url: e };
66
66
  }
67
- function C(e, r) {
67
+ function g(e, r) {
68
68
  return { type: "direct", server: r, url: e };
69
69
  }
70
70
  const R = {
@@ -72,26 +72,26 @@ const R = {
72
72
  extension: "not-initialized",
73
73
  extensionId: null,
74
74
  server: s
75
- }, A = {
75
+ }, y = {
76
76
  type: "extension",
77
77
  extension: "not-found",
78
78
  extensionId: null,
79
79
  server: s
80
80
  };
81
- function w(e) {
81
+ function C(e) {
82
82
  return e.extension === "ready" && e.server.status !== "pending-extension-ready" && u(e.server);
83
83
  }
84
- function N(e) {
84
+ function A(e) {
85
85
  return e.extension === "ready";
86
86
  }
87
- function B() {
87
+ function w() {
88
88
  return R;
89
89
  }
90
- function g() {
91
- return A;
90
+ function k() {
91
+ return y;
92
92
  }
93
- function k(e) {
94
- return c(e) ? N(e) : p(e);
93
+ function B(e) {
94
+ return c(e) ? A(e) : p(e);
95
95
  }
96
96
  function q(e) {
97
97
  return e.server;
@@ -100,7 +100,7 @@ function L(e) {
100
100
  return c(e) ? e.extensionId ?? void 0 : void 0;
101
101
  }
102
102
  function b(e) {
103
- return f(e) ? e.url ?? void 0 : void 0;
103
+ return _(e) ? e.url ?? void 0 : void 0;
104
104
  }
105
105
  function m(e) {
106
106
  return e.status === "authenticated";
@@ -115,62 +115,83 @@ const K = {
115
115
  status: "idle",
116
116
  user: null,
117
117
  accessToken: null,
118
- error: null
119
- }, P = () => {
118
+ error: null,
119
+ refreshToken: null,
120
+ expiresAt: null,
121
+ isTokenRefresh: !1
120
122
  };
121
- function U(e, r) {
123
+ class P {
124
+ constructor() {
125
+ this.store = /* @__PURE__ */ new Map();
126
+ }
127
+ async get(r) {
128
+ return this.store.get(r) ?? null;
129
+ }
130
+ async set(r) {
131
+ Object.entries(r).forEach(([n, t]) => {
132
+ this.store.set(n, String(t));
133
+ });
134
+ }
135
+ async remove(r) {
136
+ r.forEach((n) => this.store.delete(n));
137
+ }
138
+ }
139
+ const U = () => {
140
+ };
141
+ function X(e, r) {
122
142
  return { kind: "event", type: e, payload: r };
123
143
  }
124
144
  function T(e, r, n) {
125
145
  return { kind: "response", type: r, requestId: e, payload: n };
126
146
  }
127
- function X(e, r) {
147
+ function Y(e, r) {
128
148
  return { kind: "error", requestId: e, error: r };
129
149
  }
130
- function Y(e, r) {
150
+ function j(e, r) {
131
151
  const n = r[e.type];
132
152
  if (!n) return null;
133
153
  const t = n(e.payload);
134
154
  return T(e.requestId, e.type, t);
135
155
  }
136
156
  export {
137
- _ as BACKEND_SERVER_NOT_CONNECTED,
157
+ f as BACKEND_SERVER_NOT_CONNECTED,
138
158
  E as BACKEND_SERVER_NOT_REACHABLE,
139
- Z as BodhiApiError,
140
- $ as BodhiError,
141
- D as DIRECT_STATE_NOT_INITIALIZED,
142
- A as EXTENSION_STATE_NOT_FOUND,
159
+ M as BodhiApiError,
160
+ Z as BodhiError,
161
+ I as DIRECT_STATE_NOT_INITIALIZED,
162
+ y as EXTENSION_STATE_NOT_FOUND,
143
163
  R as EXTENSION_STATE_NOT_INITIALIZED,
144
164
  K as INITIAL_AUTH_STATE,
145
- P as NOOP_STATE_CALLBACK,
165
+ P as InMemoryStorage,
166
+ U as NOOP_STATE_CALLBACK,
146
167
  s as PENDING_EXTENSION_READY,
147
168
  i as SERVER_ERROR_CODES,
148
- x as backendServerNotReady,
149
- X as buildError,
150
- U as buildEvent,
169
+ h as backendServerNotReady,
170
+ Y as buildError,
171
+ X as buildEvent,
151
172
  T as buildResponse,
152
173
  v as createApiError,
153
- h as createDirectStateNotReachable,
154
- C as createDirectStateNotReady,
174
+ D as createDirectStateNotReachable,
175
+ g as createDirectStateNotReady,
155
176
  O as createDirectStateReady,
156
- g as createExtensionStateNotFound,
157
- B as createExtensionStateNotInitialized,
177
+ k as createExtensionStateNotFound,
178
+ w as createExtensionStateNotInitialized,
158
179
  o as createOperationError,
159
180
  q as getBackendServerState,
160
181
  L as getExtensionId,
161
182
  b as getServerUrl,
162
- Y as handleRequest,
183
+ j as handleRequest,
163
184
  V as isAuthError,
164
185
  H as isAuthLoading,
165
186
  m as isAuthenticated,
166
- k as isClientReady,
187
+ B as isClientReady,
167
188
  p as isDirectClientReady,
168
- I as isDirectServerReady,
169
- f as isDirectState,
170
- N as isExtensionClientReady,
171
- w as isExtensionServerReady,
189
+ x as isDirectServerReady,
190
+ _ as isDirectState,
191
+ A as isExtensionClientReady,
192
+ C as isExtensionServerReady,
172
193
  c as isExtensionState,
173
194
  u as isServerReady,
174
195
  S as throwAccessRequestDenialError,
175
- j as unwrapResponse
196
+ $ as unwrapResponse
176
197
  };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pluggable storage interface and implementations for token persistence
3
+ */
4
+ /**
5
+ * Storage adapter interface for token persistence.
6
+ * Implementations: LocalStorageAdapter (web), ChromeSessionStorageAdapter (ext), InMemoryStorage (CLI/headless)
7
+ */
8
+ export interface IStorage {
9
+ get(key: string): Promise<string | null>;
10
+ set(items: Record<string, string | number>): Promise<void>;
11
+ remove(keys: string[]): Promise<void>;
12
+ }
13
+ /**
14
+ * In-memory storage adapter for CLI/headless use cases.
15
+ * Tokens live only in process memory — use onStateChange callback for external persistence.
16
+ */
17
+ export declare class InMemoryStorage implements IStorage {
18
+ private store;
19
+ get(key: string): Promise<string | null>;
20
+ set(items: Record<string, string | number>): Promise<void>;
21
+ remove(keys: string[]): Promise<void>;
22
+ }
23
+ /**
24
+ * Pre-existing tokens for injection during client initialization.
25
+ * Used by CLI/headless hosts that obtain OAuth tokens independently.
26
+ */
27
+ export interface InitialTokens {
28
+ accessToken: string;
29
+ refreshToken?: string;
30
+ expiresAt?: number;
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js-core",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "Core types and interfaces for Bodhi Browser SDK",
5
5
  "type": "module",
6
6
  "main": "dist/bodhi-core.cjs.js",
@@ -24,6 +24,11 @@
24
24
  "types": "./dist/api/index.d.ts",
25
25
  "import": "./dist/api/index.esm.js",
26
26
  "require": "./dist/api/index.cjs.js"
27
+ },
28
+ "./mcp": {
29
+ "types": "./dist/mcp.d.ts",
30
+ "import": "./dist/mcp.esm.js",
31
+ "require": "./dist/mcp.cjs.js"
27
32
  }
28
33
  },
29
34
  "files": [
@@ -53,16 +58,26 @@
53
58
  "build:prod": "npm run copy-deps && npm run build-cli && vite build --mode production",
54
59
  "lint": "eslint . --ext .js,.jsx,.ts,.tsx && prettier --check .",
55
60
  "lint:fix": "prettier --write . && eslint . --ext .js,.jsx,.ts,.tsx --fix",
56
- "typecheck": "tsc --noEmit"
61
+ "typecheck": "tsc --noEmit",
62
+ "test": "vitest run"
63
+ },
64
+ "peerDependencies": {
65
+ "@modelcontextprotocol/sdk": "^1.0.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "@modelcontextprotocol/sdk": {
69
+ "optional": true
70
+ }
57
71
  },
58
72
  "dependencies": {
59
- "@bodhiapp/bodhi-browser-types": "0.0.30",
60
- "@bodhiapp/setup-modal-types": "0.0.30",
61
- "@bodhiapp/ts-client": "0.1.24",
73
+ "@bodhiapp/bodhi-browser-types": "0.0.32",
74
+ "@bodhiapp/setup-modal-types": "0.0.32",
75
+ "@bodhiapp/ts-client": "0.1.26",
62
76
  "ua-parser-js": "^1.0.40"
63
77
  },
64
78
  "devDependencies": {
65
79
  "@eslint/js": "^9.23.0",
80
+ "@modelcontextprotocol/sdk": "^1.29.0",
66
81
  "@types/node": "^20.19.10",
67
82
  "@types/ua-parser-js": "^0.7.39",
68
83
  "@typescript-eslint/eslint-plugin": "8.28.0",
@@ -76,6 +91,7 @@
76
91
  "tslib": "^2.6.2",
77
92
  "typescript": "^5.8.3",
78
93
  "vite": "^7.1.12",
79
- "vite-plugin-dts": "^4.5.4"
94
+ "vite-plugin-dts": "^4.5.4",
95
+ "vitest": "^4.1.2"
80
96
  }
81
97
  }