Best Practices
Guidelines, troubleshooting, and environment setup.
Best Practices
1. Keep API Keys Server-Side
Always use createAgentHandler on the server. Never expose LENS_API_KEY or OPENAI_API_KEY to the client.
.env
# NOT .env.local with NEXT_PUBLIC_ prefix
LENS_API_KEY=lens-xxx
OPENAI_API_KEY=sk-xxx2. Provide Rich Tool Metadata
The more metadata you provide, the better the LLM knows when and how to use the tool.
Good vs Bad
// Good
{
description: 'Search products by keyword, category, or author',
whenToUse: 'When the user wants to find, compare, or browse products',
schema: {
query: { type: 'string', required: true, description: 'Search keywords (1-3 words)' },
topK: { type: 'number', required: false, default: 10 },
},
output: 'Array of products with title, price, imageUrl',
execute: async (params) => { ... },
}
// Bad — LLM won't know when to use this
{
execute: async (params) => { ... },
}3. Always Return Structured ToolResult
ToolResult
// Success
{ success: true, result: { message: 'Found 5 products', products: [...] } }
// Error
{ success: false, error: 'Database connection failed' }4. Handle Errors Gracefully
Error handling
execute: async (params) => {
try {
const res = await fetch(url, { ... });
if (!res.ok) {
const err = await res.json();
return { success: false, error: err.error || 'Request failed' };
}
return await res.json();
} catch (err) {
return {
success: false,
error: err instanceof Error ? err.message : 'Unknown error',
};
}
}5. Use context.userId for User-Specific Tools
User context
user_orders: {
execute: async (params, context) => {
// context.userId is automatically populated by the SDK
const res = await fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({ ...params, userId: context?.userId }),
});
return await res.json();
},
}Environment Variables
.env
# Required (server-side only — never use NEXT_PUBLIC_ prefix)
LENS_API_KEY=lens-xxx # Lens OS API key
OPENAI_API_KEY=sk-xxx # OpenAI API key
# Optional
OPENAI_MODEL=gpt-4o # LLM model (default: gpt-4o)
NEXT_PUBLIC_BASE_URL=http://localhost:3000 # Base URL for internal API callsTroubleshooting
Module not found: @lens-os/sdk/server
Make sure your @lens-os/sdk version includes the ./server export. Check node_modules/@lens-os/sdk/package.json:
package.json exports
{
"exports": {
".": { "import": "./dist/index.mjs", "types": "./dist/index.d.mts" },
"./react": { "import": "./dist/react/index.mjs", "types": "./dist/react/index.d.mts" },
"./server": { "import": "./dist/server/index.mjs", "types": "./dist/server/index.d.mts" }
}
}SSE Connection Dropping
If the SSE stream disconnects prematurely, ensure your server/proxy is not buffering:
SSE Headers
// createAgentHandler already sets these headers:
{
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no', // for nginx
}Tools Not Being Called
- Check that
descriptionandwhenToUseare provided inToolExecutorConfig - Verify the tool is passed to
createAgentHandler({ toolExecutors: ... }) - Enable event logging:
Debug logging
onEvent: (event) => {
console.log('[SSE]', event.type, event);
}Action Request Timeout
If DOM actions timeout (default 30s), increase the timeout:
Increase timeout
createAgentHandler({
...config,
actionTimeout: 60000, // 60 seconds
});OpenAI Error: Image URLs only allowed for role 'user'
If using page state with screenshots, ensure you're using SDK version >= 0.1.3 which fixes the prompt builder to use role: "user" for page state messages containing images.
Warning
Never commit API keys to version control. Use
.env files and add them to .gitignore.