Skip to main content

Examples

Working patterns built on the SDK. Each uses the published @poolot/lily-web API.

A streaming chat, plain JavaScript

import { createLilyAgent } from '@poolot/lily-web'

const agent = await createLilyAgent({
  apiKey: userToken,
  model: 'poolot-standard',
  systemPrompt: 'You are a helpful assistant for the Acme docs.',
})

const history = []

async function ask(text) {
  history.push({ role: 'user', content: text })
  let answer = ''

  await agent.runStreaming(
    history,
    (token) => {
      answer += token
      process.stdout.write(token)
    },
    (event) => {
      if (event.type === 'tool_call') console.log(`\n[${event.name}]`)
    },
  )

  history.push({ role: 'assistant', content: answer })
  return answer
}

await ask('What is Pathvela?')
await ask('And how does the CLI relate to the SDK?')

A React chat component (browser)

Import the SDK lazily so the WebAssembly engine never loads during SSR, and stream tokens into state.

import { useEffect, useRef, useState } from 'react'
import type { LilyAgentHandle, LilyEvent } from '@poolot/lily-web'

export function Chat({ userToken }: { userToken: string }) {
  const agentRef = useRef<LilyAgentHandle | null>(null)
  const [messages, setMessages] = useState<{ role: string; content: string }[]>([])
  const [draft, setDraft] = useState('')
  const [streaming, setStreaming] = useState('')

  useEffect(() => {
    let alive = true
    ;(async () => {
      const { createLilyAgent } = await import('@poolot/lily-web')
      const agent = await createLilyAgent({ apiKey: userToken, model: 'poolot-standard' })
      if (alive) agentRef.current = agent
    })()
    return () => {
      alive = false
      agentRef.current?.abort()
    }
  }, [userToken])

  async function send() {
    const agent = agentRef.current
    if (!agent || !draft.trim()) return

    const next = [...messages, { role: 'user', content: draft }]
    setMessages(next)
    setDraft('')

    let answer = ''
    await agent.runStreaming(
      next,
      (token) => {
        answer += token
        setStreaming(answer)
      },
      (event: LilyEvent) => {
        if (event.type === 'done') {
          setMessages((m) => [...m, { role: 'assistant', content: event.answer }])
          setStreaming('')
        }
      },
    )
  }

  return (
    <div>
      {messages.map((m, i) => (
        <p key={i}><b>{m.role}:</b> {m.content}</p>
      ))}
      {streaming && <p><b>assistant:</b> {streaming}</p>}
      <input value={draft} onChange={(e) => setDraft(e.target.value)} />
      <button onClick={send}>Send</button>
    </div>
  )
}

Answering the agent mid-run

Register an input handler so the agent's ask_user questions are answered by your UI:

import { setInputHandler } from '@poolot/lily-web'

setInputHandler((question, placeholder) => {
  return new Promise((resolve) => {
    showModal({ question, placeholder, onSubmit: resolve, onCancel: () => resolve(null) })
  })
})

// Later, when tearing down:
setInputHandler(null)

Structured output

The @poolot/lily-web/structured entry point runs the agent and validates the result against a schema, retrying on mismatch:

import { createLilyAgent } from '@poolot/lily-web'
import { runStructured } from '@poolot/lily-web/structured'

const agent = await createLilyAgent({ apiKey })

const { value } = await runStructured(
  agent,
  [{ role: 'user', content: 'Extract the title and three tags from this article.' }],
  {
    schema: {
      type: 'object',
      properties: {
        title: { type: 'string' },
        tags: { type: 'array', items: { type: 'string' } },
      },
      required: ['title', 'tags'],
    },
    maxRetries: 2,
  },
)

console.log(value.title, value.tags)

Reading account and usage

const me = await agent.getMe()
console.log(`${me.tier} — balance ${me.balance}`)

const result = await agent.runDetailed([{ role: 'user', content: 'Hello' }])
console.log(result.usage) // { inputTokens, outputTokens, cachedInputTokens, calls }

See also