fix: base_url

doc: created
This commit is contained in:
Alpha Nerd 2026-04-16 16:44:26 +02:00
parent 6e02559f4e
commit 43165f86f2
Signed by: alpha-nerd
SSH key fingerprint: SHA256:QkkAgVoYi9TQ0UKPkiKSfnerZy2h4qhi3SVPXJmBN+M
17 changed files with 2151 additions and 293 deletions

524
README.md
View file

@ -1,13 +1,13 @@
# NOMYO.js - Secure JavaScript Chat Client
# 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**
🔑 **Uses hybrid encryption (AES-256-GCM + RSA-OAEP with 4096-bit keys)**
🔄 **Drop-in replacement for OpenAI's ChatCompletion API**
🌐 **Works in both Node.js and browsers**
- 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
## Quick Start
### Installation
@ -20,371 +20,323 @@ npm install nomyo-js
```javascript
import { SecureChatCompletion } from 'nomyo-js';
// Initialize client (defaults to https://api.nomyo.ai:12434)
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai:12434'
apiKey: process.env.NOMYO_API_KEY,
});
// Simple chat completion
const response = await client.create({
model: 'Qwen/Qwen3-0.6B',
messages: [
{ role: 'user', content: 'Hello! How are you today?' }
],
temperature: 0.7
messages: [{ role: 'user', content: 'Hello!' }],
temperature: 0.7,
});
console.log(response.choices[0].message.content);
client.dispose();
```
### Basic Usage (Browser)
```html
<!DOCTYPE html>
<html>
<head>
<script type="module">
import { SecureChatCompletion } from 'https://unpkg.com/nomyo-js/dist/browser/index.js';
<script type="module">
import { SecureChatCompletion } from 'https://unpkg.com/nomyo-js/dist/browser/index.js';
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai:12434'
});
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?' }
]
});
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>
</head>
<body>
<h1>NOMYO Secure Chat</h1>
</body>
</html>
console.log(response.choices[0].message.content);
</script>
```
## 🔐 Security Features
## 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 with SHA-256
- **Payload encryption**: AES-256-GCM (authenticated encryption)
- **Key exchange**: RSA-OAEP-SHA256
- **Key size**: 4096-bit RSA keys
- **All communication**: End-to-end encrypted
- **Scope**: All communication is end-to-end encrypted
### Key Management
- **Automatic key generation**: Keys are automatically generated on first use
- **Automatic key loading**: Existing keys are loaded automatically from `client_keys/` directory (Node.js only)
- **No manual intervention required**: The library handles key management automatically
- **Optional persistence**: Keys can be saved to `client_keys/` directory for reuse across sessions (Node.js only)
- **Password protection**: Optional password encryption for private keys (minimum 8 characters required)
- **Secure permissions**: Private keys stored with restricted permissions (600 - owner-only access)
- **Automatic key rotation**: Keys are rotated on a configurable interval (default: 24 hours) to limit fingerprint lifetime
- **Explicit lifecycle management**: Call `dispose()` to immediately zero in-memory key material and stop the rotation timer
- **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 Protection
### Secure Memory
> [!NOTE]
> **Pure JavaScript Implementation**: This version uses pure JavaScript with immediate memory zeroing.
> OS-level memory locking (`mlock`) is NOT available without a native addon.
> For enhanced security in production, consider implementing the optional native addon (see `native/` directory).
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.
- **Automatic cleanup**: Sensitive data is zeroed from memory immediately after use
- **Best-effort protection**: Minimizes exposure time of sensitive data
- **Fallback mechanism**: Graceful degradation if enhanced security is unavailable
## 🔄 OpenAI Compatibility
The `SecureChatCompletion` class provides **exact API compatibility** with OpenAI's `ChatCompletion.create()` method.
### Supported Parameters
All standard OpenAI parameters are supported:
- `model`: Model identifier
- `messages`: List of message objects
- `temperature`: Sampling temperature (0-2)
- `max_tokens`: Maximum tokens to generate
- `top_p`: Nucleus sampling
- `frequency_penalty`: Frequency penalty
- `presence_penalty`: Presence penalty
- `stop`: Stop sequences
- `n`: Number of completions
- `tools`: Tool definitions
- `tool_choice`: Tool selection strategy
- `user`: User identifier
### Response Format
Responses follow the OpenAI format exactly, with an additional `_metadata` field for debugging and security information:
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
{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1234567890,
"model": "Qwen/Qwen3-0.6B",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! I'm doing well, thank you for asking."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30
},
"_metadata": {
"payload_id": "openai-compat-abc123",
"processed_at": 1765250382,
"is_encrypted": true,
"encryption_algorithm": "hybrid-aes256-rsa4096",
"response_status": "success"
}
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;
}
```
## 🛠️ Usage Examples
### Per-Request Router Override
### Basic Chat
Send a single request to a different router without changing the main client:
```javascript
import { SecureChatCompletion } from 'nomyo-js';
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai:12434'
});
const response = await client.create({
model: 'Qwen/Qwen3-0.6B',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'What is the capital of France?' }
],
temperature: 0.7
model: 'Qwen/Qwen3-0.6B',
messages: [{ role: 'user', content: 'Hello from secondary router' }],
base_url: 'https://secondary.nomyo.ai:12435', // temporary — main client unchanged
});
console.log(response.choices[0].message.content);
```
### With Tools
### Tool / Function Calling
```javascript
const response = await client.create({
model: 'Qwen/Qwen3-0.6B',
messages: [
{ role: 'user', content: "What's the weather in Paris?" }
],
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',
name: 'get_weather',
description: 'Get weather information for a location',
parameters: {
type: 'object',
properties: {
location: { type: 'string' }
},
required: ['location']
}
}
}
]
properties: { location: { type: 'string' } },
required: ['location'],
},
},
},
],
tool_choice: 'auto',
});
```
### With API Key Authentication
### Thinking Models
```javascript
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai:12434',
apiKey: 'your-api-key-here'
});
// API key will be automatically included in all requests
const response = await client.create({
model: 'Qwen/Qwen3-0.6B',
messages: [
{ role: 'user', content: 'Hello!' }
]
});
```
### Custom Key Management (Node.js)
```javascript
import { SecureCompletionClient } from 'nomyo-js';
const client = new SecureCompletionClient({
routerUrl: 'https://api.nomyo.ai:12434'
model: 'LiquidAI/LFM2.5-1.2B-Thinking',
messages: [{ role: 'user', content: 'Is 9.9 larger than 9.11?' }],
});
// Generate keys with password protection
await client.generateKeys({
saveToFile: true,
keyDir: 'client_keys',
password: 'your-secure-password'
});
// Or load existing keys
await client.loadKeys(
'client_keys/private_key.pem',
'client_keys/public_key.pem',
'your-secure-password'
);
const { content, reasoning_content } = response.choices[0].message;
console.log('Reasoning:', reasoning_content);
console.log('Answer:', content);
```
### Resource Management
Always call `dispose()` when finished to zero key material and stop the background rotation timer:
```javascript
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai:12434',
keyRotationInterval: 3600000, // rotate every hour
});
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();
client.dispose(); // zeros key material, stops rotation timer
}
```
To disable key rotation entirely (e.g. short-lived scripts):
```javascript
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai:12434',
keyRotationInterval: 0, // disabled
});
```
## 🧪 Platform Support
### Node.js
- **Minimum version**: Node.js 14.17+
- **Recommended**: Node.js 18 LTS or later
- **Key storage**: File system (`client_keys/` directory)
- **Security**: Full implementation with automatic key persistence
### Browsers
- **Supported browsers**: Modern browsers with Web Crypto API support
- Chrome 37+
- Firefox 34+
- Safari 11+
- Edge 79+
- **Key storage**: In-memory only (keys not persisted for security)
- **Security**: Best-effort memory protection (no OS-level locking)
## 📚 API Reference
### SecureChatCompletion
#### Constructor
```typescript
new SecureChatCompletion(config?: {
baseUrl?: string; // Default: 'https://api.nomyo.ai:12434'
allowHttp?: boolean; // Default: false
apiKey?: string; // Default: undefined
secureMemory?: boolean; // Default: true
timeout?: number; // Request timeout ms. Default: 60000
debug?: boolean; // Enable verbose logging. Default: false
keyRotationInterval?: number; // Key rotation ms. 0 = disabled. Default: 86400000 (24h)
keyRotationDir?: string; // Directory for rotated keys. Default: 'client_keys'
keyRotationPassword?: string; // Password for rotated key files
})
```
#### Methods
- `create(request: ChatCompletionRequest): Promise<ChatCompletionResponse>`
- `acreate(request: ChatCompletionRequest): Promise<ChatCompletionResponse>` (alias)
- `dispose(): void` — zero key material and stop rotation timer
### SecureCompletionClient
Lower-level API for advanced use cases.
#### Constructor
```typescript
new SecureCompletionClient(config?: {
routerUrl?: string; // Default: 'https://api.nomyo.ai:12434'
allowHttp?: boolean; // Default: false
secureMemory?: boolean; // Default: true
keySize?: 2048 | 4096; // Default: 4096
timeout?: number; // Request timeout ms. Default: 60000
debug?: boolean; // Enable verbose logging. Default: false
keyRotationInterval?: number; // Key rotation ms. 0 = disabled. Default: 86400000 (24h)
keyRotationDir?: string; // Directory for rotated keys. Default: 'client_keys'
keyRotationPassword?: string; // Password for rotated key files
})
```
#### Methods
- `generateKeys(options?: KeyGenOptions): Promise<void>`
- `loadKeys(privateKeyPath: string, publicKeyPath?: string, password?: string): Promise<void>`
- `fetchServerPublicKey(): Promise<string>`
- `encryptPayload(payload: object): Promise<ArrayBuffer>`
- `decryptResponse(encrypted: ArrayBuffer, payloadId: string): Promise<object>`
- `sendSecureRequest(payload: object, payloadId: string, apiKey?: string): Promise<object>`
- `dispose(): void` — zero key material and stop rotation timer
## 🔧 Configuration
### Local Development (HTTP)
```javascript
const client = new SecureChatCompletion({
baseUrl: 'http://localhost:12434',
allowHttp: true // Required for HTTP connections
baseUrl: 'http://localhost:12435',
allowHttp: true, // required — also prints a visible warning
});
```
⚠️ **Warning**: Only use HTTP for local development. Never use in production!
## API Reference
### Disable Secure Memory
### `SecureChatCompletion` — Constructor Options
```javascript
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai:12434',
secureMemory: false // Disable memory protection (not recommended)
});
```typescript
new SecureChatCompletion(config?: ChatCompletionConfig)
```
## 📝 Security Best Practices
| 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, …). |
- ✅ Always use HTTPS in production
- ✅ Use password protection for private keys (Node.js)
- ✅ Keep private keys secure (permissions set to 600)
- ✅ Never share your private key
- ✅ Verify server's public key fingerprint before first use
- ✅ Enable secure memory protection (default)
#### Methods
## 🤝 Contributing
- `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
Contributions are welcome! Please open issues or pull requests on the project repository.
#### `create()` Request Fields
## 📄 License
All standard OpenAI fields (`model`, `messages`, `temperature`, `top_p`, `max_tokens`, `stop`, `n`, `tools`, `tool_choice`, `user`, `frequency_penalty`, `presence_penalty`, `logit_bias`) plus:
See LICENSE file for licensing information.
| 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 |
## 📞 Support
### `SecureCompletionClient` — Constructor Options
For questions or issues, please refer to the project documentation or open an issue.
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.