Skip to content

Commit 0be3e8c

Browse files
committed
feat: migrate @google/generative-ai → @google/genai + add Deep Research config
Task 1 — SDK Migration: - Replace @google/generative-ai with @google/genai in package.json - lib/gemini.ts: lazy-init GoogleGenAI client, ai.models.generateContent() - lib/sponsor/gemini-intent.ts: migrate to new SDK API surface - lib/sponsor/gemini-outreach.ts: migrate to new SDK API surface - response.text is now a property (not method) in new SDK Task 2 — Deep Research Config: - pipelineConfig.ts: add enableDeepResearch, deepResearchAgent, deepResearchPromptTemplate, infographicModel fields - lib/types/config.ts: add matching PipelineConfig interface fields
1 parent 8c8cc52 commit 0be3e8c

File tree

6 files changed

+64
-29
lines changed

6 files changed

+64
-29
lines changed

lib/gemini.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import { GoogleGenerativeAI } from "@google/generative-ai";
1+
import { GoogleGenAI } from "@google/genai";
22
import { getConfigValue } from "@/lib/config";
33

4-
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || "");
4+
let _ai: GoogleGenAI | null = null;
5+
6+
/** Lazy-initialize the GoogleGenAI client (avoids crash at import time if GEMINI_API_KEY is missing). */
7+
function getAI(): GoogleGenAI {
8+
if (!_ai) {
9+
const apiKey = process.env.GEMINI_API_KEY || "";
10+
_ai = new GoogleGenAI({ apiKey });
11+
}
12+
return _ai;
13+
}
514

615
/**
716
* Generate text content using Gemini Flash.
@@ -14,13 +23,13 @@ export async function generateWithGemini(
1423
systemInstruction?: string,
1524
): Promise<string> {
1625
const geminiModel = await getConfigValue("pipeline_config", "geminiModel", "gemini-2.0-flash");
17-
const model = genAI.getGenerativeModel({
26+
const ai = getAI();
27+
const response = await ai.models.generateContent({
1828
model: geminiModel,
19-
...(systemInstruction && { systemInstruction }),
29+
contents: prompt,
30+
...(systemInstruction && { config: { systemInstruction } }),
2031
});
21-
const result = await model.generateContent(prompt);
22-
const response = result.response;
23-
return response.text();
32+
return response.text ?? "";
2433
}
2534

2635
/**

lib/sponsor/gemini-intent.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GoogleGenerativeAI } from '@google/generative-ai'
1+
import { GoogleGenAI } from '@google/genai'
22
import { getConfigValue } from '@/lib/config'
33

44
const SPONSORSHIP_TIERS = [
@@ -17,10 +17,6 @@ export interface SponsorIntent {
1717
urgency: 'low' | 'medium' | 'high'
1818
}
1919

20-
/**
21-
* Uses Gemini Flash to parse inbound sponsor emails/messages
22-
* and extract structured data for creating a sponsorLead.
23-
*/
2420
export async function extractSponsorIntent(message: string): Promise<SponsorIntent> {
2521
const apiKey = process.env.GEMINI_API_KEY
2622
if (!apiKey) {
@@ -35,8 +31,7 @@ export async function extractSponsorIntent(message: string): Promise<SponsorInte
3531
}
3632

3733
const geminiModel = await getConfigValue('pipeline_config', 'geminiModel', 'gemini-2.0-flash')
38-
const genAI = new GoogleGenerativeAI(apiKey)
39-
const model = genAI.getGenerativeModel({ model: geminiModel })
34+
const ai = new GoogleGenAI({ apiKey })
4035

4136
const prompt = `You are analyzing an inbound sponsorship inquiry for CodingCat.dev, a developer education platform with YouTube videos, podcasts, blog posts, and newsletters.
4237
@@ -61,9 +56,11 @@ Message to analyze:
6156
${message}`
6257

6358
try {
64-
const result = await model.generateContent(prompt)
65-
const response = result.response
66-
const text = response.text().trim()
59+
const response = await ai.models.generateContent({
60+
model: geminiModel,
61+
contents: prompt,
62+
})
63+
const text = (response.text ?? '').trim()
6764

6865
// Strip any markdown code fences if present
6966
const jsonStr = text.replace(/^```json?\n?/i, '').replace(/\n?```$/i, '').trim()

lib/sponsor/gemini-outreach.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GoogleGenerativeAI } from '@google/generative-ai'
1+
import { GoogleGenAI } from '@google/genai'
22
import { getConfigValue } from '@/lib/config'
33

44
export interface SponsorPoolEntry {
@@ -28,10 +28,6 @@ CodingCat.dev Sponsorship Tiers:
2828
Our audience: 50K+ developers interested in web development, JavaScript/TypeScript, React, Next.js, and modern dev tools.
2929
`.trim()
3030

31-
/**
32-
* Uses Gemini to generate a personalized cold outreach email
33-
* for a potential sponsor from the sponsor pool.
34-
*/
3531
export async function generateOutreachEmail(
3632
sponsor: SponsorPoolEntry,
3733
rateCard: string = DEFAULT_RATE_CARD
@@ -42,9 +38,8 @@ export async function generateOutreachEmail(
4238
return getTemplateEmail(sponsor, rateCard)
4339
}
4440

45-
const genAI = new GoogleGenerativeAI(apiKey)
41+
const ai = new GoogleGenAI({ apiKey })
4642
const geminiModel = await getConfigValue("pipeline_config", "geminiModel", "gemini-2.0-flash");
47-
const model = genAI.getGenerativeModel({ model: geminiModel })
4843

4944
const optOutUrl = sponsor.optOutToken
5045
? `${process.env.NEXT_PUBLIC_URL || 'https://codingcat.dev'}/api/sponsor/opt-out?token=${sponsor.optOutToken}`
@@ -74,9 +69,11 @@ Respond ONLY with valid JSON, no markdown formatting:
7469
{"subject": "...", "body": "..."}`
7570

7671
try {
77-
const result = await model.generateContent(prompt)
78-
const response = result.response
79-
const text = response.text().trim()
72+
const response = await ai.models.generateContent({
73+
model: geminiModel,
74+
contents: prompt,
75+
})
76+
const text = (response.text ?? '').trim()
8077

8178
const jsonStr = text.replace(/^```json?\n?/i, '').replace(/\n?```$/i, '').trim()
8279
const parsed = JSON.parse(jsonStr) as OutreachEmail

lib/types/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export interface PipelineConfig {
1111
youtubeUploadVisibility: string;
1212
youtubeChannelId: string;
1313
enableNotebookLmResearch: boolean;
14+
enableDeepResearch: boolean;
15+
deepResearchAgent: string;
16+
deepResearchPromptTemplate: string;
17+
infographicModel: string;
1418
qualityThreshold: number;
1519
stuckTimeoutMinutes: number;
1620
maxIdeasPerRun: number;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"dependencies": {
1414
"@codingcatdev/sanity-plugin-podcast-rss": "^1.0.0",
15-
"@google/generative-ai": "^0.24.1",
15+
"@google/genai": "^1.0.0",
1616
"@hookform/resolvers": "^5.2.2",
1717
"@marsidev/react-turnstile": "^1.4.2",
1818
"@portabletext/block-tools": "^5.0.5",

sanity/schemas/singletons/pipelineConfig.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default defineType({
3434
name: "youtubeChannelId",
3535
title: "YouTube Channel ID",
3636
type: "string",
37-
description: "Your YouTube channel ID \u2014 used for analytics and upload targeting",
37+
description: "Your YouTube channel ID used for analytics and upload targeting",
3838
initialValue: "",
3939
}),
4040
defineField({
@@ -44,6 +44,34 @@ export default defineType({
4444
description: "When enabled, the ingest cron creates a NotebookLM notebook for deep research before script generation. Requires NOTEBOOKLM_AUTH_JSON env var",
4545
initialValue: false,
4646
}),
47+
defineField({
48+
name: "enableDeepResearch",
49+
title: "Enable Deep Research",
50+
type: "boolean",
51+
description: "When enabled, the ingest cron uses Gemini Deep Research API for comprehensive topic research before script generation. Replaces NotebookLM research.",
52+
initialValue: false,
53+
}),
54+
defineField({
55+
name: "deepResearchAgent",
56+
title: "Deep Research Agent",
57+
type: "string",
58+
description: "The Gemini Deep Research agent model ID. This is the agent used for autonomous web research via the Interactions API",
59+
initialValue: "deep-research-pro-preview-12-2025",
60+
}),
61+
defineField({
62+
name: "deepResearchPromptTemplate",
63+
title: "Deep Research Prompt Template",
64+
type: "text",
65+
description: "Template for Deep Research queries. Use {topic} as placeholder for the trend topic. Sent to the Deep Research agent for autonomous web research",
66+
initialValue: "Research comprehensively: \"{topic}\"\n\nFocus areas:\n- What is it and why does it matter?\n- How does it work technically?\n- Key features and capabilities\n- Comparison with alternatives\n- Getting started guide\n- Common pitfalls and best practices\n\nTarget audience: Web developers learning new tech.\nTone: Educational, accessible, engaging.",
67+
}),
68+
defineField({
69+
name: "infographicModel",
70+
title: "Infographic Model",
71+
type: "string",
72+
description: "Model used for generating brand-consistent infographics from research data. Imagen 4 Fast ($0.02/image) supports seed-based reproducibility",
73+
initialValue: "imagen-4-fast",
74+
}),
4775
defineField({
4876
name: "qualityThreshold",
4977
title: "Quality Threshold",

0 commit comments

Comments
 (0)