Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.uplink.build/llms.txt

Use this file to discover all available pages before exploring further.

Learn how to use AI automation effectively by following these best practices and patterns.

When to Use AI vs Manual Automation

Use AI When:

  • Dynamic UIs - Elements don’t have stable selectors or IDs
  • Complex interactions - Multi-step workflows that are hard to script
  • Natural workflows - Actions easier to describe than code
  • Rapid prototyping - Quick proof-of-concept without precise selectors
  • Handling variations - Page structure varies based on state or user
  • Data extraction - Unstructured content needs to be parsed

Use Manual Automation When:

  • Performance critical - AI adds 1-3 seconds per call
  • High volume - Thousands of operations (cost adds up)
  • Stable selectors - Elements have reliable IDs or classes
  • Exact precision - Need pixel-perfect control
  • Offline scenarios - No internet or API access
  • Simple operations - Basic click/input on known elements

Decision Tree

Is the UI stable with reliable selectors?
├─ Yes → Use manual automation (click, input, etc.)
└─ No → Is performance critical?
    ├─ Yes → Use manual automation with resilient selectors
    └─ No → Is cost a concern?
        ├─ Yes → Use manual for bulk, AI for complex parts
        └─ No → Use AI automation
The most effective pattern combines both AI and manual automation:
import uplink from '@uplink-code/uplink'
import ai from '@uplink-code/ai'

const agent = ai.createAgent({
  provider: 'anthropic',
  options: { apiKey: process.env.ANTHROPIC_API_KEY }
})

const session = await uplink.session('<project-api-key>', {
  projectId: '<project-id>',
  include: { ecdsa: true, ecdh: true }
})
const client = await uplink.client.fromSession(session, { agent })
const browser = await client.launch()
const page = await browser.newPage()

// Manual: Fast, reliable navigation
await page.goto('https://example.com/products')

// AI: Handle dynamic cookie banner
await page.act('Accept cookies if a banner appears')

// Manual: Precise form filling
await page.input('#search', 'wireless headphones')
await page.click('#search-button')

// AI: Complex filtering
await page.act('Filter results to show only items under $100 with free shipping')

// AI: Smart extraction
const products = await page.extract(
  'Extract the first 5 products with name, price, and rating',
  productsSchema
)

// Manual: Precise navigation
if (products.success && products.data) {
  await page.goto(products.data[0].url)
}

Prompt Engineering

Write Clear, Specific Instructions

Good prompts:
// Specific location and description
await page.act('Click the blue "Sign In" button in the top-right corner')

// Include context
await page.act('In the shipping form, select "Express Delivery" from the shipping method dropdown')

// Describe visual characteristics
await page.act('Click the red "Delete" button next to the item named "Widget Pro"')
Bad prompts:
// Too vague
await page.act('Click the button')

// Ambiguous
await page.act('Do the login thing')

// Missing context
await page.act('Select the option')

Break Down Complex Tasks

Instead of one massive instruction:
// Bad: Too complex for one instruction
await page.act('Login with username admin and password secret, then navigate to settings, update the email to newemail@example.com, and save')

// Good: Break into logical steps
await page.act('Fill in username "admin" and password "secret", then click login')
await page.act('Click the settings link in the navigation')
await page.act('Change the email field to newemail@example.com')
await page.act('Click the save button')

Provide Context

Help the AI understand the page structure:
// Without context
await page.extract('Get the price', priceSchema)

// With context (better results)
await page.extract(
  'Extract the current price shown in the product details section',
  priceSchema
)

Error Handling Patterns

Graceful Fallbacks

Always have a backup plan:
// Try AI first
let result = await page.act('Click the submit button')

if (!result.success) {
  // Fallback to manual
  console.log('AI failed, trying manual automation')
  try {
    await page.click('#submit-button')
  } catch (error) {
    console.error('Both AI and manual failed')
    throw error
  }
}

Retry with Different Instructions

If extraction fails, try rephrasing:
let result = await page.extract('Get the price', priceSchema)

if (!result.success) {
  // Try a different instruction
  result = await page.extract(
    'Extract the product price shown in USD',
    priceSchema
  )
}

if (!result.success) {
  // Fall back to manual extraction
  const priceText = await page.evaluate(() =>
    document.querySelector('.price')?.textContent
  )
}

Validation and Verification

Verify AI actions worked as expected:
// Perform action
await page.act('Add item to cart')

// Verify result
const cartCount = await page.evaluate(() =>
  document.querySelector('.cart-count')?.textContent
)

if (cartCount === '0') {
  console.error('Item was not added to cart')
  // Retry or use manual method
}

Cost Management

Optimize API Usage

AI calls cost money - use them wisely:
// Bad: Multiple separate calls
await page.act('Click button A')
await page.act('Click button B')
await page.act('Click button C')

// Good: Combine when possible
await page.act('Click buttons A, B, and C in sequence')

Cache Extracted Data

Don’t extract the same data repeatedly:
// Extract once
const productData = await page.extract(
  'Extract all product details',
  productSchema
)

// Reuse the data
if (productData.success && productData.data) {
  console.log(productData.data.name)
  console.log(productData.data.price)
  // Don't call extract again for the same data
}

Use Cheaper Models for Simple Tasks

// For simple actions, use Haiku (faster and cheaper)
const cheapAgent = ai.createAgent({
  provider: 'anthropic',
  model: 'claude-haiku-4-20241024',
  options: { apiKey: process.env.ANTHROPIC_API_KEY }
})

// Use for simple tasks
page.setAgent(cheapAgent)
await page.act('Click the agree button')

// Switch to Sonnet for complex tasks
const smartAgent = ai.createAgent({
  provider: 'anthropic',
  model: 'claude-sonnet-4-5-20250929',
  options: { apiKey: process.env.ANTHROPIC_API_KEY }
})

page.setAgent(smartAgent)
await page.extract('Extract complex nested data', complexSchema)

Batch Operations

Process multiple items with fewer AI calls:
// Bad: Extract each product separately (5 AI calls)
for (let i = 0; i < 5; i++) {
  await page.goto(`/product/${i}`)
  await page.extract('Get product details', productSchema)
}

// Good: Extract all at once (1 AI call)
await page.goto('/products')
const result = await page.extract(
  'Extract details for the first 5 products',
  z.object({
    products: z.array(productSchema)
  })
)

Testing and Reliability

Test Against Real Pages

Always test with actual websites:
// Test extraction against real page
const testUrl = 'https://example.com/product/test-item'

const result = await page.extract(
  'Extract product details',
  productSchema
)

if (!result.success) {
  console.error('Extraction failed on test page:', result.error)
  // Adjust schema or instruction
}

Handle Page Variations

Pages may look different based on state:
// Check for variations
const isLoggedIn = await page.evaluate(() =>
  !!document.querySelector('.user-profile')
)

if (!isLoggedIn) {
  await page.act('Click the login button')
  // Handle login...
}

// Now proceed with main task
await page.act('Navigate to dashboard')

Set Timeouts

AI operations can take time:
// Set reasonable timeout for complex extractions
try {
  const result = await Promise.race([
    page.extract('Extract data', schema),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), 30000)
    )
  ])
} catch (error) {
  console.error('Extraction timed out, trying simpler approach')
  // Fallback strategy
}

Monitor Success Rates

Track how often AI automation succeeds:
let successCount = 0
let totalAttempts = 0

async function automateWithTracking() {
  totalAttempts++

  const result = await page.act('Perform action')

  if (result.success) {
    successCount++
  }

  console.log(`Success rate: ${(successCount / totalAttempts * 100).toFixed(1)}%`)

  if (successCount / totalAttempts < 0.8) {
    console.warn('Success rate below 80%, consider switching to manual automation')
  }
}

Schema Design

Start Simple, Then Expand

Begin with basic schemas and add complexity as needed:
// Start simple
const simpleSchema = z.object({
  title: z.string(),
  price: z.number()
})

// Test it
let result = await page.extract('Extract product info', simpleSchema)

// If successful, expand
const detailedSchema = z.object({
  title: z.string(),
  price: z.number(),
  rating: z.number(),
  reviews: z.array(z.object({
    author: z.string(),
    text: z.string(),
    rating: z.number()
  }))
})

Use Appropriate Types

Match types to the actual data format:
// If price includes currency symbol
const schema = z.object({
  price: z.string() // "$29.99"
})

// If you need numeric price, transform it
const schema = z.object({
  price: z.string().transform(str =>
    parseFloat(str.replace(/[$,]/g, ''))
  )
})

// If price is already numeric in HTML
const schema = z.object({
  price: z.number() // 29.99
})

Make Fields Optional When Appropriate

Not all data is always present:
const schema = z.object({
  // Always present
  name: z.string(),
  price: z.number(),

  // Sometimes present
  discount: z.number().optional(),
  originalPrice: z.number().optional(),
  badge: z.string().optional(), // "Best Seller", "New", etc.

  // May be null
  reviewCount: z.number().nullable(),
})

Performance Optimization

Minimize Page Context

The AI analyzes page content - simpler pages = faster processing:
// Remove unnecessary elements before extraction
await page.evaluate(() => {
  // Hide ads, footers, etc. to reduce context
  document.querySelectorAll('.ad, footer, .sidebar').forEach(el => {
    el.style.display = 'none'
  })
})

// Now extract (faster with less content)
const result = await page.extract('Extract article', schema)

Parallel Execution

Run independent operations in parallel:
// Sequential (slow)
await page.act('Accept cookies')
await page.act('Close popup')

// Parallel (faster)
await Promise.all([
  page.act('Accept cookies if present'),
  page.act('Close popup if present')
])

Pre-navigate and Cache

Navigate ahead of time:
// Pre-load pages
const pages = await Promise.all([
  browser.newPage(),
  browser.newPage(),
  browser.newPage()
])

await Promise.all([
  pages[0].goto('https://example.com/page1'),
  pages[1].goto('https://example.com/page2'),
  pages[2].goto('https://example.com/page3')
])

// Now extract from all pages in parallel
const results = await Promise.all([
  pages[0].extract('Extract data', schema),
  pages[1].extract('Extract data', schema),
  pages[2].extract('Extract data', schema)
])

Security Considerations

Protect API Keys

Never expose API keys in code:
// Bad: Hardcoded key
const agent = ai.createAgent({
  provider: 'anthropic',
  options: {
    apiKey: 'sk-ant-api03-...' // DON'T DO THIS
  }
})

// Good: Environment variables
const agent = ai.createAgent({
  provider: 'anthropic',
  options: {
    apiKey: process.env.ANTHROPIC_API_KEY
  }
})

Sanitize Instructions

If instructions come from user input, sanitize them:
// If instruction includes user input
const userQuery = getUserInput()

// Validate and sanitize
const sanitized = userQuery
  .trim()
  .substring(0, 500) // Limit length
  .replace(/[<>]/g, '') // Remove potentially harmful chars

await page.act(`Search for ${sanitized}`)

Review AI Actions in Development

Log actions during development to verify behavior:
const result = await page.act('Perform action')

// Review what AI did
console.log('Actions taken:')
result.actions.forEach(action => {
  console.log(`- ${action.type}(${JSON.stringify(action.params)})`)
})

Complete Example

Here’s a complete example following all best practices:
import uplink from '@uplink-code/uplink'
import ai from '@uplink-code/ai'
import { z } from 'zod'

// Validate environment
if (!process.env.ANTHROPIC_API_KEY) {
  throw new Error('ANTHROPIC_API_KEY required')
}

// Create agent
const agent = ai.createAgent({
  provider: 'anthropic',
  model: 'claude-sonnet-4-5-20250929',
  options: { apiKey: process.env.ANTHROPIC_API_KEY }
})

// Define schema
const productSchema = z.object({
  name: z.string(),
  price: z.number(),
  rating: z.number().optional(),
  inStock: z.boolean()
})

const session = await uplink.session('<project-api-key>', {
  projectId: '<project-id>',
  include: { ecdsa: true, ecdh: true }
})
const client = await uplink.client.fromSession(session, { agent })

try {
  const browser = await client.launch()
  const page = await browser.newPage()

  // Manual navigation (fast)
  await page.goto('https://shop.example.com')

  // AI for dynamic content (flexible)
  await page.act('Accept cookies if a banner appears')

  // Manual search (precise)
  await page.input('#search', 'wireless headphones')
  await page.click('#search-submit')

  // Wait for results
  await page.waitForSelector('.search-results')

  // AI extraction (smart)
  const result = await page.extract(
    'Extract the first 5 products with name, price, rating, and stock status',
    z.object({
      products: z.array(productSchema)
    })
  )

  if (result.success && result.data) {
    // Process results
    result.data.products.forEach(product => {
      console.log(`${product.name}: $${product.price}`)
    })
  } else {
    // Fallback to manual
    console.error('AI extraction failed, using manual method')

    const products = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('.product')).map(el => ({
        name: el.querySelector('.name')?.textContent,
        price: parseFloat(el.querySelector('.price')?.textContent || '0')
      }))
    })
  }

  await page.close()
  await browser.close()
} finally {
  await client.close()
}

Natural Language Actions

Learn about page.act()

Data Extraction

Learn about page.extract()

AI Setup

Configure AI agents

Page API Overview

Manual automation methods