342 lines
12 KiB
Markdown
342 lines
12 KiB
Markdown
# 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
|
|
<script type="module">
|
|
import { SecureChatCompletion } from 'https://unpkg.com/nomyo-js/dist/browser/index.js';
|
|
|
|
const client = new SecureChatCompletion({
|
|
baseUrl: 'https://api.nomyo.ai',
|
|
apiKey: 'your-api-key',
|
|
});
|
|
|
|
const response = await client.create({
|
|
model: 'Qwen/Qwen3-0.6B',
|
|
messages: [{ role: 'user', content: 'What is 2+2?' }],
|
|
});
|
|
|
|
console.log(response.choices[0].message.content);
|
|
</script>
|
|
```
|
|
|
|
## 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<ChatCompletionResponse>` — send an encrypted chat completion
|
|
- `acreate(request): Promise<ChatCompletionResponse>` — 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.
|