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-xxx

2. 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 calls

Troubleshooting

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 description and whenToUse are provided in ToolExecutorConfig
  • 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.