fix: base_url port

feat: add types for reasoning_content and _metadata

fix: key format incompatibility
This commit is contained in:
Alpha Nerd 2026-04-01 13:38:45 +02:00
parent c7601b2270
commit 76703e2e3e
5 changed files with 54 additions and 7 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ coverage/
.nyc_output/ .nyc_output/
build/ build/
*.node *.node
settings.json

View file

@ -13,7 +13,7 @@ export class SecureChatCompletion {
constructor(config: ChatCompletionConfig = {}) { constructor(config: ChatCompletionConfig = {}) {
const { const {
baseUrl = 'https://api.nomyo.ai:12434', baseUrl = 'https://api.nomyo.ai:12435',
allowHttp = false, allowHttp = false,
apiKey, apiKey,
secureMemory = true, secureMemory = true,

View file

@ -64,9 +64,9 @@ export class SecureCompletionClient {
private secureMemoryImpl = createSecureMemory(); private secureMemoryImpl = createSecureMemory();
private readonly keySize: 2048 | 4096; private readonly keySize: 2048 | 4096;
constructor(config: ClientConfig = { routerUrl: 'https://api.nomyo.ai:12434' }) { constructor(config: ClientConfig = { routerUrl: 'https://api.nomyo.ai:12435' }) {
const { const {
routerUrl = 'https://api.nomyo.ai:12434', routerUrl = 'https://api.nomyo.ai:12435',
allowHttp = false, allowHttp = false,
secureMemory = true, secureMemory = true,
keySize = 4096, keySize = 4096,
@ -174,7 +174,7 @@ export class SecureCompletionClient {
throw new SecurityError( throw new SecurityError(
'Server public key must be fetched over HTTPS to prevent MITM attacks. ' + 'Server public key must be fetched over HTTPS to prevent MITM attacks. ' +
'For local development, initialize with allowHttp=true: ' + 'For local development, initialize with allowHttp=true: ' +
'new SecureChatCompletion({ baseUrl: "http://localhost:12434", allowHttp: true })' 'new SecureChatCompletion({ baseUrl: "http://localhost:12435", allowHttp: true })'
); );
} else { } else {
console.warn('Fetching key over HTTP (local development mode)'); console.warn('Fetching key over HTTP (local development mode)');

View file

@ -7,6 +7,7 @@
*/ */
import { RSAOperations } from './rsa'; import { RSAOperations } from './rsa';
import { getCrypto } from './utils';
import { KeyGenOptions, KeyPaths } from '../../types/client'; import { KeyGenOptions, KeyPaths } from '../../types/client';
export class KeyManager { export class KeyManager {
@ -66,8 +67,25 @@ export class KeyManager {
const path = require('path'); const path = require('path');
// Load private key // Load private key
const privateKeyPem = await fs.readFile(paths.privateKeyPath, 'utf-8'); const privateKeyPem = await fs.readFile(paths.privateKeyPath, 'utf-8') as string;
if (password && privateKeyPem.includes('BEGIN ENCRYPTED PRIVATE KEY')) {
// Standard PKCS#8 encrypted format (produced by Python/OpenSSL)
const { createPrivateKey } = require('crypto') as typeof import('crypto');
const keyObject = createPrivateKey({ key: privateKeyPem, format: 'pem', passphrase: password });
const pkcs8Der = keyObject.export({ type: 'pkcs8', format: 'der' }) as Buffer;
const subtle = getCrypto();
this.privateKey = await subtle.importKey(
'pkcs8',
pkcs8Der,
{ name: 'RSA-OAEP', hash: 'SHA-256' },
true,
['decrypt']
);
} else {
// Unencrypted PKCS#8 or legacy JS custom-encrypted format
this.privateKey = await this.rsa.importPrivateKey(privateKeyPem, password); this.privateKey = await this.rsa.importPrivateKey(privateKeyPem, password);
}
// Validate private key size (minimum 2048 bits) // Validate private key size (minimum 2048 bits)
const privAlgorithm = this.privateKey.algorithm as RsaHashedKeyAlgorithm; const privAlgorithm = this.privateKey.algorithm as RsaHashedKeyAlgorithm;
@ -120,7 +138,17 @@ export class KeyManager {
await fs.mkdir(directory, { recursive: true }); await fs.mkdir(directory, { recursive: true });
// Export and save private key // Export and save private key
const privateKeyPem = await this.rsa.exportPrivateKey(this.privateKey, password); let privateKeyPem: string;
if (password) {
// Use standard PKCS#8 encrypted format (compatible with Python/OpenSSL)
const { createPrivateKey } = require('crypto') as typeof import('crypto');
const subtle = getCrypto();
const pkcs8Der = await subtle.exportKey('pkcs8', this.privateKey);
const keyObject = createPrivateKey({ key: Buffer.from(pkcs8Der), format: 'der', type: 'pkcs8' });
privateKeyPem = keyObject.export({ type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: password }) as string;
} else {
privateKeyPem = await this.rsa.exportPrivateKey(this.privateKey);
}
const privateKeyPath = path.join(directory, 'private_key.pem'); const privateKeyPath = path.join(directory, 'private_key.pem');
await fs.writeFile(privateKeyPath, privateKeyPem, 'utf-8'); await fs.writeFile(privateKeyPath, privateKeyPem, 'utf-8');

View file

@ -9,6 +9,8 @@ export interface Message {
name?: string; name?: string;
tool_calls?: ToolCall[]; tool_calls?: ToolCall[];
tool_call_id?: string; tool_call_id?: string;
/** Thinking-model reasoning output (Qwen3, DeepSeek-R1, etc.) */
reasoning_content?: string;
} }
export interface ToolCall { export interface ToolCall {
@ -70,12 +72,28 @@ export interface Choice {
logprobs?: unknown; logprobs?: unknown;
} }
export interface MemoryProtectionInfo {
enabled: boolean;
platform: string;
protection_level: string;
has_memory_locking: boolean;
has_secure_zeroing: boolean;
supports_full_protection: boolean;
page_size?: number;
}
export interface ResponseMetadata { export interface ResponseMetadata {
payload_id: string; payload_id: string;
processed_at: number; processed_at: number;
is_encrypted: boolean; is_encrypted: boolean;
encryption_algorithm: string; encryption_algorithm: string;
response_status: string; response_status: string;
/** Hardware routing tier used for this request */
security_tier?: string;
/** Server-side memory protection details */
memory_protection?: MemoryProtectionInfo;
/** CUDA device ID used for inference (if applicable) */
cuda_device?: string | number;
} }
export interface ChatCompletionResponse { export interface ChatCompletionResponse {