11import { createLogger } from '@sim/logger'
22import { getErrorMessage } from '@sim/utils/errors'
3+ import { LRUCache } from 'lru-cache'
34import { type NextRequest , NextResponse } from 'next/server'
45import { copilotToolExecuteInternalBodySchema } from '@/lib/api/contracts/copilot'
56import { validationErrorResponse } from '@/lib/api/server'
@@ -33,21 +34,15 @@ const logger = createLogger('CopilotToolExecuteInternalAPI')
3334 * provenance activated by one tool call must stay visible to the next call's
3435 * egress projection, exactly as the request-lifecycle registry accumulates
3536 * across a turn. Keyed by the turn (messageId) so one background lane shares
36- * one registry; TTL -evicted since nothing signals turn end on this route.
37+ * one registry; idle -evicted since nothing signals turn end on this route.
3738 *
3839 * The turn's secret authority, which Go's body cannot carry, is cached beside it
3940 * (see {@link getRunSecretAuthority}).
4041 */
41- const TURN_REGISTRY_TTL_MS = 10 * 60 * 1000
42- const TURN_REGISTRY_CACHE_MAX = 256
43- const turnRegistryCache = new Map <
42+ const turnContextCache = new LRUCache <
4443 string ,
45- {
46- registry : ResolvedSecretTraceRegistry
47- secretAuthority : TurnSecretAuthority
48- expiresAt : number
49- }
50- > ( )
44+ { registry : ResolvedSecretTraceRegistry ; secretAuthority : TurnSecretAuthority }
45+ > ( { max : 256 , ttl : 10 * 60 * 1000 , updateAgeOnGet : true } )
5146
5247async function getTurnToolContext (
5348 userId : string ,
@@ -58,12 +53,8 @@ async function getTurnToolContext(
5853 organizationId ?: string
5954) : Promise < { registry : ResolvedSecretTraceRegistry ; secretAuthority : TurnSecretAuthority } > {
6055 const key = `${ userId } \u0000${ workspaceId ?? '' } \u0000${ organizationId ?? '' } \u0000${ chatId ?? '' } \u0000${ messageId ?? '' } \u0000${ requestMode ?? '' } `
61- const now = Date . now ( )
62- const hit = turnRegistryCache . get ( key )
63- if ( hit && hit . expiresAt > now ) {
64- hit . expiresAt = now + TURN_REGISTRY_TTL_MS
65- return hit
66- }
56+ const hit = turnContextCache . get ( key )
57+ if ( hit ) return hit
6758 const [ environmentContext , secretAuthority ] = await Promise . all ( [
6859 prepareCopilotEnvironmentContext ( userId , workspaceId , {
6960 includeSecrets : requestMode !== 'assistant' ,
@@ -72,19 +63,8 @@ async function getTurnToolContext(
7263 ? getRunSecretAuthority ( { streamId : messageId , userId, chatId } )
7364 : NO_SECRET_AUTHORITY ,
7465 ] )
75- for ( const [ cachedKey , cached ] of turnRegistryCache ) {
76- if ( cached . expiresAt <= now ) turnRegistryCache . delete ( cachedKey )
77- }
78- if ( turnRegistryCache . size >= TURN_REGISTRY_CACHE_MAX ) {
79- const oldest = turnRegistryCache . keys ( ) . next ( ) . value
80- if ( oldest !== undefined ) turnRegistryCache . delete ( oldest )
81- }
82- const entry = {
83- registry : environmentContext . resolvedSecretTraceRegistry ,
84- secretAuthority,
85- expiresAt : now + TURN_REGISTRY_TTL_MS ,
86- }
87- turnRegistryCache . set ( key , entry )
66+ const entry = { registry : environmentContext . resolvedSecretTraceRegistry , secretAuthority }
67+ turnContextCache . set ( key , entry )
8868 return entry
8969}
9070
0 commit comments