diff --git a/packages/core/src/core/prompts.test.ts b/packages/core/src/core/prompts.test.ts index bb7b0b52..028f044f 100644 --- a/packages/core/src/core/prompts.test.ts +++ b/packages/core/src/core/prompts.test.ts @@ -106,3 +106,96 @@ describe('Core System Prompt (prompts.ts)', () => { expect(prompt).toMatchSnapshot(); }); }); + +describe('URL matching with trailing slash compatibility', () => { + it('should match URLs with and without trailing slash', () => { + const config = { + systemPromptMappings: [ + { + baseUrls: ['https://api.example.com'], + modelNames: ['gpt-4'], + template: 'Custom template for example.com', + }, + { + baseUrls: ['https://api.openai.com/'], + modelNames: ['gpt-3.5-turbo'], + template: 'Custom template for openai.com', + }, + ], + }; + + // Simulate environment variables + const originalEnv = process.env; + + // Test case 1: No trailing slash in config, actual URL has trailing slash + process.env = { + ...originalEnv, + OPENAI_BASE_URL: 'https://api.example.com/', + OPENAI_MODEL: 'gpt-4', + }; + + const result1 = getCoreSystemPrompt(undefined, config); + expect(result1).toContain('Custom template for example.com'); + + // Test case 2: Config has trailing slash, actual URL has no trailing slash + process.env = { + ...originalEnv, + OPENAI_BASE_URL: 'https://api.openai.com', + OPENAI_MODEL: 'gpt-3.5-turbo', + }; + + const result2 = getCoreSystemPrompt(undefined, config); + expect(result2).toContain('Custom template for openai.com'); + + // Test case 3: No trailing slash in config, actual URL has no trailing slash + process.env = { + ...originalEnv, + OPENAI_BASE_URL: 'https://api.example.com', + OPENAI_MODEL: 'gpt-4', + }; + + const result3 = getCoreSystemPrompt(undefined, config); + expect(result3).toContain('Custom template for example.com'); + + // Test case 4: Config has trailing slash, actual URL has trailing slash + process.env = { + ...originalEnv, + OPENAI_BASE_URL: 'https://api.openai.com/', + OPENAI_MODEL: 'gpt-3.5-turbo', + }; + + const result4 = getCoreSystemPrompt(undefined, config); + expect(result4).toContain('Custom template for openai.com'); + + // Restore original environment variables + process.env = originalEnv; + }); + + it('should not match when URLs are different', () => { + const config = { + systemPromptMappings: [ + { + baseUrls: ['https://api.example.com'], + modelNames: ['gpt-4'], + template: 'Custom template for example.com', + }, + ], + }; + + const originalEnv = process.env; + + // Test case: URLs do not match + process.env = { + ...originalEnv, + OPENAI_BASE_URL: 'https://api.different.com', + OPENAI_MODEL: 'gpt-4', + }; + + const result = getCoreSystemPrompt(undefined, config); + // Should return default template, not contain custom template + expect(result).not.toContain('Custom template for example.com'); + + // Restore original environment variables + process.env = originalEnv; + }); +}); diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index 09b1d60e..664501d9 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -28,6 +28,21 @@ export interface SystemPromptConfig { systemPromptMappings?: ModelTemplateMapping[]; } +/** + * Normalizes a URL by removing trailing slash for consistent comparison + */ +function normalizeUrl(url: string): string { + return url.endsWith('/') ? url.slice(0, -1) : url; +} + +/** + * Checks if a URL matches any URL in the array, ignoring trailing slashes + */ +function urlMatches(urlArray: string[], targetUrl: string): boolean { + const normalizedTarget = normalizeUrl(targetUrl); + return urlArray.some((url) => normalizeUrl(url) === normalizedTarget); +} + export function getCoreSystemPrompt( userMemory?: string, config?: SystemPromptConfig, @@ -59,13 +74,13 @@ export function getCoreSystemPrompt( if ( baseUrls && modelNames && - baseUrls.includes(currentBaseUrl) && + urlMatches(baseUrls, currentBaseUrl) && modelNames.includes(currentModel) ) { return true; } - if (baseUrls && baseUrls.includes(currentBaseUrl) && !modelNames) { + if (baseUrls && urlMatches(baseUrls, currentBaseUrl) && !modelNames) { return true; } if (modelNames && modelNames.includes(currentModel) && !baseUrls) {