Skip to main content
@lua-ai-global/device-client connects a Node process to an agent as a device over Socket.IO or MQTT, runs command handlers, sends triggers, and uploads files. Version 1.1.0; Node 18 or later. Verified against @lua-ai-global/device-client 1.1.0.

Quick example

device.ts
trigger() needs the triggers operation on the credential: without it the platform answers with a trigger_error event, OPERATION_FORBIDDEN, and the call rejects after the 10-second acknowledgment wait.

DeviceClient

new DeviceClient(config) creates a client; it doesn’t connect until connect() is called. The constructor throws deviceCredential or apiKey is required when neither credential field is set and deviceCredential and apiKey disagree when both are set to different values.
string
required
The agent the device connects to. Must equal the agent in the credential’s binding.
string
required
The device name. Must equal the name in the credential’s binding; hyphens become underscores in tool names.
string
The device credential. Required unless apiKey is set.
string
deprecated
A legacy API key. Still accepted; use deviceCredential for new devices.
DeviceCommandDefinition[]
default:"[]"
Commands sent at connect time; each becomes a tool. Omit when the device is declared with defineDevice.
'socketio' | 'mqtt'
default:"socketio"
mqtt routes everything through MqttTransport.
string
default:"https://api.heylua.ai"
Socket.IO server. The client connects to the /devices namespace over WebSocket only.
string
default:"wss://mqtt.heylua.ai/mqtt"
Broker URL for the MQTT transport.
string
default:"https://cdn.heylua.ai"
Base URL for cdn.
string
Group name reported at connect; enables group fan-out for devices declared with defineDevice.
string
default:"device-node"
Client family reported in the Socket.IO handshake for diagnostics. Never used for authorization.
string
default:"1.1.0"
Your application’s version, reported for diagnostics alongside the package’s device-node family.
DeviceCommandDefinition
string
required
Command name; must match ^[a-z][a-z0-9_]{0,63}$ or the platform drops it.
string
required
The tool description the model reads. Cut at 500 characters.
Record<string, any>
JSON Schema for the payload; at most 4 KB when serialized.
number
default:30000
How long the platform waits for the response.
{ maxAttempts: number; backoffMs: number }
Platform-side retries after a timeout or transport failure, waiting backoffMs × attempt between attempts. Both fields are typed as required; on the wire a missing maxAttempts counts as 1 attempt and a missing backoffMs as 1,000 ms.

Methods

connect()

Opens the connection and resolves once the platform has accepted the credential.
Returns — resolves after the connected event. Errors — rejects with an error whose code is AUTH_FAILED or MISSING_AUTH when the platform refuses the credential; reconnection is disabled for the client’s lifetime. Over MQTT it rejects with the broker’s Not authorized error. Other failures are retried, not thrown.

onCommand()

Registers the handler for one command name. Registering the same name again replaces the handler.
string
required
Command name, as declared in commands or in defineDevice.
CommandHandler
required
Receives the payload; its resolved value is sent as data.
Returns — nothing. The handler’s result is sent as { commandId, success: true, data }; a thrown error is sent as { commandId, success: false, error: message }; an unregistered command answers Unknown command: <name>.

trigger()

Sends a trigger and waits for the platform’s acknowledgment. Triggers are sent one at a time in call order.
string
required
Trigger name, matched exactly against the handlers on the agent.
any
required
JSON payload; at most 1 MB serialized.
Returns — { triggerId, received }; received is true when the event was queued. Errors — Not connected to device gateway (or Not connected to MQTT broker) before connect() resolves; Trigger '<name>' ACK timeout (10s) when no acknowledgment arrives within 10 seconds. Over Socket.IO a trigger the platform rejects arrives as a trigger_error event, not as a rejection; the MQTT transport doesn’t subscribe to trigger_error, so a rejected trigger times out instead.

onTriggerResult()

Registers a handler for a trigger_result message. The platform doesn’t send that message, so the handler never runs.

disconnect()

Closes the connection and stops reconnection. Over MQTT it first publishes a retained offline status.

isConnected()

Returns — true between connected and the next disconnected.

Events

DeviceClient extends EventEmitter.

Connection behavior

  • Heartbeat every 30 seconds while connected.
  • Socket.IO reconnects with a jittered delay from 1 to 30 seconds, without limit; after a disconnect the platform initiated, for example AGENT_FORBIDDEN or DEVICE_DISABLED, it retries every second, without limit. AUTH_FAILED and MISSING_AUTH stop reconnection for good. RATE_LIMITED pauses for the platform’s retryAfterMs, clamped between 1 second and 15 minutes, or 30 seconds when none is given.
  • MQTT reconnects every 5 seconds and uses a persistent session, so commands published while the client was away are delivered on reconnect.
  • While a self-describing device is disconnected its command tools are removed from the agent on the next turn and return with the next handshake. The platform keeps the connect-time command list for 24 hours only, so a device connected longer than that loses its command tools until you call disconnect() and then connect(); the client has no timer for this.
  • Command IDs are cached for 5 minutes (1,000 entries); a redelivered command gets the cached response without running the handler.
  • SIGTERM and SIGINT call disconnect() and log [lua-device] Shutting down device '<name>'...; the process exits when nothing else keeps it alive.

MqttTransport

The MQTT implementation DeviceClient uses when transport is 'mqtt'; usable on its own with the same DeviceClientConfig.
It connects with client ID lua-<agentId>-<deviceName>, username <agentId>:<deviceName>, the credential as password, keep-alive 60 seconds, a persistent session, and a retained last-will offline status. Methods connect(), onCommand(), trigger(), onTriggerResult(), disconnect(), and isConnected() behave as on DeviceClient; it emits the same events except trigger_error, whose topic it doesn’t subscribe to. Topics are listed in the MQTT protocol.

CDN

device.cdn is a CDN instance authenticated with the device’s credential, which must grant assets.upload.

upload()

Buffer | Blob
required
File contents; at most 100 MB.
string
required
Name sent with the multipart upload.
string
default:"application/octet-stream"
MIME type for a Buffer; a Blob keeps its own type.
Returns — { fileId, mediaType, extension, url }; url is <cdnUrl>/<fileId>. Errors — Error with the platform’s message, or CDN upload failed: <status>.

getUrl()

download()

Errors — CDN download failed: <status>.

lua-device runner

The package installs a lua-device binary that connects a device without a script. It sends no command list, so pair it with a defineDevice declaration pushed to the agent.
It logs Registered command handler: <name> per handler, Connecting to <url>..., then Connected as '<name>' to agent '<id>' and Listening for commands... (Ctrl+C to stop). A refused connection logs Connection failed: <message> and exits 1.
handlers.cjs

Types

All exported from the package.
interface
The constructor options listed under DeviceClient.
interface
{ name; description; inputSchema?; timeoutMs?; retry? }.
interface
{ commandId; command; payload; timeout? } as delivered to the device. timeout is not sent by the platform.
interface
{ commandId; success; data?; error? } as sent back.
interface
{ triggerId; received; error? }.
interface
{ fileId; mediaType; extension; url }.
type
(payload: any) => Promise<any>.
type
(result: any) => void.

See also