@@ -45,6 +45,8 @@ export type MemoConfig = {
4545 providers : ProviderConfig [ ]
4646}
4747
48+ type ParsedMemoConfig = Omit < Partial < MemoConfig > , 'providers' > & { providers ?: unknown }
49+
4850const DEFAULT_MEMO_HOME = join ( homedir ( ) , '.memo' )
4951const DEFAULT_SESSIONS_DIR = 'sessions'
5052const DEFAULT_MEMORY_FILE = 'memo.md'
@@ -64,6 +66,29 @@ const DEFAULT_CONFIG: MemoConfig = {
6466 mcp_servers : { } ,
6567}
6668
69+ function formatTomlKey ( key : string ) {
70+ return / ^ [ A - Z a - z 0 - 9 _ - ] + $ / . test ( key ) ? key : JSON . stringify ( key )
71+ }
72+
73+ function normalizeProviders ( input : unknown ) : ProviderConfig [ ] {
74+ if ( ! input || typeof input !== 'object' || Array . isArray ( input ) ) return [ ]
75+
76+ const providers : ProviderConfig [ ] = [ ]
77+ for ( const [ key , value ] of Object . entries ( input as Record < string , unknown > ) ) {
78+ if ( ! value ) continue
79+ const entries = Array . isArray ( value ) ? value : [ value ]
80+ for ( const entry of entries ) {
81+ if ( ! entry || typeof entry !== 'object' ) continue
82+ const provider = { ...( entry as ProviderConfig ) }
83+ if ( ( typeof provider . name !== 'string' || provider . name . length === 0 ) && key ) {
84+ provider . name = key
85+ }
86+ providers . push ( provider )
87+ }
88+ }
89+ return providers
90+ }
91+
6792function expandHome ( path : string ) {
6893 if ( path . startsWith ( '~' ) ) {
6994 return join ( homedir ( ) , path . slice ( 1 ) )
@@ -74,13 +99,23 @@ function expandHome(path: string) {
7499function serializeConfig ( config : MemoConfig ) {
75100 const providers = config . providers
76101 . map (
77- ( p ) =>
78- `[[providers]]
79- name = "${ p . name } "
80- env_api_key = "${ p . env_api_key } "
81- model = "${ p . model } "
82- ${ p . base_url ? `base_url = "${ p . base_url } "\n` : '' } `,
102+ ( p ) => {
103+ const name = typeof p ?. name === 'string' ? p . name : ''
104+ if ( ! name ) return ''
105+ const key = formatTomlKey ( name )
106+ const lines = [
107+ `[[providers.${ key } ]]` ,
108+ `name = ${ JSON . stringify ( name ) } ` ,
109+ `env_api_key = ${ JSON . stringify ( String ( p . env_api_key ?? '' ) ) } ` ,
110+ `model = ${ JSON . stringify ( String ( p . model ?? '' ) ) } ` ,
111+ ]
112+ if ( p . base_url ) {
113+ lines . push ( `base_url = ${ JSON . stringify ( String ( p . base_url ) ) } ` )
114+ }
115+ return lines . join ( '\n' )
116+ } ,
83117 )
118+ . filter ( Boolean )
84119 . join ( '\n\n' )
85120
86121 let mcpSection = ''
@@ -139,12 +174,13 @@ export async function loadMemoConfig(): Promise<LoadedConfig> {
139174 return { config : DEFAULT_CONFIG , home, configPath, needsSetup : true }
140175 }
141176 const text = await file . text ( )
142- const parsed = parse ( text ) as Partial < MemoConfig >
177+ const parsed = parse ( text ) as ParsedMemoConfig
178+ const providers = normalizeProviders ( parsed . providers )
143179 const merged : MemoConfig = {
144180 current_provider : parsed . current_provider ?? DEFAULT_CONFIG . current_provider ,
145181 max_steps : parsed . max_steps ?? DEFAULT_CONFIG . max_steps ,
146182 stream_output : parsed . stream_output ?? DEFAULT_CONFIG . stream_output ,
147- providers : parsed . providers ?? [ ] ,
183+ providers,
148184 mcp_servers : parsed . mcp_servers ?? { } ,
149185 }
150186 const needsSetup = ! merged . providers . length
@@ -198,14 +234,22 @@ export function buildSessionPath(baseDir: string, sessionId: string) {
198234 const HH = String ( now . getHours ( ) ) . padStart ( 2 , '0' )
199235 const MM = String ( now . getMinutes ( ) ) . padStart ( 2 , '0' )
200236 const SS = String ( now . getSeconds ( ) ) . padStart ( 2 , '0' )
201- const cwd = process . cwd ( )
202- const safeParts = cwd . split ( / [ / \\ ] + / ) . map ( ( p ) => sanitizePathComponent ( p ) )
203- const truncatedParts = truncatePath ( safeParts , 180 )
204- const dirName = truncatedParts . join ( '-' )
237+ const dirName = buildSessionDirName ( process . cwd ( ) )
205238 const fileName = `${ yyyy } -${ mm } -${ dd } _${ HH } ${ MM } ${ SS } _${ sessionId } .jsonl`
206239 return join ( baseDir , dirName , fileName )
207240}
208241
242+ function buildSessionDirName ( cwd : string ) {
243+ const safeParts = cwd . split ( / [ / \\ ] + / ) . map ( ( p ) => sanitizePathComponent ( p ) )
244+ const truncatedParts = truncatePath ( safeParts , 180 )
245+ return truncatedParts . join ( '-' ) || 'root'
246+ }
247+
248+ /** 获取某个 cwd 对应的 session 日志目录。 */
249+ export function getSessionLogDir ( baseDir : string , cwd : string ) {
250+ return join ( baseDir , buildSessionDirName ( cwd ) )
251+ }
252+
209253/** 提供一个新的 sessionId,便于外部复用。 */
210254export function createSessionId ( ) {
211255 return randomUUID ( )
0 commit comments