diff --git a/src/core/dataprompt.ts b/src/core/dataprompt.ts index cdd0a8c..80563af 100644 --- a/src/core/dataprompt.ts +++ b/src/core/dataprompt.ts @@ -1,5 +1,4 @@ import { Genkit } from 'genkit'; -import { googleAI } from '@genkit-ai/googleai'; import { RequestContext } from './interfaces.js'; import { PluginManager } from './plugin.manager.js'; import { createRouteCatalog } from '../routing/index.js'; @@ -8,12 +7,10 @@ import { SchemaMap, registerUserSchemas } from '../utils/schema-loader.js'; import { RouteManager, createRouteManager } from '../routing/route-manager.js'; import { FlowManager, createFlowManager } from '../routing/flow-manager.js'; import { TaskManager, createTaskManager } from '../routing/task-manager.js'; -import { dateFormat } from '../utils/helpers/date-format.js'; -import { findUp } from 'find-up'; -import { pathToFileURL } from 'node:url'; import { DatapromptConfig, DatapromptUserConfig } from './config.js' import { ConfigManager } from './config.manager.js'; import { getLogManager } from '../utils/logging.js'; +import { createDefaultGenkit, loadUserGenkitInstance } from './genkit-factory.js'; export interface DatapromptStore { generate(url: string | Request | RequestContext): Promise; @@ -25,53 +22,6 @@ export interface DatapromptStore { userSchemas: SchemaMap; } -function createDefaultGenkit(config: DatapromptConfig): Genkit { - const plugins: any[] = []; - const googleApiKey = config.secrets?.GOOGLEAI_API_KEY || config.secrets?.GEMINI_API_KEY; - - if (config.genkitPlugins) { - plugins.push(...config.genkitPlugins); - } - - for (const plugin of config.plugins) { - if (plugin.provideGenkitPlugins) { - plugins.push(...plugin.provideGenkitPlugins()); - } - } - - // If no plugins are provided, or if we have a Google API key, - // we try to add Google AI support. - // We prioritize user provided plugins, but also support Google AI if key is present. - if (googleApiKey) { - // We can't easily check if googleAI is already in plugins because they are instances. - // However, Genkit supports multiple plugins. - plugins.push(googleAI({ apiKey: googleApiKey })); - } - - if (plugins.length === 0) { - throw new Error('FATAL: No Genkit plugins configured and GOOGLEAI_API_KEY/GEMINI_API_KEY not found.'); - } - - const ai = new Genkit({ plugins }); - ai.defineHelper('dateFormat', dateFormat); - return ai; -} - -async function loadUserGenkitInstance(rootDir: string): Promise { - const configPath = await findUp(['dataprompt.config.ts', 'dataprompt.config.js'], { cwd: rootDir }); - if (configPath) { - try { - const userModule = await import(pathToFileURL(configPath).toString()); - if (userModule.default?.genkit && userModule.default.genkit instanceof Genkit) { - return userModule.default.genkit; - } - } catch (e) { - getLogManager().system.warn(`Warning: Could not dynamically import user's genkit instance from ${configPath}.`, e); - } - } - return undefined; -} - function mergeConfigs(base: DatapromptConfig, override?: DatapromptUserConfig): DatapromptConfig { if (!override) { return { ...base, secrets: { ...base.secrets }, plugins: [...base.plugins], genkitPlugins: [...base.genkitPlugins] }; diff --git a/src/core/genkit-factory.ts b/src/core/genkit-factory.ts new file mode 100644 index 0000000..80d9094 --- /dev/null +++ b/src/core/genkit-factory.ts @@ -0,0 +1,54 @@ +import { Genkit } from 'genkit'; +import { googleAI } from '@genkit-ai/googleai'; +import { dateFormat } from '../utils/helpers/date-format.js'; +import { findUp } from 'find-up'; +import { pathToFileURL } from 'node:url'; +import { DatapromptConfig } from './config.js'; +import { getLogManager } from '../utils/logging.js'; + +export function createDefaultGenkit(config: DatapromptConfig): Genkit { + const plugins: any[] = []; + const googleApiKey = config.secrets?.GOOGLEAI_API_KEY || config.secrets?.GEMINI_API_KEY; + + if (config.genkitPlugins) { + plugins.push(...config.genkitPlugins); + } + + for (const plugin of config.plugins) { + if (plugin.provideGenkitPlugins) { + plugins.push(...plugin.provideGenkitPlugins()); + } + } + + // If no plugins are provided, or if we have a Google API key, + // we try to add Google AI support. + // We prioritize user provided plugins, but also support Google AI if key is present. + if (googleApiKey) { + // We can't easily check if googleAI is already in plugins because they are instances. + // However, Genkit supports multiple plugins. + plugins.push(googleAI({ apiKey: googleApiKey })); + } + + if (plugins.length === 0) { + throw new Error('FATAL: No Genkit plugins configured and GOOGLEAI_API_KEY/GEMINI_API_KEY not found.'); + } + + const ai = new Genkit({ plugins }); + ai.defineHelper('dateFormat', dateFormat); + return ai; +} + +export async function loadUserGenkitInstance(rootDir: string): Promise { + const configPath = await findUp(['dataprompt.config.ts', 'dataprompt.config.js'], { cwd: rootDir }); + if (configPath) { + try { + const userModule = await import(pathToFileURL(configPath).toString()); + if (userModule.default?.genkit && userModule.default.genkit instanceof Genkit) { + return userModule.default.genkit; + } + } catch (e) { + getLogManager().system.warn(`Warning: Could not dynamically import user's genkit instance from ${configPath}.`, e); + } + } + return undefined; +} diff --git a/tests/unit/genkit-factory.test.ts b/tests/unit/genkit-factory.test.ts new file mode 100644 index 0000000..2669c2b --- /dev/null +++ b/tests/unit/genkit-factory.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { createDefaultGenkit } from '../../src/core/genkit-factory.js'; +import { Genkit } from 'genkit'; +import { googleAI } from '@genkit-ai/googleai'; + +// Mock dependencies +vi.mock('genkit', () => { + return { + Genkit: vi.fn().mockImplementation(() => ({ + defineHelper: vi.fn(), + })), + }; +}); + +vi.mock('@genkit-ai/googleai', () => ({ + googleAI: vi.fn().mockReturnValue('mock-google-ai-plugin'), +})); + +vi.mock('../../src/utils/helpers/date-format.js', () => ({ + dateFormat: vi.fn(), +})); + +describe('GenkitFactory', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should create Genkit with Google AI plugin when API key is present', () => { + const mockConfig: any = { + secrets: { GOOGLEAI_API_KEY: 'test-key' }, + plugins: [], + genkitPlugins: [], + }; + + createDefaultGenkit(mockConfig); + + expect(googleAI).toHaveBeenCalledWith({ apiKey: 'test-key' }); + expect(Genkit).toHaveBeenCalledWith(expect.objectContaining({ + plugins: expect.arrayContaining(['mock-google-ai-plugin']), + })); + }); + + it('should throw error if no plugins and no API key', () => { + const mockConfig: any = { + secrets: {}, + plugins: [], + genkitPlugins: [], + }; + + expect(() => createDefaultGenkit(mockConfig)).toThrow('FATAL: No Genkit plugins configured and GOOGLEAI_API_KEY/GEMINI_API_KEY not found.'); + }); + + it('should include user provided genkit plugins', () => { + const mockConfig: any = { + secrets: {}, + plugins: [], + genkitPlugins: ['user-plugin'], + }; + + createDefaultGenkit(mockConfig); + + expect(Genkit).toHaveBeenCalledWith(expect.objectContaining({ + plugins: expect.arrayContaining(['user-plugin']), + })); + }); +});