npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@devana/ws-tools

v1.0.6

Published

WebSocket client library for Devana Dynamic Tools - Connect any app to Devana AI agents

Downloads

40

Readme

@devana/ws-tools

npm version License: MIT

Bibliothèque JavaScript/TypeScript pour connecter n'importe quelle application à Devana AI via WebSocket et exposer des tools dynamiques que l'agent IA peut utiliser en temps réel.

🌟 Qu'est-ce que @devana/ws-tools ?

Cette bibliothèque permet de :

  • Connecter votre application à Devana AI via WebSocket
  • Exposer des fonctionnalités de votre app comme des "tools" pour l'IA
  • Permettre à l'agent IA d'interagir avec votre application en temps réel
  • Supporter multi-sessions et multi-documents avec un seul client

🚀 Installation

npm install @devana/ws-tools
# ou
yarn add @devana/ws-tools
# ou
pnpm add @devana/ws-tools

Pour Node.js, installez également :

npm install ws

📝 Guide de Démarrage Rapide

1. Créer un Client WebSocket

import { DevanaWSClient } from '@devana/ws-tools';

const client = new DevanaWSClient({
  apiUrl: 'https://api.devana.ai',
  clientType: 'my-app',              // Type de votre application
  documentName: 'Document Principal', // Optionnel : nom du document
  bearerToken: 'eyJhbGciOi...',      // Optionnel : Token JWT (le serveur extrait automatiquement le userId)
  metadata: {                        // Optionnel : métadonnées
    version: '1.0.0',
    platform: 'web'
  },
  onConnected: (sessionId) => {
    console.log('✅ Connecté avec session:', sessionId);
  },
  onDisconnected: () => {
    console.log('❌ Déconnecté');
  },
  onError: (error) => {
    console.error('⚠️ Erreur:', error);
  }
});

2. Enregistrer des Tools (Fonctionnalités)

Les tools sont des fonctionnalités que vous exposez à l'agent IA :

// Tool simple
client.registerTool('get_current_time', {
  description: 'Obtenir l\'heure actuelle',
  schema: {
    type: 'object',
    properties: {},
    required: []
  },
  handler: async () => {
    return new Date().toLocaleTimeString();
  }
});

// Tool avec paramètres
client.registerTool('create_document', {
  description: 'Créer un nouveau document',
  schema: {
    type: 'object',
    properties: {
      title: {
        type: 'string',
        description: 'Titre du document'
      },
      content: {
        type: 'string',
        description: 'Contenu initial'
      },
      format: {
        type: 'string',
        enum: ['txt', 'md', 'html', 'json'],
        description: 'Format du document'
      }
    },
    required: ['title', 'content']
  },
  handler: async (data) => {
    // Votre logique métier
    const doc = await createDocumentInYourApp(data);
    return `Document "${data.title}" créé avec succès`;
  }
});

// Enregistrer plusieurs tools d'un coup
client.registerTools([
  {
    name: 'save_file',
    description: 'Sauvegarder un fichier',
    schema: { /* ... */ },
    handler: async (data) => { /* ... */ }
  },
  {
    name: 'delete_file',
    description: 'Supprimer un fichier',
    schema: { /* ... */ },
    handler: async (data) => { /* ... */ }
  }
]);

3. Se Connecter au Serveur

// Connexion asynchrone
await client.connect();

// Vérifier la connexion
if (client.isConnected()) {
  console.log('Session ID:', client.getSessionId());
  console.log('Document:', client.getDocumentName());
}

4. Utiliser avec l'API Devana

import { callDevanaWithTools } from '@devana/ws-tools';

// Envoyer une requête au LLM avec vos tools
const response = await callDevanaWithTools(
  'https://api.devana.ai',
  'votre-api-key',
  {
    agentId: 'devana',                    // ID de l'agent
    message: 'Crée un document test.md',  // Message utilisateur
    sessionId: client.getSessionId()!,    // Session WebSocket
    tools: client.getToolsConfig(),       // Vos tools
    conversationId: 'conv-123',           // Optionnel
    stream: false                         // Optionnel
  }
);

// L'agent utilisera automatiquement vos tools pour accomplir la tâche !
const result = await response.json();
console.log(result);

🎨 Exemples Complets

Exemple 1 : Microsoft Word Add-in

import { DevanaWSClient } from '@devana/ws-tools';

class WordAddIn {
  private client: DevanaWSClient;

  constructor() {
    this.client = new DevanaWSClient({
      apiUrl: 'https://api.devana.ai',
      clientType: 'word',
      documentName: this.getDocumentName(),
      onConnected: (sessionId) => {
        this.updateUI('connected', sessionId);
      }
    });

    this.registerWordTools();
  }

  private getDocumentName(): string {
    // Obtenir le nom du document Word actuel
    return Office.context.document.url || 'Sans titre';
  }

  private registerWordTools() {
    // Ajouter du texte avec style
    this.client.registerTool('add_paragraph', {
      description: 'Ajouter un paragraphe avec un style spécifique',
      schema: {
        type: 'object',
        properties: {
          text: {
            type: 'string',
            description: 'Texte du paragraphe'
          },
          style: {
            type: 'string',
            enum: ['Title', 'Heading1', 'Heading2', 'Heading3', 'Normal', 'Quote'],
            description: 'Style du paragraphe'
          },
          alignment: {
            type: 'string',
            enum: ['Left', 'Center', 'Right', 'Justified'],
            description: 'Alignement du texte'
          }
        },
        required: ['text', 'style']
      },
      handler: async (data) => {
        return await Word.run(async (context) => {
          const body = context.document.body;
          const paragraph = body.insertParagraph(
            data.text,
            Word.InsertLocation.end
          );

          paragraph.style = data.style;
          if (data.alignment) {
            paragraph.alignment = data.alignment;
          }

          await context.sync();
          return `Paragraphe ajouté avec le style ${data.style}`;
        });
      }
    });

    // Insérer un tableau
    this.client.registerTool('insert_table', {
      description: 'Insérer un tableau dans le document',
      schema: {
        type: 'object',
        properties: {
          rows: {
            type: 'number',
            description: 'Nombre de lignes',
            minimum: 1,
            maximum: 100
          },
          columns: {
            type: 'number',
            description: 'Nombre de colonnes',
            minimum: 1,
            maximum: 20
          },
          data: {
            type: 'array',
            description: 'Données du tableau (optionnel)',
            items: {
              type: 'array',
              items: { type: 'string' }
            }
          }
        },
        required: ['rows', 'columns']
      },
      handler: async (data) => {
        return await Word.run(async (context) => {
          const body = context.document.body;
          const table = body.insertTable(
            data.rows,
            data.columns,
            Word.InsertLocation.end
          );

          // Remplir avec les données si fournies
          if (data.data) {
            for (let i = 0; i < data.data.length && i < data.rows; i++) {
              for (let j = 0; j < data.data[i].length && j < data.columns; j++) {
                table.getCell(i, j).value = data.data[i][j];
              }
            }
          }

          await context.sync();
          return `Tableau ${data.rows}x${data.columns} inséré`;
        });
      }
    });

    // Obtenir les statistiques du document
    this.client.registerTool('get_document_stats', {
      description: 'Obtenir les statistiques du document',
      schema: {
        type: 'object',
        properties: {},
        required: []
      },
      handler: async () => {
        return await Word.run(async (context) => {
          const body = context.document.body;
          const paragraphs = body.paragraphs;
          const tables = body.tables;

          body.load('text');
          paragraphs.load('items');
          tables.load('items');

          await context.sync();

          const text = body.text;
          const words = text.trim().split(/\s+/).filter(w => w.length > 0);
          const characters = text.length;

          return {
            paragraphs: paragraphs.items.length,
            tables: tables.items.length,
            words: words.length,
            characters: characters,
            charactersNoSpaces: text.replace(/\s/g, '').length
          };
        });
      }
    });
  }

  async connect() {
    await this.client.connect();
  }

  getClient() {
    return this.client;
  }
}

// Utilisation
Office.onReady(async () => {
  const wordAddIn = new WordAddIn();
  await wordAddIn.connect();

  // Maintenant l'agent IA peut contrôler Word !
});

Exemple 2 : Application IoT / Domotique

import { DevanaWSClient } from '@devana/ws-tools';

class SmartHomeClient {
  private client: DevanaWSClient;
  private devices: Map<string, any> = new Map();

  constructor() {
    this.client = new DevanaWSClient({
      apiUrl: 'https://api.devana.ai',
      clientType: 'smart-home',
      bearerToken: 'eyJhbGciOi...',
      metadata: {
        location: 'Maison principale',
        rooms: ['salon', 'cuisine', 'chambres']
      }
    });

    this.registerSmartHomeTools();
  }

  private registerSmartHomeTools() {
    // Contrôle des lumières
    this.client.registerTool('control_lights', {
      description: 'Contrôler les lumières de la maison',
      schema: {
        type: 'object',
        properties: {
          room: {
            type: 'string',
            enum: ['salon', 'cuisine', 'chambre1', 'chambre2', 'all'],
            description: 'Pièce cible'
          },
          action: {
            type: 'string',
            enum: ['on', 'off', 'dim'],
            description: 'Action à effectuer'
          },
          brightness: {
            type: 'number',
            minimum: 0,
            maximum: 100,
            description: 'Niveau de luminosité (pour dim)'
          }
        },
        required: ['room', 'action']
      },
      handler: async (data) => {
        // Logique de contrôle des lumières
        const devices = data.room === 'all'
          ? this.getAllLights()
          : this.getLightsByRoom(data.room);

        for (const device of devices) {
          switch (data.action) {
            case 'on':
              await device.turnOn();
              break;
            case 'off':
              await device.turnOff();
              break;
            case 'dim':
              await device.setBrightness(data.brightness || 50);
              break;
          }
        }

        return `Lumières ${data.room}: ${data.action}`;
      }
    });

    // Thermostat
    this.client.registerTool('set_temperature', {
      description: 'Régler la température',
      schema: {
        type: 'object',
        properties: {
          zone: {
            type: 'string',
            enum: ['salon', 'chambres', 'global'],
            description: 'Zone de température'
          },
          temperature: {
            type: 'number',
            minimum: 15,
            maximum: 30,
            description: 'Température en Celsius'
          },
          mode: {
            type: 'string',
            enum: ['heat', 'cool', 'auto'],
            description: 'Mode de climatisation'
          }
        },
        required: ['zone', 'temperature']
      },
      handler: async (data) => {
        const thermostat = this.getThermostat(data.zone);
        await thermostat.setTemperature(data.temperature);
        if (data.mode) {
          await thermostat.setMode(data.mode);
        }
        return `Température ${data.zone} réglée à ${data.temperature}°C`;
      }
    });

    // Sécurité
    this.client.registerTool('security_control', {
      description: 'Contrôler le système de sécurité',
      schema: {
        type: 'object',
        properties: {
          action: {
            type: 'string',
            enum: ['arm', 'disarm', 'status', 'lock_doors', 'unlock_doors'],
            description: 'Action de sécurité'
          },
          code: {
            type: 'string',
            description: 'Code de sécurité (pour arm/disarm)'
          }
        },
        required: ['action']
      },
      handler: async (data) => {
        switch (data.action) {
          case 'arm':
            return await this.armSecuritySystem(data.code);
          case 'disarm':
            return await this.disarmSecuritySystem(data.code);
          case 'status':
            return await this.getSecurityStatus();
          case 'lock_doors':
            return await this.lockAllDoors();
          case 'unlock_doors':
            return await this.unlockDoors(data.code);
        }
      }
    });

    // Scénarios
    this.client.registerTool('run_scenario', {
      description: 'Exécuter un scénario prédéfini',
      schema: {
        type: 'object',
        properties: {
          scenario: {
            type: 'string',
            enum: ['morning', 'night', 'away', 'movie', 'party'],
            description: 'Scénario à exécuter'
          }
        },
        required: ['scenario']
      },
      handler: async (data) => {
        switch (data.scenario) {
          case 'morning':
            await this.runMorningRoutine();
            break;
          case 'night':
            await this.runNightRoutine();
            break;
          case 'away':
            await this.runAwayMode();
            break;
          case 'movie':
            await this.runMovieMode();
            break;
          case 'party':
            await this.runPartyMode();
            break;
        }
        return `Scénario "${data.scenario}" activé`;
      }
    });
  }

  // Méthodes helper...
  private async runMorningRoutine() {
    // Ouvrir les volets
    // Allumer lumières cuisine
    // Régler température à 21°C
    // Lancer machine à café
  }

  // ... autres méthodes
}

Exemple 3 : Application de Dessin/Design

import { DevanaWSClient } from '@devana/ws-tools';

class DesignAppClient {
  private client: DevanaWSClient;
  private canvas: any;

  constructor(canvas: any) {
    this.canvas = canvas;
    this.client = new DevanaWSClient({
      apiUrl: 'https://api.devana.ai',
      clientType: 'design-app',
      documentName: canvas.projectName
    });

    this.registerDesignTools();
  }

  private registerDesignTools() {
    // Créer des formes
    this.client.registerTool('create_shape', {
      description: 'Créer une forme géométrique',
      schema: {
        type: 'object',
        properties: {
          type: {
            type: 'string',
            enum: ['rectangle', 'circle', 'triangle', 'polygon', 'star'],
            description: 'Type de forme'
          },
          x: { type: 'number', description: 'Position X' },
          y: { type: 'number', description: 'Position Y' },
          width: { type: 'number', description: 'Largeur' },
          height: { type: 'number', description: 'Hauteur' },
          fill: { type: 'string', description: 'Couleur de remplissage' },
          stroke: { type: 'string', description: 'Couleur de bordure' },
          strokeWidth: { type: 'number', description: 'Épaisseur de bordure' }
        },
        required: ['type', 'x', 'y', 'width', 'height']
      },
      handler: async (data) => {
        const shape = this.canvas.createShape(data);
        return `Forme ${data.type} créée avec ID: ${shape.id}`;
      }
    });

    // Ajouter du texte
    this.client.registerTool('add_text', {
      description: 'Ajouter du texte sur le canvas',
      schema: {
        type: 'object',
        properties: {
          text: { type: 'string', description: 'Texte à afficher' },
          x: { type: 'number', description: 'Position X' },
          y: { type: 'number', description: 'Position Y' },
          font: { type: 'string', description: 'Police' },
          size: { type: 'number', description: 'Taille de police' },
          color: { type: 'string', description: 'Couleur' },
          bold: { type: 'boolean', description: 'Gras' },
          italic: { type: 'boolean', description: 'Italique' }
        },
        required: ['text', 'x', 'y']
      },
      handler: async (data) => {
        const textObj = this.canvas.addText(data);
        return `Texte ajouté: "${data.text}"`;
      }
    });

    // Appliquer des filtres
    this.client.registerTool('apply_filter', {
      description: 'Appliquer un filtre à un élément ou au canvas',
      schema: {
        type: 'object',
        properties: {
          target: {
            type: 'string',
            description: 'ID de l\'élément ou "canvas" pour tout'
          },
          filter: {
            type: 'string',
            enum: ['blur', 'brightness', 'contrast', 'grayscale', 'sepia', 'saturate'],
            description: 'Type de filtre'
          },
          value: {
            type: 'number',
            description: 'Valeur du filtre (0-100)'
          }
        },
        required: ['filter', 'value']
      },
      handler: async (data) => {
        if (data.target === 'canvas') {
          this.canvas.applyGlobalFilter(data.filter, data.value);
        } else {
          const element = this.canvas.getElementById(data.target);
          element.applyFilter(data.filter, data.value);
        }
        return `Filtre ${data.filter} appliqué`;
      }
    });
  }
}

📊 Architecture Multi-Sessions

Le système supporte plusieurs sessions simultanées pour un même utilisateur :

// Client 1 : Document Word
const wordClient = new DevanaWSClient({
  apiUrl: 'https://api.devana.ai',
  clientType: 'word',
  documentName: 'Rapport Q4 2024',
  bearerToken: 'eyJhbGciOi...'
});

// Client 2 : Document Excel
const excelClient = new DevanaWSClient({
  apiUrl: 'https://api.devana.ai',
  clientType: 'excel',
  documentName: 'Budget 2024',
  bearerToken: 'eyJhbGciOi...'  // Même utilisateur
});

// Client 3 : Application IoT
const iotClient = new DevanaWSClient({
  apiUrl: 'https://api.devana.ai',
  clientType: 'smart-home',
  bearerToken: 'eyJhbGciOi...'  // Même utilisateur
});

// L'agent IA peut interagir avec les 3 en même temps !

🔧 Configuration Avancée

Options de Reconnexion

const client = new DevanaWSClient({
  apiUrl: 'https://api.devana.ai',
  clientType: 'my-app',

  // Reconnexion automatique
  autoReconnect: true,              // Par défaut: true
  maxReconnectAttempts: 10,         // Par défaut: 5
  reconnectDelay: 5000,             // Par défaut: 3000ms

  // Callbacks
  onReconnected: (sessionId) => {
    console.log('Reconnecté avec nouvelle session:', sessionId);
    // Réenregistrer l'état si nécessaire
  }
});

Gestion des Erreurs

client.registerTool('risky_operation', {
  description: 'Opération qui peut échouer',
  schema: { /* ... */ },
  handler: async (data) => {
    try {
      // Opération risquée
      const result = await dangerousOperation(data);
      return result;
    } catch (error) {
      // L'erreur sera transmise à l'agent IA
      throw new Error(`Échec de l'opération: ${error.message}`);
    }
  }
});

Heartbeat / Keep-Alive

La bibliothèque gère automatiquement un heartbeat (ping/pong) toutes les 30 secondes pour maintenir la connexion active.

📚 API Reference Complète

DevanaWSClient

Constructor Options

| Option | Type | Description | Default | |--------|------|-------------|---------| | apiUrl | string | URL de l'API Devana | Required | | clientType | string | Type de client (word, excel, custom...) | Required | | documentName | string | Nom du document ou contexte | undefined | | bearerToken | string | Token JWT pour authentification (le serveur extrait automatiquement le userId) | undefined | | metadata | object | Métadonnées supplémentaires | {} | | autoReconnect | boolean | Reconnexion automatique si déconnecté | true | | maxReconnectAttempts | number | Nombre max de tentatives | 5 | | reconnectDelay | number | Délai entre tentatives (ms) | 3000 | | onConnected | (sessionId: string) => void | Callback connexion réussie | () => {} | | onDisconnected | () => void | Callback déconnexion | () => {} | | onReconnected | (sessionId: string) => void | Callback reconnexion | () => {} | | onError | (error: Error) => void | Callback erreur | () => {} | | onSessionsChanged | () => void | Callback changement de sessions | undefined |

Methods

registerTool(name: string, config: ToolDefinition)

Enregistre un tool avec son handler.

client.registerTool('my_tool', {
  description: 'Description du tool',
  schema: { /* JSON Schema */ },
  handler: async (data) => { /* ... */ }
});
registerTools(tools: ToolDefinition[])

Enregistre plusieurs tools en une fois.

getToolsConfig(): ToolConfig[]

Retourne la configuration des tools (sans les handlers) pour envoyer au serveur.

connect(): Promise<string>

Se connecte au serveur WebSocket. Retourne le sessionId.

getSessionId(): string | null

Retourne l'ID de session actuel.

getDocumentName(): string | undefined

Retourne le nom du document associé.

isConnected(): boolean

Vérifie si le client est connecté.

disconnect(): void

Déconnecte proprement le client.

callDevanaWithTools()

Helper pour envoyer une requête à l'API Devana avec support des tools dynamiques.

async function callDevanaWithTools(
  apiUrl: string,
  apiKey: string,
  options: {
    agentId: string;          // ID de l'agent IA
    message: string;          // Message utilisateur
    sessionId: string;        // Session WebSocket
    tools: ToolConfig[];      // Configuration des tools
    conversationId?: string;  // ID de conversation (optionnel)
    stream?: boolean;         // Mode streaming (optionnel)
  }
): Promise<Response>

Types TypeScript

interface ToolSchema {
  type: "object";
  properties: Record<string, {
    type: string;
    description?: string;
    enum?: string[];
    minimum?: number;
    maximum?: number;
    items?: any;
  }>;
  required?: string[];
}

interface ToolConfig {
  name: string;
  description: string;
  schema: ToolSchema;
}

interface ToolDefinition extends ToolConfig {
  handler: (data: Record<string, any>) => Promise<any>;
}

interface DevanaWSClientOptions {
  apiUrl: string;
  clientType: string;
  documentName?: string;
  bearerToken?: string; // Token JWT pour authentification
  metadata?: Record<string, any>;
  autoReconnect?: boolean;
  maxReconnectAttempts?: number;
  reconnectDelay?: number;
  onConnected?: (sessionId: string) => void;
  onDisconnected?: () => void;
  onReconnected?: (sessionId: string) => void;
  onError?: (error: Error) => void;
  onSessionsChanged?: () => void;
}

🔒 Sécurité

Authentification

  • Les sessions WebSocket sont temporaires et uniques
  • Chaque session a un ID unique généré côté client
  • Le serveur valide les permissions via JWT

Isolation

  • Chaque session est isolée
  • Pas d'accès cross-session
  • Les tools sont limités à leur session

Validation

  • Les schémas sont validés côté serveur avec Zod
  • Timeout de 30 secondes par exécution de tool
  • Rate limiting sur les appels

🐛 Debugging

Activer les logs détaillés :

// Les logs sont automatiquement affichés dans la console
// Format: [DevanaWSClient] Message

// Exemples de logs:
// [DevanaWSClient] ✅ Registered
// [DevanaWSClient] 📥 add_paragraph
// [DevanaWSClient] ▶ add_paragraph
// [DevanaWSClient] ✅ add_paragraph done
// [DevanaWSClient] 📤 Response sent (OK)

🚀 Déploiement

Browser (Web)

<script src="https://unpkg.com/@devana/ws-tools"></script>
<script>
  const client = new DevanaWSTools.DevanaWSClient({
    apiUrl: 'https://api.devana.ai',
    clientType: 'web-app'
  });
</script>

Node.js

import { DevanaWSClient } from '@devana/ws-tools';
// ou
const { DevanaWSClient } = require('@devana/ws-tools');

React/Vue/Angular

// Dans un composant React
import { useEffect, useState } from 'react';
import { DevanaWSClient } from '@devana/ws-tools';

function MyComponent() {
  const [client, setClient] = useState<DevanaWSClient | null>(null);

  useEffect(() => {
    const wsClient = new DevanaWSClient({
      apiUrl: process.env.REACT_APP_DEVANA_API,
      clientType: 'react-app'
    });

    wsClient.registerTool(/* ... */);
    wsClient.connect();

    setClient(wsClient);

    return () => {
      wsClient.disconnect();
    };
  }, []);

  // ...
}

📖 Documentation & Ressources

🤝 Support & Communauté

🔄 Changelog

v1.0.0 (2024-01)

  • Version initiale
  • Support WebSocket avec auto-reconnect
  • Système de tools dynamiques
  • Support multi-sessions
  • Heartbeat automatique
  • Support Browser et Node.js

📄 License

MIT © Devana.ai


Fait avec ❤️ par l'équipe Devana.ai