fix: base_url port
feat: add types for reasoning_content and _metadata fix: key format incompatibility
This commit is contained in:
parent
c7601b2270
commit
76703e2e3e
5 changed files with 54 additions and 7 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,3 +7,4 @@ coverage/
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
build/
|
build/
|
||||||
*.node
|
*.node
|
||||||
|
settings.json
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)');
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue