# NOMYO.js — Secure JavaScript Chat Client **OpenAI-compatible secure chat client with end-to-end encryption for NOMYO Inference Endpoints** - All prompts and responses are automatically encrypted and decrypted - Hybrid encryption: AES-256-GCM payload + RSA-OAEP-SHA256 key exchange, 4096-bit keys - Drop-in replacement for OpenAI's ChatCompletion API - Works in both Node.js and browsers ## Quick Start ### Installation ```bash npm install nomyo-js ``` ### Basic Usage (Node.js) ```javascript import { SecureChatCompletion } from 'nomyo-js'; const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY, }); const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [{ role: 'user', content: 'Hello!' }], temperature: 0.7, }); console.log(response.choices[0].message.content); client.dispose(); ``` ### Basic Usage (Browser) ```html ``` ## Documentation Full documentation is in the [`doc/`](doc/) directory: - [Getting Started](doc/getting-started.md) — walkthrough for new users - [API Reference](doc/api-reference.md) — complete constructor options, methods, types, and error classes - [Models](doc/models.md) — available models and selection guide - [Security Guide](doc/security-guide.md) — encryption, memory protection, key management, compliance - [Rate Limits](doc/rate-limits.md) — limits, automatic retry behaviour, batch throttling - [Examples](doc/examples.md) — 12+ code examples for common scenarios - [Troubleshooting](doc/troubleshooting.md) — error reference and debugging tips ## Security Features ### Hybrid Encryption - **Payload encryption**: AES-256-GCM (authenticated encryption) - **Key exchange**: RSA-OAEP-SHA256 - **Key size**: 4096-bit RSA keys - **Scope**: All communication is end-to-end encrypted ### Key Management - **Automatic**: Keys are generated on first use and saved to `keyDir` (default: `client_keys/`). Existing keys are reloaded on subsequent runs. Node.js only. - **Password protection**: Optional AES-encrypted private key files (minimum 8 characters). - **Secure permissions**: Private key files saved at `0600` (owner-only). - **Auto-rotation**: Keys rotate every 24 hours by default (configurable via `keyRotationInterval`). - **Explicit lifecycle**: Call `dispose()` to zero in-memory key material and stop the rotation timer. ### Secure Memory The library wraps all intermediate sensitive buffers (AES keys, plaintext payload, decrypted bytes) in `SecureByteContext`, which zeroes them in a `finally` block immediately after use. Pure JavaScript cannot lock pages to prevent OS swapping (`mlock`). For environments where swap-file exposure is unacceptable, install the optional `nomyo-native` addon. Check the current protection level: ```javascript import { getMemoryProtectionInfo } from 'nomyo-js'; const info = getMemoryProtectionInfo(); // Without addon: { method: 'zero-only', canLock: false } // With addon: { method: 'mlock', canLock: true } ``` ### Security Tiers Pass `security_tier` per request to route inference to increasingly isolated hardware: | Tier | Hardware | Use case | |------|----------|----------| | `"standard"` | GPU | General secure inference | | `"high"` | CPU/GPU balanced | Sensitive business data | | `"maximum"` | CPU only | HIPAA PHI, classified data | ```javascript const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [{ role: 'user', content: 'Patient record summary...' }], security_tier: 'maximum', }); ``` ## Usage Examples ### With API Key ```javascript const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY }); ``` ### Error Handling ```javascript import { SecureChatCompletion, AuthenticationError, RateLimitError, ForbiddenError, } from 'nomyo-js'; try { const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [...] }); } catch (err) { if (err instanceof AuthenticationError) console.error('Check API key:', err.message); else if (err instanceof RateLimitError) console.error('Rate limit hit:', err.message); else if (err instanceof ForbiddenError) console.error('Model/tier mismatch:', err.message); else throw err; } ``` ### Per-Request Router Override Send a single request to a different router without changing the main client: ```javascript const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [{ role: 'user', content: 'Hello from secondary router' }], base_url: 'https://secondary.nomyo.ai:12435', // temporary — main client unchanged }); ``` ### Tool / Function Calling ```javascript const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [{ role: 'user', content: "What's the weather in Paris?" }], tools: [ { type: 'function', function: { name: 'get_weather', description: 'Get weather information for a location', parameters: { type: 'object', properties: { location: { type: 'string' } }, required: ['location'], }, }, }, ], tool_choice: 'auto', }); ``` ### Thinking Models ```javascript const response = await client.create({ model: 'LiquidAI/LFM2.5-1.2B-Thinking', messages: [{ role: 'user', content: 'Is 9.9 larger than 9.11?' }], }); const { content, reasoning_content } = response.choices[0].message; console.log('Reasoning:', reasoning_content); console.log('Answer:', content); ``` ### Resource Management ```javascript const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY }); try { const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [...] }); console.log(response.choices[0].message.content); } finally { client.dispose(); // zeros key material, stops rotation timer } ``` ### Local Development (HTTP) ```javascript const client = new SecureChatCompletion({ baseUrl: 'http://localhost:12435', allowHttp: true, // required — also prints a visible warning }); ``` ## API Reference ### `SecureChatCompletion` — Constructor Options ```typescript new SecureChatCompletion(config?: ChatCompletionConfig) ``` | Option | Type | Default | Description | |--------|------|---------|-------------| | `baseUrl` | `string` | `'https://api.nomyo.ai'` | NOMYO router URL. Must be HTTPS in production. | | `allowHttp` | `boolean` | `false` | Allow HTTP connections. Local development only. | | `apiKey` | `string` | `undefined` | Bearer token for `Authorization` header. | | `secureMemory` | `boolean` | `true` | Zero sensitive buffers immediately after use. | | `timeout` | `number` | `60000` | Request timeout in milliseconds. | | `debug` | `boolean` | `false` | Print verbose logging to the console. | | `keyDir` | `string` | `'client_keys'` | Directory to load/save RSA keys on startup. | | `keyRotationInterval` | `number` | `86400000` | Auto-rotate keys every N ms. `0` disables rotation. | | `keyRotationDir` | `string` | `'client_keys'` | Directory for rotated key files. Node.js only. | | `keyRotationPassword` | `string` | `undefined` | Password for encrypted rotated key files. | | `maxRetries` | `number` | `2` | Extra retry attempts on 429/5xx/network errors. Exponential backoff (1 s, 2 s, …). | #### Methods - `create(request): Promise` — send an encrypted chat completion - `acreate(request): Promise` — alias for `create()` - `dispose(): void` — zero key material and stop the rotation timer #### `create()` Request Fields All standard OpenAI fields (`model`, `messages`, `temperature`, `top_p`, `max_tokens`, `stop`, `n`, `tools`, `tool_choice`, `user`, `frequency_penalty`, `presence_penalty`, `logit_bias`) plus: | Field | Description | |-------|-------------| | `security_tier` | `"standard"` \| `"high"` \| `"maximum"` — hardware isolation level | | `api_key` | Per-request API key override | | `base_url` | Per-request router URL override — creates a temporary client, used once, then disposed | ### `SecureCompletionClient` — Constructor Options Lower-level client. All options above apply, with these differences: | Option | Type | Default | Description | |--------|------|---------|-------------| | `routerUrl` | `string` | `'https://api.nomyo.ai'` | Base URL (`baseUrl` is renamed here) | | `keySize` | `2048 \| 4096` | `4096` | RSA modulus length | #### Methods - `generateKeys(options?)` — generate a new RSA key pair - `loadKeys(privateKeyPath, publicKeyPath?, password?)` — load existing PEM files - `fetchServerPublicKey()` — fetch the server's RSA public key - `encryptPayload(payload)` — encrypt a request payload - `decryptResponse(encrypted, payloadId)` — decrypt a response body - `sendSecureRequest(payload, payloadId, apiKey?, securityTier?)` — full encrypt → POST → decrypt cycle - `dispose()` — zero key material and stop rotation timer ### Secure Memory Public API ```typescript import { getMemoryProtectionInfo, disableSecureMemory, enableSecureMemory, SecureByteContext, } from 'nomyo-js'; ``` | Export | Description | |--------|-------------| | `getMemoryProtectionInfo()` | Returns `{ method, canLock, isPlatformSecure, details? }` | | `disableSecureMemory()` | Disable global secure-memory zeroing | | `enableSecureMemory()` | Re-enable global secure-memory zeroing | | `SecureByteContext` | Low-level buffer wrapper — zeros in `finally` block | ### Error Classes ```typescript import { AuthenticationError, InvalidRequestError, RateLimitError, ForbiddenError, ServerError, ServiceUnavailableError, APIConnectionError, SecurityError, DisposedError, APIError, } from 'nomyo-js'; ``` | Class | HTTP | Thrown when | |-------|------|-------------| | `AuthenticationError` | 401 | Invalid or missing API key | | `InvalidRequestError` | 400 | Malformed request | | `ForbiddenError` | 403 | Model not allowed for the security tier | | `RateLimitError` | 429 | Rate limit exceeded (after all retries) | | `ServerError` | 500 | Internal server error (after all retries) | | `ServiceUnavailableError` | 503 | Backend unavailable (after all retries) | | `APIError` | varies | Other HTTP errors | | `APIConnectionError` | — | Network failure or timeout (after all retries) | | `SecurityError` | — | HTTPS not used, header injection, or crypto failure | | `DisposedError` | — | Method called after `dispose()` | ## Platform Support ### Node.js - **Minimum**: Node.js 14.17+ - **Recommended**: Node.js 18 LTS or later - **Key storage**: File system (`keyDir` directory, default `client_keys/`) ### Browsers - **Supported**: Chrome 37+, Firefox 34+, Safari 11+, Edge 79+ - **Key storage**: In-memory only (not persisted) - **Limitation**: File-based key operations (`keyDir`, `loadKeys`) are not available ## Security Best Practices - Always use HTTPS (`allowHttp` is `false` by default) - Load API key from an environment variable, never hardcode it - Use password-protected key files (`keyRotationPassword`) - Store keys outside the project directory and outside version control - Add `client_keys/` and `*.pem` to `.gitignore` - Call `dispose()` when the client is no longer needed - Use `security_tier: 'maximum'` for HIPAA PHI or classified data - Consider the `nomyo-native` addon if swap-file exposure is unacceptable ## License See LICENSE file.