./kaisetsu-app/src/lib/genai.ts
import { GoogleGenAI } from '@google/genai';
import { GEMINI_API_KEY, GCP_PROJECT_ID, VERTEX_AI_LOCATION } from './config';
const useApiKey = !!GEMINI_API_KEY;
if (!useApiKey && !GCP_PROJECT_ID) {
throw new Error('Neither GEMINI_API_KEY nor GCP_PROJECT_ID is set. Configure one of them.');
}
export const genai = useApiKey
? new GoogleGenAI({ apiKey: GEMINI_API_KEY })
: new GoogleGenAI({
vertexai: true,
project: GCP_PROJECT_ID!,
location: VERTEX_AI_LOCATION,
httpOptions: {
timeout: 600_000,
headersTimeout: 600_000,
bodyTimeout: 600_000,
} as Record<string, number>,
});
export interface VideoRef {
fileUri: string;
mimeType: string;
}
// Prepare a video for Gemini:
// - Vertex AI: accepts gs:// directly, pass through
// - Gemini API: must upload via Files API first
export async function prepareVideo(uri: string, mimeType: string): Promise<VideoRef> {
if (!useApiKey) {
if (!uri.startsWith('gs://')) {
throw new Error(`Vertex AI requires gs:// URI; got: ${uri}`);
}
return { fileUri: uri, mimeType };
}
// API key mode — upload local file
const path = uri.replace(/^file:\/\//, '');
if (!path.startsWith('/')) {
throw new Error(`Gemini API requires absolute local path or gs:// (not supported); got: ${uri}`);
}
console.log(`[genai] Uploading ${path} to Gemini Files API...`);
const uploaded = await genai.files.upload({ file: path, config: { mimeType } });
// Wait for ACTIVE state before use
let file = uploaded;
const name = file.name!;
while (file.state === 'PROCESSING') {
await new Promise((r) => setTimeout(r, 2000));
file = await genai.files.get({ name });
}
if (file.state !== 'ACTIVE') {
throw new Error(`Gemini file upload ended in state ${file.state}: ${name}`);
}
console.log(`[genai] Uploaded: ${file.uri}`);
return { fileUri: file.uri!, mimeType: file.mimeType! };
}