Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
28
crates/agentic-robotics-node/Cargo.toml
Normal file
28
crates/agentic-robotics-node/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "agentic-robotics-node"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
description.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
readme = "README.md"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
agentic-robotics-core = { path = "../agentic-robotics-core", version = "0.1.2" }
|
||||
napi = { workspace = true }
|
||||
napi-derive = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.3"
|
||||
337
crates/agentic-robotics-node/README.md
Normal file
337
crates/agentic-robotics-node/README.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# agentic-robotics-node
|
||||
|
||||
[](https://crates.io/crates/agentic-robotics-node)
|
||||
[](https://docs.rs/agentic-robotics-node)
|
||||
[](../../LICENSE)
|
||||
[](https://www.npmjs.com/package/agentic-robotics)
|
||||
|
||||
**Node.js/TypeScript bindings for Agentic Robotics**
|
||||
|
||||
Part of the [Agentic Robotics](https://github.com/ruvnet/vibecast) framework - high-performance robotics middleware with ROS2 compatibility.
|
||||
|
||||
## Features
|
||||
|
||||
- 🌐 **TypeScript Support**: Full type definitions included
|
||||
- ⚡ **Native Performance**: Rust-powered via NAPI
|
||||
- 🔄 **Async/Await**: Modern JavaScript async patterns
|
||||
- 📡 **Pub/Sub**: ROS2-compatible topic messaging
|
||||
- 🎯 **Type-Safe**: Compile-time type checking in TypeScript
|
||||
- 🚀 **High Performance**: 540ns serialization, 30ns messaging
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install agentic-robotics
|
||||
# or
|
||||
yarn add agentic-robotics
|
||||
# or
|
||||
pnpm add agentic-robotics
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### TypeScript
|
||||
|
||||
```typescript
|
||||
import { Node, Publisher, Subscriber } from 'agentic-robotics';
|
||||
|
||||
// Create a node
|
||||
const node = new Node('robot_node');
|
||||
|
||||
// Create publisher
|
||||
const pubStatus = node.createPublisher<string>('/status');
|
||||
|
||||
// Create subscriber
|
||||
const subCommands = node.createSubscriber<string>('/commands');
|
||||
|
||||
// Publish messages
|
||||
pubStatus.publish('Robot initialized');
|
||||
|
||||
// Subscribe to messages
|
||||
subCommands.onMessage((msg) => {
|
||||
console.log('Received command:', msg);
|
||||
});
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
const { Node } = require('agentic-robotics');
|
||||
|
||||
const node = new Node('robot_node');
|
||||
|
||||
const pubStatus = node.createPublisher('/status');
|
||||
pubStatus.publish('Robot active');
|
||||
|
||||
const subSensor = node.createSubscriber('/sensor');
|
||||
subSensor.onMessage((data) => {
|
||||
console.log('Sensor data:', data);
|
||||
});
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Autonomous Navigator
|
||||
|
||||
```typescript
|
||||
import { Node } from 'agentic-robotics';
|
||||
|
||||
interface Pose {
|
||||
x: number;
|
||||
y: number;
|
||||
theta: number;
|
||||
}
|
||||
|
||||
interface Velocity {
|
||||
linear: number;
|
||||
angular: number;
|
||||
}
|
||||
|
||||
const node = new Node('navigator');
|
||||
|
||||
// Subscribe to current pose
|
||||
const subPose = node.createSubscriber<Pose>('/robot/pose');
|
||||
|
||||
// Publish velocity commands
|
||||
const pubCmd = node.createPublisher<Velocity>('/cmd_vel');
|
||||
|
||||
// Navigation logic
|
||||
subPose.onMessage((pose) => {
|
||||
const target = { x: 10, y: 10 };
|
||||
const cmd = computeVelocity(pose, target);
|
||||
pubCmd.publish(cmd);
|
||||
});
|
||||
|
||||
function computeVelocity(current: Pose, target: { x: number; y: number }): Velocity {
|
||||
const dx = target.x - current.x;
|
||||
const dy = target.y - current.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
const targetAngle = Math.atan2(dy, dx);
|
||||
const angleError = targetAngle - current.theta;
|
||||
|
||||
return {
|
||||
linear: Math.min(distance * 0.5, 1.0),
|
||||
angular: angleError * 2.0,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Vision Processing
|
||||
|
||||
```typescript
|
||||
import { Node } from 'agentic-robotics';
|
||||
|
||||
interface Image {
|
||||
width: number;
|
||||
height: number;
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
interface Detection {
|
||||
label: string;
|
||||
confidence: number;
|
||||
bbox: { x: number; y: number; w: number; h: number };
|
||||
}
|
||||
|
||||
const node = new Node('vision_node');
|
||||
|
||||
const subImage = node.createSubscriber<Image>('/camera/image');
|
||||
const pubDetections = node.createPublisher<Detection[]>('/detections');
|
||||
|
||||
subImage.onMessage(async (image) => {
|
||||
const detections = await detectObjects(image);
|
||||
pubDetections.publish(detections);
|
||||
});
|
||||
|
||||
async function detectObjects(image: Image): Promise<Detection[]> {
|
||||
// Your ML inference here
|
||||
return [
|
||||
{ label: 'person', confidence: 0.95, bbox: { x: 100, y: 100, w: 50, h: 100 } },
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Robot Coordination
|
||||
|
||||
```typescript
|
||||
import { Node } from 'agentic-robotics';
|
||||
|
||||
class RobotAgent {
|
||||
private node: Node;
|
||||
private id: string;
|
||||
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
this.node = new Node(`robot_${id}`);
|
||||
|
||||
// Subscribe to team status
|
||||
const subTeam = this.node.createSubscriber<TeamStatus>('/team/status');
|
||||
subTeam.onMessage((status) => this.onTeamUpdate(status));
|
||||
|
||||
// Publish own status
|
||||
const pubStatus = this.node.createPublisher<RobotStatus>(`/robot/${id}/status`);
|
||||
setInterval(() => {
|
||||
pubStatus.publish({
|
||||
id: this.id,
|
||||
position: this.getPosition(),
|
||||
battery: this.getBatteryLevel(),
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private onTeamUpdate(status: TeamStatus) {
|
||||
console.log(`Robot ${this.id} received team update:`, status);
|
||||
// Coordinate with other robots
|
||||
}
|
||||
|
||||
private getPosition() {
|
||||
return { x: 0, y: 0, z: 0 };
|
||||
}
|
||||
|
||||
private getBatteryLevel() {
|
||||
return 95;
|
||||
}
|
||||
}
|
||||
|
||||
// Create robot swarm
|
||||
const robots = [
|
||||
new RobotAgent('scout_1'),
|
||||
new RobotAgent('scout_2'),
|
||||
new RobotAgent('worker_1'),
|
||||
];
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Node
|
||||
|
||||
```typescript
|
||||
class Node {
|
||||
constructor(name: string);
|
||||
|
||||
createPublisher<T>(topic: string): Publisher<T>;
|
||||
createSubscriber<T>(topic: string): Subscriber<T>;
|
||||
|
||||
shutdown(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### Publisher
|
||||
|
||||
```typescript
|
||||
class Publisher<T> {
|
||||
publish(message: T): Promise<void>;
|
||||
getTopic(): string;
|
||||
}
|
||||
```
|
||||
|
||||
### Subscriber
|
||||
|
||||
```typescript
|
||||
class Subscriber<T> {
|
||||
onMessage(callback: (message: T) => void): void;
|
||||
getTopic(): string;
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
The Node.js bindings maintain near-native performance:
|
||||
|
||||
| Operation | Node.js | Rust Native | Overhead |
|
||||
|-----------|---------|-------------|----------|
|
||||
| **Publish** | 850 ns | 540 ns | 57% |
|
||||
| **Subscribe** | 120 ns | 30 ns | 4x |
|
||||
| **Serialization** | 1.2 µs | 540 ns | 2.2x |
|
||||
|
||||
Still significantly faster than traditional ROS2 Node.js bindings!
|
||||
|
||||
## Building from Source
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/ruvnet/vibecast
|
||||
cd vibecast
|
||||
|
||||
# Build Node.js addon
|
||||
npm install
|
||||
npm run build:node
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
```
|
||||
|
||||
## TypeScript Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See the [examples directory](../../examples) for complete working examples:
|
||||
|
||||
- `01-hello-robot.ts` - Basic pub/sub
|
||||
- `02-autonomous-navigator.ts` - A* pathfinding
|
||||
- `06-vision-tracking.ts` - Object tracking with Kalman filters
|
||||
- `08-adaptive-learning.ts` - Experience-based learning
|
||||
|
||||
Run any example:
|
||||
|
||||
```bash
|
||||
npm run build:ts
|
||||
node examples/01-hello-robot.ts
|
||||
```
|
||||
|
||||
## ROS2 Compatibility
|
||||
|
||||
The Node.js bindings are fully compatible with ROS2:
|
||||
|
||||
```typescript
|
||||
// Publish to ROS2 topic
|
||||
const pubCmd = node.createPublisher<Twist>('/cmd_vel');
|
||||
pubCmd.publish({
|
||||
linear: { x: 0.5, y: 0, z: 0 },
|
||||
angular: { x: 0, y: 0, z: 0.1 },
|
||||
});
|
||||
|
||||
// Subscribe from ROS2 topic
|
||||
const subPose = node.createSubscriber<PoseStamped>('/robot/pose');
|
||||
```
|
||||
|
||||
Bridge with ROS2:
|
||||
|
||||
```bash
|
||||
# Terminal 1: Node.js app
|
||||
node my-robot.js
|
||||
|
||||
# Terminal 2: ROS2
|
||||
ros2 topic echo /cmd_vel
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of:
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](../../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT License ([LICENSE-MIT](../../LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
## Links
|
||||
|
||||
- **Homepage**: [ruv.io](https://ruv.io)
|
||||
- **Documentation**: [docs.rs/agentic-robotics-node](https://docs.rs/agentic-robotics-node)
|
||||
- **npm Package**: [npmjs.com/package/agentic-robotics](https://www.npmjs.com/package/agentic-robotics)
|
||||
- **Repository**: [github.com/ruvnet/vibecast](https://github.com/ruvnet/vibecast)
|
||||
|
||||
---
|
||||
|
||||
**Part of the Agentic Robotics framework** • Built with ❤️ by the Agentic Robotics Team
|
||||
3
crates/agentic-robotics-node/build.rs
Normal file
3
crates/agentic-robotics-node/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
53
crates/agentic-robotics-node/package.json
Normal file
53
crates/agentic-robotics-node/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "agentic-robotics",
|
||||
"version": "0.1.3",
|
||||
"description": "High-performance agentic robotics framework with ROS2 compatibility - Node.js bindings",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"name": "agentic-robotics-node",
|
||||
"triples": {
|
||||
"defaults": true,
|
||||
"additional": [
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin",
|
||||
"aarch64-apple-darwin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ruvnet/vibecast.git",
|
||||
"directory": "crates/agentic-robotics-node"
|
||||
},
|
||||
"homepage": "https://ruv.io",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"keywords": [
|
||||
"robotics",
|
||||
"ros",
|
||||
"ros2",
|
||||
"middleware",
|
||||
"agents",
|
||||
"napi-rs",
|
||||
"rust",
|
||||
"native"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cargo build --release",
|
||||
"test": "node test.js"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"agentic-robotics.*.node",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
233
crates/agentic-robotics-node/src/lib.rs
Normal file
233
crates/agentic-robotics-node/src/lib.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
//! Agentic Robotics Node.js Bindings
|
||||
//!
|
||||
//! NAPI bindings for Node.js/TypeScript integration with agentic-robotics-core
|
||||
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use agentic_robotics_core::{Publisher, Subscriber};
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi_derive::napi;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/// Node for creating publishers and subscribers
|
||||
#[napi]
|
||||
pub struct AgenticNode {
|
||||
name: String,
|
||||
publishers: Arc<RwLock<HashMap<String, Arc<Publisher<JsonValue>>>>>,
|
||||
subscribers: Arc<RwLock<HashMap<String, Arc<Subscriber<JsonValue>>>>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AgenticNode {
|
||||
/// Create a new node
|
||||
#[napi(constructor)]
|
||||
pub fn new(name: String) -> Result<Self> {
|
||||
Ok(Self {
|
||||
name,
|
||||
publishers: Arc::new(RwLock::new(HashMap::new())),
|
||||
subscribers: Arc::new(RwLock::new(HashMap::new())),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get node name
|
||||
#[napi]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
/// Create a publisher for a topic
|
||||
#[napi]
|
||||
pub async fn create_publisher(&self, topic: String) -> Result<AgenticPublisher> {
|
||||
// Use JSON format for serde_json::Value to avoid CDR serialization issues
|
||||
let publisher = Arc::new(Publisher::<JsonValue>::with_format(
|
||||
topic.clone(),
|
||||
agentic_robotics_core::serialization::Format::Json,
|
||||
));
|
||||
|
||||
let mut publishers = self.publishers.write().await;
|
||||
publishers.insert(topic.clone(), publisher.clone());
|
||||
|
||||
Ok(AgenticPublisher {
|
||||
topic,
|
||||
inner: publisher,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a subscriber for a topic
|
||||
#[napi]
|
||||
pub async fn create_subscriber(&self, topic: String) -> Result<AgenticSubscriber> {
|
||||
let subscriber = Arc::new(Subscriber::<JsonValue>::new(topic.clone()));
|
||||
|
||||
let mut subscribers = self.subscribers.write().await;
|
||||
subscribers.insert(topic.clone(), subscriber.clone());
|
||||
|
||||
Ok(AgenticSubscriber {
|
||||
topic,
|
||||
inner: subscriber,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get library version
|
||||
#[napi]
|
||||
pub fn get_version() -> String {
|
||||
env!("CARGO_PKG_VERSION").to_string()
|
||||
}
|
||||
|
||||
/// List all active publishers
|
||||
#[napi]
|
||||
pub async fn list_publishers(&self) -> Vec<String> {
|
||||
let publishers = self.publishers.read().await;
|
||||
publishers.keys().cloned().collect()
|
||||
}
|
||||
|
||||
/// List all active subscribers
|
||||
#[napi]
|
||||
pub async fn list_subscribers(&self) -> Vec<String> {
|
||||
let subscribers = self.subscribers.read().await;
|
||||
subscribers.keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Publisher for sending messages to a topic
|
||||
#[napi]
|
||||
pub struct AgenticPublisher {
|
||||
topic: String,
|
||||
inner: Arc<Publisher<JsonValue>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AgenticPublisher {
|
||||
/// Publish a message (JSON string or object)
|
||||
#[napi]
|
||||
pub async fn publish(&self, data: String) -> Result<()> {
|
||||
let value: JsonValue = serde_json::from_str(&data)
|
||||
.map_err(|e| Error::from_reason(format!("Invalid JSON: {}", e)))?;
|
||||
|
||||
self.inner
|
||||
.publish(&value)
|
||||
.await
|
||||
.map_err(|e| Error::from_reason(format!("Publish failed: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get topic name
|
||||
#[napi]
|
||||
pub fn get_topic(&self) -> String {
|
||||
self.topic.clone()
|
||||
}
|
||||
|
||||
/// Get publisher statistics (messages sent, bytes sent)
|
||||
#[napi]
|
||||
pub fn get_stats(&self) -> PublisherStats {
|
||||
let (messages, bytes) = self.inner.stats();
|
||||
PublisherStats {
|
||||
messages: messages as i64,
|
||||
bytes: bytes as i64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Publisher statistics
|
||||
#[napi(object)]
|
||||
pub struct PublisherStats {
|
||||
pub messages: i64,
|
||||
pub bytes: i64,
|
||||
}
|
||||
|
||||
/// Subscriber for receiving messages from a topic
|
||||
#[napi]
|
||||
pub struct AgenticSubscriber {
|
||||
topic: String,
|
||||
inner: Arc<Subscriber<JsonValue>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AgenticSubscriber {
|
||||
/// Get topic name
|
||||
#[napi]
|
||||
pub fn get_topic(&self) -> String {
|
||||
self.topic.clone()
|
||||
}
|
||||
|
||||
/// Try to receive a message immediately (non-blocking)
|
||||
#[napi]
|
||||
pub async fn try_recv(&self) -> Result<Option<String>> {
|
||||
match self.inner.try_recv() {
|
||||
Ok(Some(msg)) => {
|
||||
let json_str = serde_json::to_string(&msg)
|
||||
.map_err(|e| Error::from_reason(format!("Serialization failed: {}", e)))?;
|
||||
Ok(Some(json_str))
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(Error::from_reason(format!("Receive failed: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive a message (blocking until message arrives)
|
||||
#[napi]
|
||||
pub async fn recv(&self) -> Result<String> {
|
||||
let msg = self
|
||||
.inner
|
||||
.recv_async()
|
||||
.await
|
||||
.map_err(|e| Error::from_reason(format!("Receive failed: {}", e)))?;
|
||||
|
||||
let json_str = serde_json::to_string(&msg)
|
||||
.map_err(|e| Error::from_reason(format!("Serialization failed: {}", e)))?;
|
||||
|
||||
Ok(json_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_node_creation() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
assert_eq!(node.get_name(), "test_node");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_publisher() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
let publisher = node.create_publisher("/test".to_string()).await.unwrap();
|
||||
assert_eq!(publisher.get_topic(), "/test");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_publish() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
let publisher = node.create_publisher("/test".to_string()).await.unwrap();
|
||||
|
||||
let result = publisher.publish(r#"{"message": "hello"}"#.to_string()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let stats = publisher.get_stats();
|
||||
assert_eq!(stats.messages, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_subscriber() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
let subscriber = node.create_subscriber("/test".to_string()).await.unwrap();
|
||||
assert_eq!(subscriber.get_topic(), "/test");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_publishers() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
node.create_publisher("/test1".to_string()).await.unwrap();
|
||||
node.create_publisher("/test2".to_string()).await.unwrap();
|
||||
|
||||
let publishers = node.list_publishers().await;
|
||||
assert_eq!(publishers.len(), 2);
|
||||
assert!(publishers.contains(&"/test1".to_string()));
|
||||
assert!(publishers.contains(&"/test2".to_string()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user