11import { validateSingleAgent } from '@codebuff/common/templates/agent-validation'
2- import { userColumns } from '@codebuff/common/types/contracts/database'
32import { DynamicAgentTemplateSchema } from '@codebuff/common/types/dynamic-agent-template'
43import { getErrorObject } from '@codebuff/common/util/error'
54import z from 'zod/v4'
@@ -19,9 +18,13 @@ import type {
1918import type { DynamicAgentTemplate } from '@codebuff/common/types/dynamic-agent-template'
2019import type { ParamsOf } from '@codebuff/common/types/function-params'
2120
21+ type CachedUserInfo = Partial <
22+ NonNullable < Awaited < GetUserInfoFromApiKeyOutput < UserColumn > > >
23+ >
24+
2225const userInfoCache : Record <
2326 string ,
24- Awaited < GetUserInfoFromApiKeyOutput < UserColumn > > | null
27+ CachedUserInfo | null
2528> = { }
2629
2730const agentsResponseSchema = z . object ( {
@@ -34,20 +37,29 @@ export async function getUserInfoFromApiKey<T extends UserColumn>(
3437) : GetUserInfoFromApiKeyOutput < T > {
3538 const { apiKey, fields, logger } = params
3639
37- if ( apiKey in userInfoCache ) {
38- const userInfo = userInfoCache [ apiKey ]
39- if ( userInfo === null ) {
40- throw new AuthenticationError ( 'Authentication failed' , 401 )
41- }
42- return Object . fromEntries (
43- fields . map ( ( field ) => [ field , userInfo [ field ] ] ) ,
44- ) as {
45- [ K in ( typeof fields ) [ number ] ] : ( typeof userInfo ) [ K ]
46- }
40+ const cached = userInfoCache [ apiKey ]
41+ if ( cached === null ) {
42+ throw new AuthenticationError ( 'Authentication failed' , 401 )
4743 }
44+ if (
45+ cached &&
46+ fields . every ( ( field ) =>
47+ Object . prototype . hasOwnProperty . call ( cached , field ) ,
48+ )
49+ ) {
50+ return Object . fromEntries ( fields . map ( ( field ) => [ field , cached [ field ] ] ) ) as {
51+ [ K in T ] : CachedUserInfo [ K ]
52+ } as Awaited < GetUserInfoFromApiKeyOutput < T > >
53+ }
54+
55+ const fieldsToFetch = cached
56+ ? fields . filter (
57+ ( field ) => ! Object . prototype . hasOwnProperty . call ( cached , field ) ,
58+ )
59+ : fields
4860
4961 const urlParams = new URLSearchParams ( {
50- fields : userColumns . join ( ',' ) ,
62+ fields : fieldsToFetch . join ( ',' ) ,
5163 } )
5264 const url = new URL ( `/api/v1/me?${ urlParams } ` , WEBSITE_URL )
5365
@@ -100,8 +112,13 @@ export async function getUserInfoFromApiKey<T extends UserColumn>(
100112 throw new NetworkError ( 'Request failed' , ErrorCodes . UNKNOWN_ERROR , response . status )
101113 }
102114
115+ const cachedBeforeMerge = userInfoCache [ apiKey ]
103116 try {
104- userInfoCache [ apiKey ] = await response . json ( )
117+ const fetchedFields = ( await response . json ( ) ) as CachedUserInfo
118+ userInfoCache [ apiKey ] = {
119+ ...( cachedBeforeMerge ?? { } ) ,
120+ ...fetchedFields ,
121+ }
105122 } catch ( error ) {
106123 logger . error (
107124 { error : getErrorObject ( error ) , apiKey, fields } ,
@@ -114,11 +131,21 @@ export async function getUserInfoFromApiKey<T extends UserColumn>(
114131 if ( userInfo === null ) {
115132 throw new AuthenticationError ( 'Authentication failed' , 401 )
116133 }
134+ if (
135+ ! userInfo ||
136+ ! fields . every ( ( field ) =>
137+ Object . prototype . hasOwnProperty . call ( userInfo , field ) ,
138+ )
139+ ) {
140+ logger . error (
141+ { apiKey, fields } ,
142+ 'getUserInfoFromApiKey: response missing required fields' ,
143+ )
144+ throw new NetworkError ( 'Request failed' , ErrorCodes . UNKNOWN_ERROR , response . status )
145+ }
117146 return Object . fromEntries (
118147 fields . map ( ( field ) => [ field , userInfo [ field ] ] ) ,
119- ) as {
120- [ K in ( typeof fields ) [ number ] ] : ( typeof userInfo ) [ K ]
121- }
148+ ) as Awaited < GetUserInfoFromApiKeyOutput < T > >
122149}
123150
124151export async function fetchAgentFromDatabase (
0 commit comments