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 }