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 +22 -6
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +22 -6
- package/dist/api/index.mjs.map +1 -1
- package/package.json +2 -1
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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) {
|
package/dist/api/index.js.map
CHANGED
|
@@ -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"]}
|
package/dist/api/index.mjs
CHANGED
|
@@ -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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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) {
|
package/dist/api/index.mjs.map
CHANGED
|
@@ -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
|
+
"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"
|