Installation
npm install @lua-ai/device-client
DeviceClientConfig
The configuration object passed to new DeviceClient().
Agent ID to connect to. Found in .lua/lua.config.yaml or the Lua dashboard.
API key for authentication. Generate with lua auth configure.
Unique name for this device. Lowercase with hyphens (e.g., label-printer, pico-sensor-01).
commands
DeviceCommandDefinition[]
Array of commands this device supports. Sent to the server at connect time so the agent can use them as tools. See Self-Describing Commands .
transport
'socketio' | 'mqtt'
default: "socketio"
Transport protocol. Use 'mqtt' for constrained devices or environments where MQTT is preferred.
serverUrl
string
default: "https://api.heylua.ai"
Server URL for Socket.IO transport.
mqttUrl
string
default: "mqtts://mqtt.heylua.ai:8883"
MQTT broker URL. Required when transport is 'mqtt'.
cdnUrl
string
default: "https://cdn.heylua.ai"
CDN URL for file uploads and downloads.
Optional device group name for fan-out commands (e.g., 'printers', 'sensors-floor-2').
DeviceCommandDefinition
Each entry in the commands array describes one command the device supports.
Command name. Used by the agent to invoke the command (e.g., read_temperature).
Human-readable description. Shown to the AI agent as the tool description. Write it as if explaining to a person what the command does.
JSON Schema for command input parameters. The agent uses this to know what arguments to pass.
Command timeout in milliseconds. If the device does not respond within this time, the command fails.
retry
{ maxAttempts: number; backoffMs: number }
Retry configuration for failed commands. The gateway retries with exponential backoff.
Connection Lifecycle
import { DeviceClient } from '@lua-ai/device-client' ;
const device = new DeviceClient ({ /* config */ });
// Connect -- resolves when authenticated
await device . connect ();
// Check connection status
console . log ( device . isConnected ()); // true
// Listen for lifecycle events
device . on ( 'connected' , () => console . log ( 'Connected' ));
device . on ( 'reconnected' , () => console . log ( 'Reconnected' ));
device . on ( 'disconnected' , ( reason ) => console . log ( 'Disconnected:' , reason ));
device . on ( 'error' , ( err ) => console . error ( 'Error:' , err ));
// Graceful shutdown (stops auto-reconnect)
await device . disconnect ();
The client automatically reconnects on network failures with jittered exponential backoff (1s to 30s). Call disconnect() to stop reconnection. The client also registers SIGTERM and SIGINT handlers for graceful shutdown.
Handling Commands
Register handlers for commands the agent can send to this device:
device . onCommand ( 'read_temperature' , async ( payload ) => {
// payload contains whatever the agent sent (validated against inputSchema)
const reading = await sensor . read ();
return {
temperature: reading . celsius ,
humidity: reading . humidity ,
timestamp: new Date (). toISOString (),
};
});
device . onCommand ( 'set_led' , async ( payload ) => {
const { color , brightness } = payload ;
await led . setColor ( color );
await led . setBrightness ( brightness );
return { success: true , color , brightness };
});
If a handler throws an error, the error message is returned to the agent as a failed command result. The agent sees the error and can decide how to respond to the user.
Firing Triggers
Send events from the device to the agent:
// Fire a trigger -- resolves when the server acknowledges receipt
await device . trigger ( 'temperature_alert' , {
temperature: 42.1 ,
threshold: 40 ,
sensor: 'main-floor' ,
});
// Optionally listen for trigger execution results
device . onTriggerResult ( 'temperature_alert' , ( result ) => {
console . log ( 'Agent handled the alert:' , result );
});
See Triggers for the full guide on how to handle triggers on the agent side.
CDN Uploads
Every DeviceClient instance includes a cdn property for uploading and downloading files:
import fs from 'fs' ;
// Upload a screenshot
const screenshot = fs . readFileSync ( '/tmp/screenshot.png' );
const result = await device . cdn . upload ( screenshot , 'screenshot.png' , 'image/png' );
console . log ( result . url ); // https://cdn.heylua.ai/{fileId}
// Get the URL for a previously uploaded file
const url = device . cdn . getUrl ( result . fileId );
// Download a file
const buffer = await device . cdn . download ( result . fileId );
fs . writeFileSync ( '/tmp/downloaded.png' , buffer );
See CDN Uploads for more details.
Transport Configuration
const device = new DeviceClient ({
agentId: 'your-agent-id' ,
apiKey: 'your-api-key' ,
deviceName: 'my-device' ,
// transport defaults to 'socketio'
// serverUrl defaults to 'https://api.heylua.ai'
commands: [
{ name: 'ping' , description: 'Health check' },
],
});
Socket.IO is best for Node.js applications running on desktops, servers, or single-board computers with plenty of memory. const device = new DeviceClient ({
agentId: 'your-agent-id' ,
apiKey: 'your-api-key' ,
deviceName: 'my-device' ,
transport: 'mqtt' ,
// mqttUrl defaults to 'mqtts://mqtt.heylua.ai:8883'
commands: [
{ name: 'ping' , description: 'Health check' },
],
});
MQTT is best for constrained devices, battery-powered sensors, or environments with flaky network connectivity. MQTT supports offline message queueing with persistent sessions.
Complete Example
A device that simulates a smart thermostat with temperature reading, target temperature setting, and a high-temperature alert trigger:
import { DeviceClient } from '@lua-ai/device-client' ;
let currentTemp = 21.0 ;
let targetTemp = 22.0 ;
let heatingOn = false ;
const device = new DeviceClient ({
agentId: process . env . LUA_AGENT_ID ! ,
apiKey: process . env . LUA_API_KEY ! ,
deviceName: 'smart-thermostat' ,
commands: [
{
name: 'read_temperature' ,
description: 'Read the current room temperature in celsius' ,
},
{
name: 'set_target' ,
description: 'Set the target temperature for the thermostat' ,
inputSchema: {
type: 'object' ,
properties: {
temperature: { type: 'number' , minimum: 10 , maximum: 35 },
},
required: [ 'temperature' ],
},
},
{
name: 'get_status' ,
description: 'Get full thermostat status including heating state' ,
},
],
});
device . onCommand ( 'read_temperature' , async () => {
return { temperature: currentTemp , unit: 'celsius' };
});
device . onCommand ( 'set_target' , async ( payload ) => {
targetTemp = payload . temperature ;
heatingOn = currentTemp < targetTemp ;
return { targetTemperature: targetTemp , heatingOn };
});
device . onCommand ( 'get_status' , async () => {
return {
currentTemperature: currentTemp ,
targetTemperature: targetTemp ,
heatingOn ,
timestamp: new Date (). toISOString (),
};
});
async function main () {
device . on ( 'connected' , () => console . log ( 'Thermostat online' ));
device . on ( 'disconnected' , ( reason ) => console . log ( 'Offline:' , reason ));
await device . connect ();
// Simulate temperature changes and fire trigger on high temp
setInterval ( async () => {
currentTemp += ( Math . random () - 0.4 ) * 0.5 ;
currentTemp = Math . round ( currentTemp * 10 ) / 10 ;
if ( currentTemp > 35 ) {
await device . trigger ( 'high_temperature' , {
temperature: currentTemp ,
threshold: 35 ,
});
}
}, 5000 );
}
main (). catch ( console . error );
Next Steps
MQTT Transport Deep dive into MQTT configuration and topic structure
CDN Uploads Upload and download files from your device
Triggers Send events from your device to the agent
API Reference Complete class and method documentation