ai-site-pilot 0.4.3 → 0.4.4

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/dist/api/index.js CHANGED
@@ -147,6 +147,7 @@ function createHandler(config) {
147
147
  }
148
148
  const decoder = new TextDecoder();
149
149
  let buffer = "";
150
+ const pendingToolCalls = /* @__PURE__ */ new Map();
150
151
  while (true) {
151
152
  const { done, value } = await reader.read();
152
153
  if (done) break;
@@ -165,12 +166,17 @@ function createHandler(config) {
165
166
  }
166
167
  if (delta?.tool_calls) {
167
168
  for (const toolCall of delta.tool_calls) {
168
- if (toolCall.function?.name && toolCall.function?.arguments) {
169
- try {
170
- const args = JSON.parse(toolCall.function.arguments);
171
- controller.enqueue(sse.encodeTool(toolCall.function.name, args));
172
- } catch {
173
- }
169
+ const index = toolCall.index ?? 0;
170
+ let pending = pendingToolCalls.get(index);
171
+ if (!pending) {
172
+ pending = { name: "", arguments: "" };
173
+ pendingToolCalls.set(index, pending);
174
+ }
175
+ if (toolCall.function?.name) {
176
+ pending.name = toolCall.function.name;
177
+ }
178
+ if (toolCall.function?.arguments) {
179
+ pending.arguments += toolCall.function.arguments;
174
180
  }
175
181
  }
176
182
  }
@@ -179,6 +185,16 @@ function createHandler(config) {
179
185
  }
180
186
  }
181
187
  }
188
+ for (const [, toolCall] of pendingToolCalls) {
189
+ if (toolCall.name && toolCall.arguments) {
190
+ try {
191
+ const args = JSON.parse(toolCall.arguments);
192
+ controller.enqueue(sse.encodeTool(toolCall.name, args));
193
+ } catch (e) {
194
+ console.error("Failed to parse tool arguments:", toolCall.arguments, e);
195
+ }
196
+ }
197
+ }
182
198
  controller.enqueue(sse.encodeDone());
183
199
  controller.close();
184
200
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createHandler.ts"],"names":[],"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 * Universal chat handler using OpenRouter\n * Works with any model: Gemini, GPT-4, Claude, Llama, etc.\n * No SDK required - just standard fetch.\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface HandlerConfig {\n /** OpenRouter API key (or set OPENROUTER_API_KEY env var) */\n apiKey?: string;\n /** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */\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 /** Your site URL (shown in OpenRouter dashboard) */\n siteUrl?: string;\n /** Your app name (shown in OpenRouter dashboard) */\n siteName?: string;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\ninterface OpenRouterMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\ninterface OpenRouterTool {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n };\n };\n}\n\n/**\n * Convert tool definitions to OpenAI/OpenRouter format\n */\nfunction convertTools(tools: ToolDefinition[]): OpenRouterTool[] {\n return tools.map(tool => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object' as const,\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 ...(value.enum && { enum: value.enum }),\n },\n ])\n ),\n required: tool.parameters.required,\n },\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler using OpenRouter\n *\n * Works with any model - just change the model string:\n * - 'google/gemini-2.0-flash-exp:free' (free!)\n * - 'openai/gpt-4o'\n * - 'anthropic/claude-3.5-sonnet'\n * - 'meta-llama/llama-3.1-70b-instruct'\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createHandler({\n * model: 'google/gemini-2.0-flash-exp:free',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createHandler(config: HandlerConfig) {\n const {\n apiKey = process.env.OPENROUTER_API_KEY,\n model = 'google/gemini-2.0-flash-exp:free',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n siteUrl,\n siteName,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'OpenRouter API key not configured. Get one at https://openrouter.ai' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Build messages array with system prompt\n const openRouterMessages: OpenRouterMessage[] = [\n { role: 'system', content: systemPrompt },\n ...messages.map((m) => ({\n role: m.role === 'assistant' ? 'assistant' as const : 'user' as const,\n content: m.content,\n })),\n ];\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n 'HTTP-Referer': siteUrl || '',\n 'X-Title': siteName || '',\n },\n body: JSON.stringify({\n model,\n messages: openRouterMessages,\n temperature,\n stream: true,\n ...(tools.length > 0 && { tools: convertTools(tools) }),\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n console.error('OpenRouter error:', error);\n controller.enqueue(sse.encodeError('Failed to get AI response'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n controller.enqueue(sse.encodeError('No response stream'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\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 const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n const delta = parsed.choices?.[0]?.delta;\n\n // Handle text content\n if (delta?.content) {\n controller.enqueue(sse.encodeText(delta.content));\n }\n\n // Handle tool calls\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n if (toolCall.function?.name && toolCall.function?.arguments) {\n try {\n const args = JSON.parse(toolCall.function.arguments);\n controller.enqueue(sse.encodeTool(toolCall.function.name, args));\n } catch {\n // Arguments might be streamed in chunks, skip incomplete\n }\n }\n }\n }\n } catch {\n // Skip malformed JSON\n }\n }\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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('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
+ {"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createHandler.ts"],"names":[],"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 * Universal chat handler using OpenRouter\n * Works with any model: Gemini, GPT-4, Claude, Llama, etc.\n * No SDK required - just standard fetch.\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface HandlerConfig {\n /** OpenRouter API key (or set OPENROUTER_API_KEY env var) */\n apiKey?: string;\n /** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */\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 /** Your site URL (shown in OpenRouter dashboard) */\n siteUrl?: string;\n /** Your app name (shown in OpenRouter dashboard) */\n siteName?: string;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\ninterface OpenRouterMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\ninterface OpenRouterTool {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n };\n };\n}\n\n/**\n * Convert tool definitions to OpenAI/OpenRouter format\n */\nfunction convertTools(tools: ToolDefinition[]): OpenRouterTool[] {\n return tools.map(tool => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object' as const,\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 ...(value.enum && { enum: value.enum }),\n },\n ])\n ),\n required: tool.parameters.required,\n },\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler using OpenRouter\n *\n * Works with any model - just change the model string:\n * - 'google/gemini-2.0-flash-exp:free' (free!)\n * - 'openai/gpt-4o'\n * - 'anthropic/claude-3.5-sonnet'\n * - 'meta-llama/llama-3.1-70b-instruct'\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createHandler({\n * model: 'google/gemini-2.0-flash-exp:free',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createHandler(config: HandlerConfig) {\n const {\n apiKey = process.env.OPENROUTER_API_KEY,\n model = 'google/gemini-2.0-flash-exp:free',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n siteUrl,\n siteName,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'OpenRouter API key not configured. Get one at https://openrouter.ai' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Build messages array with system prompt\n const openRouterMessages: OpenRouterMessage[] = [\n { role: 'system', content: systemPrompt },\n ...messages.map((m) => ({\n role: m.role === 'assistant' ? 'assistant' as const : 'user' as const,\n content: m.content,\n })),\n ];\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n 'HTTP-Referer': siteUrl || '',\n 'X-Title': siteName || '',\n },\n body: JSON.stringify({\n model,\n messages: openRouterMessages,\n temperature,\n stream: true,\n ...(tools.length > 0 && { tools: convertTools(tools) }),\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n console.error('OpenRouter error:', error);\n controller.enqueue(sse.encodeError('Failed to get AI response'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n controller.enqueue(sse.encodeError('No response stream'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n // Accumulate tool calls across stream chunks\n // OpenRouter streams tool calls in pieces: name first, then arguments in chunks\n const pendingToolCalls: Map<number, { name: string; arguments: string }> = new Map();\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 const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n const delta = parsed.choices?.[0]?.delta;\n\n // Handle text content\n if (delta?.content) {\n controller.enqueue(sse.encodeText(delta.content));\n }\n\n // Handle tool calls - accumulate across chunks\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n const index = toolCall.index ?? 0;\n\n // Get or create pending tool call\n let pending = pendingToolCalls.get(index);\n if (!pending) {\n pending = { name: '', arguments: '' };\n pendingToolCalls.set(index, pending);\n }\n\n // Accumulate name (usually comes in first chunk)\n if (toolCall.function?.name) {\n pending.name = toolCall.function.name;\n }\n\n // Accumulate arguments (streamed in chunks)\n if (toolCall.function?.arguments) {\n pending.arguments += toolCall.function.arguments;\n }\n }\n }\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n\n // Emit all accumulated tool calls now that streaming is complete\n for (const [, toolCall] of pendingToolCalls) {\n if (toolCall.name && toolCall.arguments) {\n try {\n const args = JSON.parse(toolCall.arguments);\n controller.enqueue(sse.encodeTool(toolCall.name, args));\n } catch (e) {\n console.error('Failed to parse tool arguments:', toolCall.arguments, e);\n }\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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('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"]}
@@ -145,6 +145,7 @@ function createHandler(config) {
145
145
  }
146
146
  const decoder = new TextDecoder();
147
147
  let buffer = "";
148
+ const pendingToolCalls = /* @__PURE__ */ new Map();
148
149
  while (true) {
149
150
  const { done, value } = await reader.read();
150
151
  if (done) break;
@@ -163,12 +164,17 @@ function createHandler(config) {
163
164
  }
164
165
  if (delta?.tool_calls) {
165
166
  for (const toolCall of delta.tool_calls) {
166
- if (toolCall.function?.name && toolCall.function?.arguments) {
167
- try {
168
- const args = JSON.parse(toolCall.function.arguments);
169
- controller.enqueue(sse.encodeTool(toolCall.function.name, args));
170
- } catch {
171
- }
167
+ const index = toolCall.index ?? 0;
168
+ let pending = pendingToolCalls.get(index);
169
+ if (!pending) {
170
+ pending = { name: "", arguments: "" };
171
+ pendingToolCalls.set(index, pending);
172
+ }
173
+ if (toolCall.function?.name) {
174
+ pending.name = toolCall.function.name;
175
+ }
176
+ if (toolCall.function?.arguments) {
177
+ pending.arguments += toolCall.function.arguments;
172
178
  }
173
179
  }
174
180
  }
@@ -177,6 +183,16 @@ function createHandler(config) {
177
183
  }
178
184
  }
179
185
  }
186
+ for (const [, toolCall] of pendingToolCalls) {
187
+ if (toolCall.name && toolCall.arguments) {
188
+ try {
189
+ const args = JSON.parse(toolCall.arguments);
190
+ controller.enqueue(sse.encodeTool(toolCall.name, args));
191
+ } catch (e) {
192
+ console.error("Failed to parse tool arguments:", toolCall.arguments, e);
193
+ }
194
+ }
195
+ }
180
196
  controller.enqueue(sse.encodeDone());
181
197
  controller.close();
182
198
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createHandler.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 * Universal chat handler using OpenRouter\n * Works with any model: Gemini, GPT-4, Claude, Llama, etc.\n * No SDK required - just standard fetch.\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface HandlerConfig {\n /** OpenRouter API key (or set OPENROUTER_API_KEY env var) */\n apiKey?: string;\n /** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */\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 /** Your site URL (shown in OpenRouter dashboard) */\n siteUrl?: string;\n /** Your app name (shown in OpenRouter dashboard) */\n siteName?: string;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\ninterface OpenRouterMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\ninterface OpenRouterTool {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n };\n };\n}\n\n/**\n * Convert tool definitions to OpenAI/OpenRouter format\n */\nfunction convertTools(tools: ToolDefinition[]): OpenRouterTool[] {\n return tools.map(tool => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object' as const,\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 ...(value.enum && { enum: value.enum }),\n },\n ])\n ),\n required: tool.parameters.required,\n },\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler using OpenRouter\n *\n * Works with any model - just change the model string:\n * - 'google/gemini-2.0-flash-exp:free' (free!)\n * - 'openai/gpt-4o'\n * - 'anthropic/claude-3.5-sonnet'\n * - 'meta-llama/llama-3.1-70b-instruct'\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createHandler({\n * model: 'google/gemini-2.0-flash-exp:free',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createHandler(config: HandlerConfig) {\n const {\n apiKey = process.env.OPENROUTER_API_KEY,\n model = 'google/gemini-2.0-flash-exp:free',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n siteUrl,\n siteName,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'OpenRouter API key not configured. Get one at https://openrouter.ai' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Build messages array with system prompt\n const openRouterMessages: OpenRouterMessage[] = [\n { role: 'system', content: systemPrompt },\n ...messages.map((m) => ({\n role: m.role === 'assistant' ? 'assistant' as const : 'user' as const,\n content: m.content,\n })),\n ];\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n 'HTTP-Referer': siteUrl || '',\n 'X-Title': siteName || '',\n },\n body: JSON.stringify({\n model,\n messages: openRouterMessages,\n temperature,\n stream: true,\n ...(tools.length > 0 && { tools: convertTools(tools) }),\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n console.error('OpenRouter error:', error);\n controller.enqueue(sse.encodeError('Failed to get AI response'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n controller.enqueue(sse.encodeError('No response stream'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\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 const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n const delta = parsed.choices?.[0]?.delta;\n\n // Handle text content\n if (delta?.content) {\n controller.enqueue(sse.encodeText(delta.content));\n }\n\n // Handle tool calls\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n if (toolCall.function?.name && toolCall.function?.arguments) {\n try {\n const args = JSON.parse(toolCall.function.arguments);\n controller.enqueue(sse.encodeTool(toolCall.function.name, args));\n } catch {\n // Arguments might be streamed in chunks, skip incomplete\n }\n }\n }\n }\n } catch {\n // Skip malformed JSON\n }\n }\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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('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
+ {"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createHandler.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 * Universal chat handler using OpenRouter\n * Works with any model: Gemini, GPT-4, Claude, Llama, etc.\n * No SDK required - just standard fetch.\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface HandlerConfig {\n /** OpenRouter API key (or set OPENROUTER_API_KEY env var) */\n apiKey?: string;\n /** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */\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 /** Your site URL (shown in OpenRouter dashboard) */\n siteUrl?: string;\n /** Your app name (shown in OpenRouter dashboard) */\n siteName?: string;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\ninterface OpenRouterMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\ninterface OpenRouterTool {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n };\n };\n}\n\n/**\n * Convert tool definitions to OpenAI/OpenRouter format\n */\nfunction convertTools(tools: ToolDefinition[]): OpenRouterTool[] {\n return tools.map(tool => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object' as const,\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 ...(value.enum && { enum: value.enum }),\n },\n ])\n ),\n required: tool.parameters.required,\n },\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler using OpenRouter\n *\n * Works with any model - just change the model string:\n * - 'google/gemini-2.0-flash-exp:free' (free!)\n * - 'openai/gpt-4o'\n * - 'anthropic/claude-3.5-sonnet'\n * - 'meta-llama/llama-3.1-70b-instruct'\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createHandler({\n * model: 'google/gemini-2.0-flash-exp:free',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createHandler(config: HandlerConfig) {\n const {\n apiKey = process.env.OPENROUTER_API_KEY,\n model = 'google/gemini-2.0-flash-exp:free',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n siteUrl,\n siteName,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'OpenRouter API key not configured. Get one at https://openrouter.ai' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Build messages array with system prompt\n const openRouterMessages: OpenRouterMessage[] = [\n { role: 'system', content: systemPrompt },\n ...messages.map((m) => ({\n role: m.role === 'assistant' ? 'assistant' as const : 'user' as const,\n content: m.content,\n })),\n ];\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n 'HTTP-Referer': siteUrl || '',\n 'X-Title': siteName || '',\n },\n body: JSON.stringify({\n model,\n messages: openRouterMessages,\n temperature,\n stream: true,\n ...(tools.length > 0 && { tools: convertTools(tools) }),\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n console.error('OpenRouter error:', error);\n controller.enqueue(sse.encodeError('Failed to get AI response'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n controller.enqueue(sse.encodeError('No response stream'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n // Accumulate tool calls across stream chunks\n // OpenRouter streams tool calls in pieces: name first, then arguments in chunks\n const pendingToolCalls: Map<number, { name: string; arguments: string }> = new Map();\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 const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n const delta = parsed.choices?.[0]?.delta;\n\n // Handle text content\n if (delta?.content) {\n controller.enqueue(sse.encodeText(delta.content));\n }\n\n // Handle tool calls - accumulate across chunks\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n const index = toolCall.index ?? 0;\n\n // Get or create pending tool call\n let pending = pendingToolCalls.get(index);\n if (!pending) {\n pending = { name: '', arguments: '' };\n pendingToolCalls.set(index, pending);\n }\n\n // Accumulate name (usually comes in first chunk)\n if (toolCall.function?.name) {\n pending.name = toolCall.function.name;\n }\n\n // Accumulate arguments (streamed in chunks)\n if (toolCall.function?.arguments) {\n pending.arguments += toolCall.function.arguments;\n }\n }\n }\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n\n // Emit all accumulated tool calls now that streaming is complete\n for (const [, toolCall] of pendingToolCalls) {\n if (toolCall.name && toolCall.arguments) {\n try {\n const args = JSON.parse(toolCall.arguments);\n controller.enqueue(sse.encodeTool(toolCall.name, args));\n } catch (e) {\n console.error('Failed to parse tool arguments:', toolCall.arguments, e);\n }\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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-site-pilot",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "AI chat widget that can control and navigate your website. Works with any AI model via OpenRouter.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -71,6 +71,7 @@
71
71
  "react-dom": "^18.0.0 || ^19.0.0"
72
72
  },
73
73
  "dependencies": {
74
+ "ai-site-pilot": "^0.4.3",
74
75
  "framer-motion": "^11.0.0",
75
76
  "lucide-react": "^0.400.0",
76
77
  "react-markdown": "^9.0.0"