Compare commits

...

5 Commits

Author SHA1 Message Date
Dani Akash
f6fd8d8140 fix: klavis cache invalidation 2026-03-18 14:31:39 +05:30
Dani Akash
4bd6071fc6 fix: code reviews 2026-03-18 13:25:46 +05:30
Dani Akash
adfec7fde7 fix: prevent cache key collision 2026-03-18 11:25:06 +05:30
Dani Akash
cb7d00bb3b fix: invalidate client when a mcp server is deleted 2026-03-18 11:24:04 +05:30
Dani Akash
bc56503957 feat: use cache key to persist klavis client 2026-03-18 10:55:37 +05:30
4 changed files with 64 additions and 9 deletions

View File

@@ -16,16 +16,16 @@ import { ConversationIdParamSchema } from '../utils/validation'
interface ChatRouteDeps {
browser: Browser
registry: ToolRegistry
klavisClient: KlavisClient
browserosId?: string
rateLimiter?: RateLimiter
aiSdkDevtoolsEnabled?: boolean
}
export function createChatRoutes(deps: ChatRouteDeps) {
const { browserosId, rateLimiter } = deps
const { browserosId, rateLimiter, klavisClient } = deps
const sessionStore = new SessionStore()
const klavisClient = new KlavisClient()
const service = new ChatService({
sessionStore,
klavisClient,

View File

@@ -17,6 +17,7 @@ const ServerNameSchema = z.object({
interface KlavisRouteDeps {
browserosId: string
klavisClient: KlavisClient
}
const normalizeServerKey = (value: string): string =>
@@ -43,8 +44,7 @@ const getAuthUrlForServer = (
}
export function createKlavisRoutes(deps: KlavisRouteDeps) {
const { browserosId } = deps
const klavisClient = new KlavisClient()
const { browserosId, klavisClient } = deps
// Chain route definitions for proper Hono RPC type inference
return new Hono()
@@ -124,6 +124,7 @@ export function createKlavisRoutes(deps: KlavisRouteDeps) {
logger.info('Adding server to strata', { serverName })
klavisClient.invalidateStrataCache(browserosId)
const result = await klavisClient.createStrata(browserosId, [serverName])
return c.json({
@@ -154,6 +155,7 @@ export function createKlavisRoutes(deps: KlavisRouteDeps) {
try {
await klavisClient.submitApiKey(apiKeyUrl, apiKey)
klavisClient.invalidateStrataCache(browserosId)
logger.info('Submitted API key for server', { serverName })
@@ -185,6 +187,7 @@ export function createKlavisRoutes(deps: KlavisRouteDeps) {
logger.info('Removing server from strata', { serverName })
await klavisClient.removeServer(browserosId, serverName)
klavisClient.invalidateStrataCache(browserosId)
return c.json({
success: true,

View File

@@ -75,12 +75,15 @@ export async function createHttpServer(config: HttpServerConfig) {
const { onShutdown } = config
// Single KlavisClient shared across all routes for cache effectiveness
const klavisClient = new KlavisClient()
// Connect Klavis proxy (non-blocking: browser tools still work if this fails)
let klavisProxy: KlavisProxyHandle | null = null
if (browserosId) {
try {
klavisProxy = await connectKlavisProxy({
klavisClient: new KlavisClient(),
klavisClient,
browserosId,
})
} catch (error) {
@@ -115,7 +118,7 @@ export async function createHttpServer(config: HttpServerConfig) {
.route('/skills', createSkillsRoutes())
.route('/test-provider', createProviderRoutes())
.route('/refine-prompt', createRefinePromptRoutes())
.route('/klavis', createKlavisRoutes({ browserosId: browserosId || '' }))
.route('/klavis', createKlavisRoutes({ browserosId: browserosId || '', klavisClient }))
.route(
'/mcp',
createMcpRoutes({
@@ -132,6 +135,7 @@ export async function createHttpServer(config: HttpServerConfig) {
createChatRoutes({
browser,
registry,
klavisClient,
browserosId,
rateLimiter,
aiSdkDevtoolsEnabled: config.aiSdkDevtoolsEnabled,

View File

@@ -28,13 +28,25 @@ export interface UserIntegration {
isAuthenticated: boolean
}
interface CachedStrata {
response: StrataCreateResponse
expiresAt: number
}
export class KlavisClient {
private baseUrl: string
private strataCache = new Map<string, CachedStrata>()
private pendingRequests = new Map<string, Promise<StrataCreateResponse>>()
private static STRATA_CACHE_TTL = 5 * 60 * 1000 // 5 minutes
constructor(baseUrl?: string) {
this.baseUrl = baseUrl || EXTERNAL_URLS.KLAVIS_PROXY
}
private buildStrataCacheKey(userId: string, servers: string[]): string {
return JSON.stringify([userId, ...[...servers].sort()])
}
private async request<T>(
method: string,
path: string,
@@ -77,18 +89,44 @@ export class KlavisClient {
}
/**
* Create Strata instance with specified servers
* Returns strataServerUrl for MCP connection and oauthUrls for authentication
* Create Strata instance with specified servers.
* Cached by (userId, sorted servers) with 5-min TTL.
* Concurrent requests for the same key are deduplicated.
*/
async createStrata(
userId: string,
servers: string[],
): Promise<StrataCreateResponse> {
return this.request<StrataCreateResponse>(
const cacheKey = this.buildStrataCacheKey(userId, servers)
const cached = this.strataCache.get(cacheKey)
if (cached && cached.expiresAt > Date.now()) {
return cached.response
}
const pending = this.pendingRequests.get(cacheKey)
if (pending) return pending
const promise = this.request<StrataCreateResponse>(
'POST',
'/mcp-server/strata/create',
{ userId, servers },
)
.then((response) => {
this.strataCache.set(cacheKey, {
response,
expiresAt: Date.now() + KlavisClient.STRATA_CACHE_TTL,
})
this.pendingRequests.delete(cacheKey)
return response
})
.catch((error) => {
this.pendingRequests.delete(cacheKey)
throw error
})
this.pendingRequests.set(cacheKey, promise)
return promise
}
/**
@@ -156,5 +194,15 @@ export class KlavisClient {
'DELETE',
`/mcp-server/strata/${strata.strataId}/servers?servers=${encodeURIComponent(serverName)}`,
)
this.strataCache.delete(this.buildStrataCacheKey(userId, [serverName]))
}
/** Clear all cached Strata entries for a user (call after auth changes). */
invalidateStrataCache(userId: string): void {
for (const key of this.strataCache.keys()) {
if (key.startsWith(`["${userId}"`)) {
this.strataCache.delete(key)
}
}
}
}