Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 1 addition & 51 deletions src/core/dataprompt.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<Output = any>(url: string | Request | RequestContext): Promise<Output>;
Expand All @@ -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<Genkit | undefined> {
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] };
Expand Down
54 changes: 54 additions & 0 deletions src/core/genkit-factory.ts
Original file line number Diff line number Diff line change
@@ -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<Genkit | undefined> {
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;
}
66 changes: 66 additions & 0 deletions tests/unit/genkit-factory.test.ts
Original file line number Diff line number Diff line change
@@ -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']),
}));
});
});