ai-site-pilot 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,12 +40,51 @@ The AI doesn't automatically know your site structure—you teach it via the sys
40
40
 
41
41
  ```bash
42
42
  npm install ai-site-pilot
43
+
44
+ # Choose your backend (pick ONE):
45
+ npm install @google/genai # Gemini (recommended, no AI SDK needed)
46
+ npm install ai @ai-sdk/google # Vercel AI SDK with Gemini
47
+ npm install ai @ai-sdk/openai # Vercel AI SDK with OpenAI
43
48
  ```
44
49
 
45
50
  ## Quick Start
46
51
 
47
52
  ### 1. Create the API Route
48
53
 
54
+ **Option A: Gemini Direct (Recommended)** - No Vercel AI SDK needed!
55
+
56
+ ```typescript
57
+ // app/api/chat/route.ts
58
+ import { createGeminiHandler } from 'ai-site-pilot/api';
59
+ import { defineTool } from 'ai-site-pilot/tools';
60
+
61
+ const navigateTool = defineTool({
62
+ name: 'navigate',
63
+ description: 'Navigate to a section of the page',
64
+ parameters: {
65
+ type: 'object',
66
+ properties: {
67
+ section: {
68
+ type: 'string',
69
+ description: 'Section to navigate to',
70
+ enum: ['home', 'products', 'about', 'contact'],
71
+ },
72
+ },
73
+ required: ['section'],
74
+ },
75
+ });
76
+
77
+ export const POST = createGeminiHandler({
78
+ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
79
+ model: 'gemini-2.0-flash',
80
+ systemPrompt: `You are a helpful assistant for our website.
81
+ You can navigate users to different sections using the navigate tool.`,
82
+ tools: [navigateTool],
83
+ });
84
+ ```
85
+
86
+ **Option B: Vercel AI SDK** - Works with any AI SDK provider
87
+
49
88
  ```typescript
50
89
  // app/api/chat/route.ts
51
90
  import { createChatHandler } from 'ai-site-pilot/api';
@@ -152,9 +191,25 @@ interface SitePilotFeatures {
152
191
  }
153
192
  ```
154
193
 
194
+ ### `createGeminiHandler()`
195
+
196
+ Factory for creating Next.js API route handlers using Gemini directly (no AI SDK needed).
197
+
198
+ ```typescript
199
+ import { createGeminiHandler } from 'ai-site-pilot/api';
200
+
201
+ export const POST = createGeminiHandler({
202
+ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY, // Optional, uses env var by default
203
+ model: 'gemini-2.0-flash', // Default: gemini-2.0-flash
204
+ systemPrompt: 'You are a helpful assistant...',
205
+ tools: [myTool1, myTool2],
206
+ temperature: 0.7,
207
+ });
208
+ ```
209
+
155
210
  ### `createChatHandler()`
156
211
 
157
- Factory for creating Next.js API route handlers.
212
+ Factory for creating Next.js API route handlers using Vercel AI SDK.
158
213
 
159
214
  ```typescript
160
215
  import { createChatHandler } from 'ai-site-pilot/api';
@@ -318,11 +373,85 @@ const tools = [
318
373
  ];
319
374
  ```
320
375
 
376
+ ## Custom API Implementation
377
+
378
+ If you need to implement your own API route (e.g., using a different AI provider), the widget expects Server-Sent Events (SSE) in this format:
379
+
380
+ ### SSE Streaming Format
381
+
382
+ ```
383
+ data: {"type":"text","content":"Hello, "}
384
+
385
+ data: {"type":"text","content":"how can I help?"}
386
+
387
+ data: {"type":"tool","name":"navigate","args":{"section":"products"}}
388
+
389
+ data: {"type":"done"}
390
+ ```
391
+
392
+ #### Event Types
393
+
394
+ | Type | Description | Example |
395
+ |------|-------------|---------|
396
+ | `text` | Streaming text chunk | `{"type":"text","content":"Hello"}` |
397
+ | `tool` | Tool/function call | `{"type":"tool","name":"navigate","args":{"section":"home"}}` |
398
+ | `done` | Stream complete | `{"type":"done"}` |
399
+ | `error` | Error occurred | `{"type":"error","message":"Something went wrong"}` |
400
+
401
+ ### SSE Utilities
402
+
403
+ Use the built-in SSE helpers for custom implementations:
404
+
405
+ ```typescript
406
+ import { createSSEEncoder, getSSEHeaders } from 'ai-site-pilot/api';
407
+
408
+ export async function POST(req: Request) {
409
+ const sse = createSSEEncoder();
410
+
411
+ const stream = new ReadableStream({
412
+ async start(controller) {
413
+ // Stream text
414
+ controller.enqueue(sse.encodeText('Hello!'));
415
+
416
+ // Send tool call
417
+ controller.enqueue(sse.encodeTool('navigate', { section: 'products' }));
418
+
419
+ // Done
420
+ controller.enqueue(sse.encodeDone());
421
+ controller.close();
422
+ },
423
+ });
424
+
425
+ return new Response(stream, { headers: getSSEHeaders() });
426
+ }
427
+ ```
428
+
429
+ ## Handling Tool-Only Responses
430
+
431
+ When the AI calls tools without providing text, you can customize the fallback message:
432
+
433
+ ```typescript
434
+ import { SitePilot, createFallbackMessageGenerator } from 'ai-site-pilot';
435
+
436
+ const generateFallback = createFallbackMessageGenerator({
437
+ navigate: (args) => `Scrolled to **${args.section}** section.`,
438
+ filter_products: (args) => `Showing **${args.category}** products.`,
439
+ search: (args) => `Found results for "${args.query}".`,
440
+ });
441
+
442
+ <SitePilot
443
+ apiEndpoint="/api/chat"
444
+ generateFallbackMessage={generateFallback}
445
+ />
446
+ ```
447
+
321
448
  ## Requirements
322
449
 
323
450
  - React 18+ or React 19
324
451
  - Next.js 13+ (for API routes)
325
- - A Vercel AI SDK compatible model
452
+ - One of:
453
+ - `@google/genai` (Gemini direct - recommended)
454
+ - `ai` + provider package (Vercel AI SDK)
326
455
 
327
456
  ## License
328
457
 
@@ -1,6 +1,6 @@
1
1
  import { LanguageModel } from 'ai';
2
2
  import { T as ToolDefinition } from '../types--7jDyUM6.mjs';
3
- import { S as StreamEvent } from '../types-2ZpUTVRN.mjs';
3
+ import { S as StreamEvent } from '../types-K00dDlBC.mjs';
4
4
 
5
5
  /**
6
6
  * Factory for creating Next.js API route handlers
@@ -52,6 +52,44 @@ interface ChatHandlerConfig {
52
52
  */
53
53
  declare function createChatHandler(config: ChatHandlerConfig): (req: Request) => Promise<Response>;
54
54
 
55
+ /**
56
+ * Gemini-specific handler using @google/genai directly
57
+ * No Vercel AI SDK required!
58
+ */
59
+
60
+ interface GeminiHandlerConfig {
61
+ /** Google AI API key (or set GOOGLE_GENERATIVE_AI_API_KEY env var) */
62
+ apiKey?: string;
63
+ /** Gemini model to use (default: gemini-2.0-flash) */
64
+ model?: string;
65
+ /** System prompt for the AI */
66
+ systemPrompt: string;
67
+ /** Tool definitions for the AI */
68
+ tools?: ToolDefinition[];
69
+ /** Temperature for response generation (0-1) */
70
+ temperature?: number;
71
+ }
72
+ /**
73
+ * Create a Next.js API route handler for Gemini
74
+ *
75
+ * Uses @google/genai directly - no Vercel AI SDK required!
76
+ * Works with Gemini 2.0+ models.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * // app/api/chat/route.ts
81
+ * import { createGeminiHandler } from 'ai-site-pilot/api';
82
+ *
83
+ * export const POST = createGeminiHandler({
84
+ * apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
85
+ * model: 'gemini-2.0-flash',
86
+ * systemPrompt: 'You are a helpful assistant...',
87
+ * tools: myTools,
88
+ * });
89
+ * ```
90
+ */
91
+ declare function createGeminiHandler(config: GeminiHandlerConfig): (req: Request) => Promise<Response>;
92
+
55
93
  /**
56
94
  * SSE streaming utilities
57
95
  */
@@ -75,4 +113,4 @@ declare function getSSEHeaders(): HeadersInit;
75
113
  */
76
114
  declare function parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>): AsyncGenerator<StreamEvent>;
77
115
 
78
- export { type ChatHandlerConfig, createChatHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
116
+ export { type ChatHandlerConfig, type GeminiHandlerConfig, createChatHandler, createGeminiHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
@@ -1,6 +1,6 @@
1
1
  import { LanguageModel } from 'ai';
2
2
  import { T as ToolDefinition } from '../types--7jDyUM6.js';
3
- import { S as StreamEvent } from '../types-2ZpUTVRN.js';
3
+ import { S as StreamEvent } from '../types-K00dDlBC.js';
4
4
 
5
5
  /**
6
6
  * Factory for creating Next.js API route handlers
@@ -52,6 +52,44 @@ interface ChatHandlerConfig {
52
52
  */
53
53
  declare function createChatHandler(config: ChatHandlerConfig): (req: Request) => Promise<Response>;
54
54
 
55
+ /**
56
+ * Gemini-specific handler using @google/genai directly
57
+ * No Vercel AI SDK required!
58
+ */
59
+
60
+ interface GeminiHandlerConfig {
61
+ /** Google AI API key (or set GOOGLE_GENERATIVE_AI_API_KEY env var) */
62
+ apiKey?: string;
63
+ /** Gemini model to use (default: gemini-2.0-flash) */
64
+ model?: string;
65
+ /** System prompt for the AI */
66
+ systemPrompt: string;
67
+ /** Tool definitions for the AI */
68
+ tools?: ToolDefinition[];
69
+ /** Temperature for response generation (0-1) */
70
+ temperature?: number;
71
+ }
72
+ /**
73
+ * Create a Next.js API route handler for Gemini
74
+ *
75
+ * Uses @google/genai directly - no Vercel AI SDK required!
76
+ * Works with Gemini 2.0+ models.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * // app/api/chat/route.ts
81
+ * import { createGeminiHandler } from 'ai-site-pilot/api';
82
+ *
83
+ * export const POST = createGeminiHandler({
84
+ * apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
85
+ * model: 'gemini-2.0-flash',
86
+ * systemPrompt: 'You are a helpful assistant...',
87
+ * tools: myTools,
88
+ * });
89
+ * ```
90
+ */
91
+ declare function createGeminiHandler(config: GeminiHandlerConfig): (req: Request) => Promise<Response>;
92
+
55
93
  /**
56
94
  * SSE streaming utilities
57
95
  */
@@ -75,4 +113,4 @@ declare function getSSEHeaders(): HeadersInit;
75
113
  */
76
114
  declare function parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>): AsyncGenerator<StreamEvent>;
77
115
 
78
- export { type ChatHandlerConfig, createChatHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
116
+ export { type ChatHandlerConfig, type GeminiHandlerConfig, createChatHandler, createGeminiHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
package/dist/api/index.js CHANGED
@@ -134,7 +134,105 @@ function createChatHandler(config) {
134
134
  };
135
135
  }
136
136
 
137
+ // src/api/createGeminiHandler.ts
138
+ function convertToolsToGemini(tools) {
139
+ return tools.map((tool) => ({
140
+ name: tool.name,
141
+ description: tool.description,
142
+ parameters: {
143
+ type: "object",
144
+ properties: Object.fromEntries(
145
+ Object.entries(tool.parameters.properties).map(([key, value]) => [
146
+ key,
147
+ {
148
+ type: value.type,
149
+ description: value.description,
150
+ enum: value.enum
151
+ }
152
+ ])
153
+ ),
154
+ required: tool.parameters.required
155
+ }
156
+ }));
157
+ }
158
+ function createGeminiHandler(config) {
159
+ const {
160
+ apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY,
161
+ model = "gemini-2.0-flash",
162
+ systemPrompt,
163
+ tools = [],
164
+ temperature = 0.7
165
+ } = config;
166
+ return async function POST(req) {
167
+ if (!apiKey) {
168
+ return new Response(
169
+ JSON.stringify({ error: "Google AI API key not configured" }),
170
+ { status: 500, headers: { "Content-Type": "application/json" } }
171
+ );
172
+ }
173
+ try {
174
+ const { GoogleGenAI } = await import('@google/genai');
175
+ const ai = new GoogleGenAI({ apiKey });
176
+ const body = await req.json();
177
+ const { messages } = body;
178
+ const geminiMessages = messages.map((m) => ({
179
+ role: m.role === "assistant" ? "model" : "user",
180
+ parts: [{ text: m.content }]
181
+ }));
182
+ const sse = createSSEEncoder();
183
+ const stream = new ReadableStream({
184
+ async start(controller) {
185
+ try {
186
+ const response = await ai.models.generateContentStream({
187
+ model,
188
+ contents: geminiMessages,
189
+ config: {
190
+ systemInstruction: systemPrompt,
191
+ temperature,
192
+ tools: tools.length > 0 ? [{
193
+ functionDeclarations: convertToolsToGemini(tools)
194
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
+ }] : void 0
196
+ }
197
+ });
198
+ for await (const chunk of response) {
199
+ const parts = chunk.candidates?.[0]?.content?.parts || [];
200
+ for (const part of parts) {
201
+ if ("text" in part && part.text) {
202
+ controller.enqueue(sse.encodeText(part.text));
203
+ }
204
+ if ("functionCall" in part && part.functionCall) {
205
+ controller.enqueue(
206
+ sse.encodeTool(
207
+ part.functionCall.name,
208
+ part.functionCall.args
209
+ )
210
+ );
211
+ }
212
+ }
213
+ }
214
+ controller.enqueue(sse.encodeDone());
215
+ controller.close();
216
+ } catch (error) {
217
+ console.error("Gemini streaming error:", error);
218
+ controller.enqueue(sse.encodeError("An error occurred during streaming"));
219
+ controller.close();
220
+ }
221
+ }
222
+ });
223
+ return new Response(stream, { headers: getSSEHeaders() });
224
+ } catch (error) {
225
+ console.error("Gemini handler error:", error);
226
+ return new Response(
227
+ JSON.stringify({ error: "Internal server error" }),
228
+ { status: 500, headers: { "Content-Type": "application/json" } }
229
+ );
230
+ }
231
+ };
232
+ }
233
+
137
234
  exports.createChatHandler = createChatHandler;
235
+ exports.createGeminiHandler = createGeminiHandler;
138
236
  exports.createSSEEncoder = createSSEEncoder;
139
237
  exports.getSSEHeaders = getSSEHeaders;
140
238
  exports.parseSSEStream = parseSSEStream;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts"],"names":["streamText"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SSE streaming utilities\n */\n\nimport type { StreamEvent } from '../types';\n\n/**\n * Create an SSE encoder for streaming responses\n */\nexport function createSSEEncoder() {\n const encoder = new TextEncoder();\n\n return {\n encode(event: StreamEvent): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify(event)}\\n\\n`);\n },\n\n encodeText(content: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'text', content })}\\n\\n`);\n },\n\n encodeTool(name: string, args: Record<string, unknown>): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'tool', name, args })}\\n\\n`);\n },\n\n encodeDone(): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'done' })}\\n\\n`);\n },\n\n encodeError(message: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'error', message })}\\n\\n`);\n },\n };\n}\n\n/**\n * Create SSE response headers\n */\nexport function getSSEHeaders(): HeadersInit {\n return {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n };\n}\n\n/**\n * Parse SSE events from a ReadableStream\n */\nexport async function* parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>\n): AsyncGenerator<StreamEvent> {\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6)) as StreamEvent;\n yield data;\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n}\n","/**\n * Factory for creating Next.js API route handlers\n */\n\nimport { streamText, type CoreMessage, type LanguageModel } from 'ai';\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface ChatHandlerConfig {\n /** The AI model to use (from Vercel AI SDK) */\n model: LanguageModel;\n /** System prompt for the AI */\n systemPrompt: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n /** Maximum tokens in response */\n maxTokens?: number;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Vercel AI SDK format\n */\nfunction convertToolsToAISDK(tools: ToolDefinition[]) {\n const result: Record<string, { description: string; parameters: unknown }> = {};\n\n for (const tool of tools) {\n result[tool.name] = {\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n };\n }\n\n return result;\n}\n\n/**\n * Create a Next.js API route handler for chat\n *\n * Works with any Vercel AI SDK compatible model including:\n * - Google Gemini (@ai-sdk/google)\n * - OpenAI (@ai-sdk/openai)\n * - Anthropic (@ai-sdk/anthropic)\n * - And more...\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createChatHandler } from 'ai-site-pilot/api';\n * import { google } from '@ai-sdk/google';\n *\n * export const POST = createChatHandler({\n * model: google('gemini-2.0-flash'),\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n *\n * @example Using OpenAI\n * ```ts\n * import { openai } from '@ai-sdk/openai';\n *\n * export const POST = createChatHandler({\n * model: openai('gpt-4o'),\n * systemPrompt: 'You are a helpful assistant...',\n * });\n * ```\n */\nexport function createChatHandler(config: ChatHandlerConfig) {\n const { model, systemPrompt, tools = [], temperature = 0.7, maxTokens } = config;\n\n return async function POST(req: Request): Promise<Response> {\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to CoreMessage format\n const coreMessages: CoreMessage[] = messages.map((m) => ({\n role: m.role,\n content: m.content,\n }));\n\n // Create the SSE encoder\n const sse = createSSEEncoder();\n\n // Create a readable stream for SSE\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const result = streamText({\n model,\n system: systemPrompt,\n messages: coreMessages,\n temperature,\n maxTokens,\n tools: tools.length > 0 ? convertToolsToAISDK(tools) : undefined,\n });\n\n // Stream text chunks\n for await (const chunk of (await result).textStream) {\n if (chunk) {\n controller.enqueue(sse.encodeText(chunk));\n }\n }\n\n // Get tool calls from the result\n const finalResult = await result;\n const toolCalls = (await finalResult.toolCalls) || [];\n\n for (const toolCall of toolCalls) {\n controller.enqueue(\n sse.encodeTool(toolCall.toolName, toolCall.args as Record<string, unknown>)\n );\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Streaming error:', error);\n controller.enqueue(sse.encodeError('An error occurred during streaming'));\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: getSSEHeaders(),\n });\n } catch (error) {\n console.error('Chat API error:', error);\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts","../../src/api/createGeminiHandler.ts"],"names":["streamText"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SSE streaming utilities\n */\n\nimport type { StreamEvent } from '../types';\n\n/**\n * Create an SSE encoder for streaming responses\n */\nexport function createSSEEncoder() {\n const encoder = new TextEncoder();\n\n return {\n encode(event: StreamEvent): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify(event)}\\n\\n`);\n },\n\n encodeText(content: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'text', content })}\\n\\n`);\n },\n\n encodeTool(name: string, args: Record<string, unknown>): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'tool', name, args })}\\n\\n`);\n },\n\n encodeDone(): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'done' })}\\n\\n`);\n },\n\n encodeError(message: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'error', message })}\\n\\n`);\n },\n };\n}\n\n/**\n * Create SSE response headers\n */\nexport function getSSEHeaders(): HeadersInit {\n return {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n };\n}\n\n/**\n * Parse SSE events from a ReadableStream\n */\nexport async function* parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>\n): AsyncGenerator<StreamEvent> {\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6)) as StreamEvent;\n yield data;\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n}\n","/**\n * Factory for creating Next.js API route handlers\n */\n\nimport { streamText, type CoreMessage, type LanguageModel } from 'ai';\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface ChatHandlerConfig {\n /** The AI model to use (from Vercel AI SDK) */\n model: LanguageModel;\n /** System prompt for the AI */\n systemPrompt: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n /** Maximum tokens in response */\n maxTokens?: number;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Vercel AI SDK format\n */\nfunction convertToolsToAISDK(tools: ToolDefinition[]) {\n const result: Record<string, { description: string; parameters: unknown }> = {};\n\n for (const tool of tools) {\n result[tool.name] = {\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n };\n }\n\n return result;\n}\n\n/**\n * Create a Next.js API route handler for chat\n *\n * Works with any Vercel AI SDK compatible model including:\n * - Google Gemini (@ai-sdk/google)\n * - OpenAI (@ai-sdk/openai)\n * - Anthropic (@ai-sdk/anthropic)\n * - And more...\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createChatHandler } from 'ai-site-pilot/api';\n * import { google } from '@ai-sdk/google';\n *\n * export const POST = createChatHandler({\n * model: google('gemini-2.0-flash'),\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n *\n * @example Using OpenAI\n * ```ts\n * import { openai } from '@ai-sdk/openai';\n *\n * export const POST = createChatHandler({\n * model: openai('gpt-4o'),\n * systemPrompt: 'You are a helpful assistant...',\n * });\n * ```\n */\nexport function createChatHandler(config: ChatHandlerConfig) {\n const { model, systemPrompt, tools = [], temperature = 0.7, maxTokens } = config;\n\n return async function POST(req: Request): Promise<Response> {\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to CoreMessage format\n const coreMessages: CoreMessage[] = messages.map((m) => ({\n role: m.role,\n content: m.content,\n }));\n\n // Create the SSE encoder\n const sse = createSSEEncoder();\n\n // Create a readable stream for SSE\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const result = streamText({\n model,\n system: systemPrompt,\n messages: coreMessages,\n temperature,\n maxTokens,\n tools: tools.length > 0 ? convertToolsToAISDK(tools) : undefined,\n });\n\n // Stream text chunks\n for await (const chunk of (await result).textStream) {\n if (chunk) {\n controller.enqueue(sse.encodeText(chunk));\n }\n }\n\n // Get tool calls from the result\n const finalResult = await result;\n const toolCalls = (await finalResult.toolCalls) || [];\n\n for (const toolCall of toolCalls) {\n controller.enqueue(\n sse.encodeTool(toolCall.toolName, toolCall.args as Record<string, unknown>)\n );\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Streaming error:', error);\n controller.enqueue(sse.encodeError('An error occurred during streaming'));\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: getSSEHeaders(),\n });\n } catch (error) {\n console.error('Chat API error:', error);\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n","/**\n * Gemini-specific handler using @google/genai directly\n * No Vercel AI SDK required!\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface GeminiHandlerConfig {\n /** Google AI API key (or set GOOGLE_GENERATIVE_AI_API_KEY env var) */\n apiKey?: string;\n /** Gemini model to use (default: gemini-2.0-flash) */\n model?: string;\n /** System prompt for the AI */\n systemPrompt: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Gemini function declaration format\n * Uses 'as unknown' to handle SDK type differences across versions\n */\nfunction convertToolsToGemini(tools: ToolDefinition[]): unknown[] {\n return tools.map(tool => ({\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(tool.parameters.properties).map(([key, value]) => [\n key,\n {\n type: value.type,\n description: value.description,\n enum: value.enum,\n },\n ])\n ),\n required: tool.parameters.required,\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler for Gemini\n *\n * Uses @google/genai directly - no Vercel AI SDK required!\n * Works with Gemini 2.0+ models.\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createGeminiHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createGeminiHandler({\n * apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n * model: 'gemini-2.0-flash',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createGeminiHandler(config: GeminiHandlerConfig) {\n const {\n apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n model = 'gemini-2.0-flash',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'Google AI API key not configured' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n // Dynamic import to avoid bundling issues\n const { GoogleGenAI } = await import('@google/genai');\n const ai = new GoogleGenAI({ apiKey });\n\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to Gemini format\n const geminiMessages = messages.map((m) => ({\n role: m.role === 'assistant' ? 'model' as const : 'user' as const,\n parts: [{ text: m.content }],\n }));\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await ai.models.generateContentStream({\n model,\n contents: geminiMessages,\n config: {\n systemInstruction: systemPrompt,\n temperature,\n tools: tools.length > 0 ? [{\n functionDeclarations: convertToolsToGemini(tools),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n }] as any : undefined,\n },\n });\n\n for await (const chunk of response) {\n const parts = chunk.candidates?.[0]?.content?.parts || [];\n\n for (const part of parts) {\n // Handle text chunks\n if ('text' in part && part.text) {\n controller.enqueue(sse.encodeText(part.text));\n }\n\n // Handle function calls\n if ('functionCall' in part && part.functionCall) {\n controller.enqueue(\n sse.encodeTool(\n part.functionCall.name as string,\n part.functionCall.args as Record<string, unknown>\n )\n );\n }\n }\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Gemini streaming error:', error);\n controller.enqueue(sse.encodeError('An error occurred during streaming'));\n controller.close();\n }\n },\n });\n\n return new Response(stream, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('Gemini handler error:', error);\n return new Response(\n JSON.stringify({ error: 'Internal server error' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n };\n}\n"]}
@@ -132,6 +132,103 @@ function createChatHandler(config) {
132
132
  };
133
133
  }
134
134
 
135
- export { createChatHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
135
+ // src/api/createGeminiHandler.ts
136
+ function convertToolsToGemini(tools) {
137
+ return tools.map((tool) => ({
138
+ name: tool.name,
139
+ description: tool.description,
140
+ parameters: {
141
+ type: "object",
142
+ properties: Object.fromEntries(
143
+ Object.entries(tool.parameters.properties).map(([key, value]) => [
144
+ key,
145
+ {
146
+ type: value.type,
147
+ description: value.description,
148
+ enum: value.enum
149
+ }
150
+ ])
151
+ ),
152
+ required: tool.parameters.required
153
+ }
154
+ }));
155
+ }
156
+ function createGeminiHandler(config) {
157
+ const {
158
+ apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY,
159
+ model = "gemini-2.0-flash",
160
+ systemPrompt,
161
+ tools = [],
162
+ temperature = 0.7
163
+ } = config;
164
+ return async function POST(req) {
165
+ if (!apiKey) {
166
+ return new Response(
167
+ JSON.stringify({ error: "Google AI API key not configured" }),
168
+ { status: 500, headers: { "Content-Type": "application/json" } }
169
+ );
170
+ }
171
+ try {
172
+ const { GoogleGenAI } = await import('@google/genai');
173
+ const ai = new GoogleGenAI({ apiKey });
174
+ const body = await req.json();
175
+ const { messages } = body;
176
+ const geminiMessages = messages.map((m) => ({
177
+ role: m.role === "assistant" ? "model" : "user",
178
+ parts: [{ text: m.content }]
179
+ }));
180
+ const sse = createSSEEncoder();
181
+ const stream = new ReadableStream({
182
+ async start(controller) {
183
+ try {
184
+ const response = await ai.models.generateContentStream({
185
+ model,
186
+ contents: geminiMessages,
187
+ config: {
188
+ systemInstruction: systemPrompt,
189
+ temperature,
190
+ tools: tools.length > 0 ? [{
191
+ functionDeclarations: convertToolsToGemini(tools)
192
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
193
+ }] : void 0
194
+ }
195
+ });
196
+ for await (const chunk of response) {
197
+ const parts = chunk.candidates?.[0]?.content?.parts || [];
198
+ for (const part of parts) {
199
+ if ("text" in part && part.text) {
200
+ controller.enqueue(sse.encodeText(part.text));
201
+ }
202
+ if ("functionCall" in part && part.functionCall) {
203
+ controller.enqueue(
204
+ sse.encodeTool(
205
+ part.functionCall.name,
206
+ part.functionCall.args
207
+ )
208
+ );
209
+ }
210
+ }
211
+ }
212
+ controller.enqueue(sse.encodeDone());
213
+ controller.close();
214
+ } catch (error) {
215
+ console.error("Gemini streaming error:", error);
216
+ controller.enqueue(sse.encodeError("An error occurred during streaming"));
217
+ controller.close();
218
+ }
219
+ }
220
+ });
221
+ return new Response(stream, { headers: getSSEHeaders() });
222
+ } catch (error) {
223
+ console.error("Gemini handler error:", error);
224
+ return new Response(
225
+ JSON.stringify({ error: "Internal server error" }),
226
+ { status: 500, headers: { "Content-Type": "application/json" } }
227
+ );
228
+ }
229
+ };
230
+ }
231
+
232
+ export { createChatHandler, createGeminiHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
136
233
  //# sourceMappingURL=index.mjs.map
137
234
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * SSE streaming utilities\n */\n\nimport type { StreamEvent } from '../types';\n\n/**\n * Create an SSE encoder for streaming responses\n */\nexport function createSSEEncoder() {\n const encoder = new TextEncoder();\n\n return {\n encode(event: StreamEvent): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify(event)}\\n\\n`);\n },\n\n encodeText(content: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'text', content })}\\n\\n`);\n },\n\n encodeTool(name: string, args: Record<string, unknown>): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'tool', name, args })}\\n\\n`);\n },\n\n encodeDone(): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'done' })}\\n\\n`);\n },\n\n encodeError(message: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'error', message })}\\n\\n`);\n },\n };\n}\n\n/**\n * Create SSE response headers\n */\nexport function getSSEHeaders(): HeadersInit {\n return {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n };\n}\n\n/**\n * Parse SSE events from a ReadableStream\n */\nexport async function* parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>\n): AsyncGenerator<StreamEvent> {\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6)) as StreamEvent;\n yield data;\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n}\n","/**\n * Factory for creating Next.js API route handlers\n */\n\nimport { streamText, type CoreMessage, type LanguageModel } from 'ai';\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface ChatHandlerConfig {\n /** The AI model to use (from Vercel AI SDK) */\n model: LanguageModel;\n /** System prompt for the AI */\n systemPrompt: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n /** Maximum tokens in response */\n maxTokens?: number;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Vercel AI SDK format\n */\nfunction convertToolsToAISDK(tools: ToolDefinition[]) {\n const result: Record<string, { description: string; parameters: unknown }> = {};\n\n for (const tool of tools) {\n result[tool.name] = {\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n };\n }\n\n return result;\n}\n\n/**\n * Create a Next.js API route handler for chat\n *\n * Works with any Vercel AI SDK compatible model including:\n * - Google Gemini (@ai-sdk/google)\n * - OpenAI (@ai-sdk/openai)\n * - Anthropic (@ai-sdk/anthropic)\n * - And more...\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createChatHandler } from 'ai-site-pilot/api';\n * import { google } from '@ai-sdk/google';\n *\n * export const POST = createChatHandler({\n * model: google('gemini-2.0-flash'),\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n *\n * @example Using OpenAI\n * ```ts\n * import { openai } from '@ai-sdk/openai';\n *\n * export const POST = createChatHandler({\n * model: openai('gpt-4o'),\n * systemPrompt: 'You are a helpful assistant...',\n * });\n * ```\n */\nexport function createChatHandler(config: ChatHandlerConfig) {\n const { model, systemPrompt, tools = [], temperature = 0.7, maxTokens } = config;\n\n return async function POST(req: Request): Promise<Response> {\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to CoreMessage format\n const coreMessages: CoreMessage[] = messages.map((m) => ({\n role: m.role,\n content: m.content,\n }));\n\n // Create the SSE encoder\n const sse = createSSEEncoder();\n\n // Create a readable stream for SSE\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const result = streamText({\n model,\n system: systemPrompt,\n messages: coreMessages,\n temperature,\n maxTokens,\n tools: tools.length > 0 ? convertToolsToAISDK(tools) : undefined,\n });\n\n // Stream text chunks\n for await (const chunk of (await result).textStream) {\n if (chunk) {\n controller.enqueue(sse.encodeText(chunk));\n }\n }\n\n // Get tool calls from the result\n const finalResult = await result;\n const toolCalls = (await finalResult.toolCalls) || [];\n\n for (const toolCall of toolCalls) {\n controller.enqueue(\n sse.encodeTool(toolCall.toolName, toolCall.args as Record<string, unknown>)\n );\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Streaming error:', error);\n controller.enqueue(sse.encodeError('An error occurred during streaming'));\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: getSSEHeaders(),\n });\n } catch (error) {\n console.error('Chat API error:', error);\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts","../../src/api/createGeminiHandler.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * SSE streaming utilities\n */\n\nimport type { StreamEvent } from '../types';\n\n/**\n * Create an SSE encoder for streaming responses\n */\nexport function createSSEEncoder() {\n const encoder = new TextEncoder();\n\n return {\n encode(event: StreamEvent): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify(event)}\\n\\n`);\n },\n\n encodeText(content: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'text', content })}\\n\\n`);\n },\n\n encodeTool(name: string, args: Record<string, unknown>): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'tool', name, args })}\\n\\n`);\n },\n\n encodeDone(): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'done' })}\\n\\n`);\n },\n\n encodeError(message: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'error', message })}\\n\\n`);\n },\n };\n}\n\n/**\n * Create SSE response headers\n */\nexport function getSSEHeaders(): HeadersInit {\n return {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n };\n}\n\n/**\n * Parse SSE events from a ReadableStream\n */\nexport async function* parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>\n): AsyncGenerator<StreamEvent> {\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6)) as StreamEvent;\n yield data;\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n}\n","/**\n * Factory for creating Next.js API route handlers\n */\n\nimport { streamText, type CoreMessage, type LanguageModel } from 'ai';\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface ChatHandlerConfig {\n /** The AI model to use (from Vercel AI SDK) */\n model: LanguageModel;\n /** System prompt for the AI */\n systemPrompt: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n /** Maximum tokens in response */\n maxTokens?: number;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Vercel AI SDK format\n */\nfunction convertToolsToAISDK(tools: ToolDefinition[]) {\n const result: Record<string, { description: string; parameters: unknown }> = {};\n\n for (const tool of tools) {\n result[tool.name] = {\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n };\n }\n\n return result;\n}\n\n/**\n * Create a Next.js API route handler for chat\n *\n * Works with any Vercel AI SDK compatible model including:\n * - Google Gemini (@ai-sdk/google)\n * - OpenAI (@ai-sdk/openai)\n * - Anthropic (@ai-sdk/anthropic)\n * - And more...\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createChatHandler } from 'ai-site-pilot/api';\n * import { google } from '@ai-sdk/google';\n *\n * export const POST = createChatHandler({\n * model: google('gemini-2.0-flash'),\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n *\n * @example Using OpenAI\n * ```ts\n * import { openai } from '@ai-sdk/openai';\n *\n * export const POST = createChatHandler({\n * model: openai('gpt-4o'),\n * systemPrompt: 'You are a helpful assistant...',\n * });\n * ```\n */\nexport function createChatHandler(config: ChatHandlerConfig) {\n const { model, systemPrompt, tools = [], temperature = 0.7, maxTokens } = config;\n\n return async function POST(req: Request): Promise<Response> {\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to CoreMessage format\n const coreMessages: CoreMessage[] = messages.map((m) => ({\n role: m.role,\n content: m.content,\n }));\n\n // Create the SSE encoder\n const sse = createSSEEncoder();\n\n // Create a readable stream for SSE\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const result = streamText({\n model,\n system: systemPrompt,\n messages: coreMessages,\n temperature,\n maxTokens,\n tools: tools.length > 0 ? convertToolsToAISDK(tools) : undefined,\n });\n\n // Stream text chunks\n for await (const chunk of (await result).textStream) {\n if (chunk) {\n controller.enqueue(sse.encodeText(chunk));\n }\n }\n\n // Get tool calls from the result\n const finalResult = await result;\n const toolCalls = (await finalResult.toolCalls) || [];\n\n for (const toolCall of toolCalls) {\n controller.enqueue(\n sse.encodeTool(toolCall.toolName, toolCall.args as Record<string, unknown>)\n );\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Streaming error:', error);\n controller.enqueue(sse.encodeError('An error occurred during streaming'));\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: getSSEHeaders(),\n });\n } catch (error) {\n console.error('Chat API error:', error);\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n","/**\n * Gemini-specific handler using @google/genai directly\n * No Vercel AI SDK required!\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface GeminiHandlerConfig {\n /** Google AI API key (or set GOOGLE_GENERATIVE_AI_API_KEY env var) */\n apiKey?: string;\n /** Gemini model to use (default: gemini-2.0-flash) */\n model?: string;\n /** System prompt for the AI */\n systemPrompt: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Gemini function declaration format\n * Uses 'as unknown' to handle SDK type differences across versions\n */\nfunction convertToolsToGemini(tools: ToolDefinition[]): unknown[] {\n return tools.map(tool => ({\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(tool.parameters.properties).map(([key, value]) => [\n key,\n {\n type: value.type,\n description: value.description,\n enum: value.enum,\n },\n ])\n ),\n required: tool.parameters.required,\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler for Gemini\n *\n * Uses @google/genai directly - no Vercel AI SDK required!\n * Works with Gemini 2.0+ models.\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createGeminiHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createGeminiHandler({\n * apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n * model: 'gemini-2.0-flash',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createGeminiHandler(config: GeminiHandlerConfig) {\n const {\n apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n model = 'gemini-2.0-flash',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'Google AI API key not configured' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n // Dynamic import to avoid bundling issues\n const { GoogleGenAI } = await import('@google/genai');\n const ai = new GoogleGenAI({ apiKey });\n\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to Gemini format\n const geminiMessages = messages.map((m) => ({\n role: m.role === 'assistant' ? 'model' as const : 'user' as const,\n parts: [{ text: m.content }],\n }));\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await ai.models.generateContentStream({\n model,\n contents: geminiMessages,\n config: {\n systemInstruction: systemPrompt,\n temperature,\n tools: tools.length > 0 ? [{\n functionDeclarations: convertToolsToGemini(tools),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n }] as any : undefined,\n },\n });\n\n for await (const chunk of response) {\n const parts = chunk.candidates?.[0]?.content?.parts || [];\n\n for (const part of parts) {\n // Handle text chunks\n if ('text' in part && part.text) {\n controller.enqueue(sse.encodeText(part.text));\n }\n\n // Handle function calls\n if ('functionCall' in part && part.functionCall) {\n controller.enqueue(\n sse.encodeTool(\n part.functionCall.name as string,\n part.functionCall.args as Record<string, unknown>\n )\n );\n }\n }\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Gemini streaming error:', error);\n controller.enqueue(sse.encodeError('An error occurred during streaming'));\n controller.close();\n }\n },\n });\n\n return new Response(stream, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('Gemini handler error:', error);\n return new Response(\n JSON.stringify({ error: 'Internal server error' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { C as ChatMessage } from '../types-2ZpUTVRN.mjs';
1
+ import { C as ChatMessage, T as ToolExecution } from '../types-K00dDlBC.mjs';
2
2
 
3
3
  interface UseChatOptions {
4
4
  /** API endpoint for chat */
@@ -11,6 +11,9 @@ interface UseChatOptions {
11
11
  onStreamStart?: () => void;
12
12
  /** Callback when streaming ends */
13
13
  onStreamEnd?: () => void;
14
+ /** Generate a fallback message when AI uses tools but provides no text.
15
+ * If not provided, uses a generic fallback message. */
16
+ generateFallbackMessage?: (toolCalls: ToolExecution[]) => string;
14
17
  }
15
18
  interface UseChatReturn {
16
19
  /** All messages in the conversation */
@@ -1,4 +1,4 @@
1
- import { C as ChatMessage } from '../types-2ZpUTVRN.js';
1
+ import { C as ChatMessage, T as ToolExecution } from '../types-K00dDlBC.js';
2
2
 
3
3
  interface UseChatOptions {
4
4
  /** API endpoint for chat */
@@ -11,6 +11,9 @@ interface UseChatOptions {
11
11
  onStreamStart?: () => void;
12
12
  /** Callback when streaming ends */
13
13
  onStreamEnd?: () => void;
14
+ /** Generate a fallback message when AI uses tools but provides no text.
15
+ * If not provided, uses a generic fallback message. */
16
+ generateFallbackMessage?: (toolCalls: ToolExecution[]) => string;
14
17
  }
15
18
  interface UseChatReturn {
16
19
  /** All messages in the conversation */