Rene Fichtmueller b68d5c3fbf fix: client CompletionResponse matches actual gateway response fields
- Match field names: id, status, confidence, model, task_type, latency_ms, tokens, output
- Default URL now https://llm-gateway.context-x.org (public endpoint)
- ShieldX client uses 'shieldx' caller (not 'internal')
- tokens.in/tokens.out instead of token_count.input/output
2026-04-02 23:17:14 +02:00

255 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @llm-gateway/client
*
* TypeScript client library for the LLM Gateway.
* Used by all Context X projects: TIP, EO Global Pulse, SwitchBlade,
* PeerCortex, NOGnet, ShieldX, and CtxEvent.
*
* Usage:
* import { LLMGatewayClient, createTIPClient } from '@llm-gateway/client';
* const client = createTIPClient();
* const result = await client.completion({ task_type: 'summarize', input: '...' });
*/
// ============================================================
// Request / Response types
// ============================================================
export interface CompletionRequest {
/** Identifies which project/service is calling (e.g. 'tip-scraper', 'eo-global-pulse') */
caller: string;
/** Task type that maps to a prompt template (e.g. 'summarize', 'classify', 'translate') */
task_type: string;
/** The raw input text to process */
input: string;
/** Preferred output language */
language?: 'de' | 'en';
/** Additional context passed to the prompt template */
context?: Record<string, unknown>;
/** Per-request model / behavior overrides */
options?: {
/** Override the model (e.g. 'qwen2.5:32b'). Gateway picks a sensible default. */
model?: string;
/** Sampling temperature 01 */
temperature?: number;
/** Max output tokens */
max_tokens?: number;
/** Include full validation details in the response */
return_validation_details?: boolean;
};
}
export interface CompletionResponse {
/** Request ID for tracing */
id: string;
/** Overall status of the response */
status: 'approved' | 'warning' | 'pending_review' | 'rejected';
/** Model confidence score 010 */
confidence: number;
/** Ollama model that produced the output */
model: string;
/** Task type that was processed */
task_type: string;
/** End-to-end latency in milliseconds */
latency_ms: number;
/** Token usage */
tokens: { in: number; out: number };
/** The LLM output text */
output: string;
/** Validation details (present when return_validation_details=true) */
validation?: {
passed: boolean;
score: number;
validators: Record<string, unknown>;
};
}
export interface ClassifyResponse {
task_type: string;
content_type: string;
language: string;
complexity: 'low' | 'medium' | 'high';
requires_facts: boolean;
suggested_task_types: string[];
}
export interface BatchResponse {
batch_id: string;
}
export interface HealthResponse {
status: 'ok' | 'degraded' | 'down';
ollama: unknown;
queue: unknown;
}
// ============================================================
// Gateway client
// ============================================================
export class LLMGatewayClient {
private readonly baseUrl: string;
private readonly caller: string;
private readonly timeout: number;
constructor(config: {
baseUrl?: string;
caller: string;
/** Request timeout in ms (default: 30 000) */
timeout?: number;
}) {
this.baseUrl = config.baseUrl
?? process.env['LLM_GATEWAY_URL']
?? 'https://llm-gateway.context-x.org';
this.caller = config.caller;
this.timeout = config.timeout ?? 30_000;
}
// ----------------------------------------------------------
// Core: completion
// ----------------------------------------------------------
async completion(
params: Omit<CompletionRequest, 'caller'>,
): Promise<CompletionResponse> {
const body: CompletionRequest = { ...params, caller: this.caller };
return this.post<CompletionResponse>('/v1/completion', body);
}
// ----------------------------------------------------------
// Classify input before routing
// ----------------------------------------------------------
async classify(input: string): Promise<ClassifyResponse> {
return this.post<ClassifyResponse>('/v1/classify', {
caller: this.caller,
input,
});
}
// ----------------------------------------------------------
// Batch: submit multiple tasks, results delivered via webhook
// ----------------------------------------------------------
async batch(
tasks: Array<Omit<CompletionRequest, 'caller'>>,
webhookUrl: string,
): Promise<BatchResponse> {
return this.post<BatchResponse>('/v1/batch', {
caller: this.caller,
tasks,
webhook_url: webhookUrl,
});
}
// ----------------------------------------------------------
// Health
// ----------------------------------------------------------
async health(): Promise<HealthResponse> {
const res = await this.fetchWithTimeout(`${this.baseUrl}/health`);
if (!res.ok) {
throw new Error(`Health check failed: ${res.status}`);
}
return res.json() as Promise<HealthResponse>;
}
// ----------------------------------------------------------
// Graceful degradation — returns null when gateway is unavailable
// ----------------------------------------------------------
async safeCompletion(
params: Omit<CompletionRequest, 'caller'>,
): Promise<CompletionResponse | null> {
try {
return await this.completion(params);
} catch {
// Gateway is down or timed out — caller handles degraded mode
return null;
}
}
// ----------------------------------------------------------
// Internal helpers
// ----------------------------------------------------------
private async post<T>(path: string, body: unknown): Promise<T> {
const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`Gateway error ${res.status} on ${path}: ${text}`);
}
return res.json() as Promise<T>;
}
private fetchWithTimeout(url: string, init?: RequestInit): Promise<Response> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), this.timeout);
return fetch(url, { ...init, signal: controller.signal }).finally(() =>
clearTimeout(timer),
);
}
}
// ============================================================
// Project-specific pre-configured factory functions
// ============================================================
/**
* TIP (Transceiver Intelligence Platform)
* Long timeout because scraping + AI analysis can take time.
*/
export function createTIPClient(baseUrl?: string): LLMGatewayClient {
return new LLMGatewayClient({ caller: 'tip-scraper', baseUrl, timeout: 60_000 });
}
/**
* EO Global Pulse — team collaboration & CRM intelligence
*/
export function createEOPulseClient(baseUrl?: string): LLMGatewayClient {
return new LLMGatewayClient({ caller: 'eo-global-pulse', baseUrl, timeout: 30_000 });
}
/**
* SwitchBlade — infrastructure management platform
*/
export function createSwitchBladeClient(baseUrl?: string): LLMGatewayClient {
return new LLMGatewayClient({ caller: 'switchblade', baseUrl, timeout: 15_000 });
}
/**
* PeerCortex — BGP/RPKI network intelligence
* Short timeout: results must be near-real-time for network monitoring.
*/
export function createPeerCortexClient(baseUrl?: string): LLMGatewayClient {
return new LLMGatewayClient({ caller: 'peercortex', baseUrl, timeout: 8_000 });
}
/**
* NOGnet — NOG Support Program & event management
*/
export function createNOGnetClient(baseUrl?: string): LLMGatewayClient {
return new LLMGatewayClient({ caller: 'nognet', baseUrl, timeout: 30_000 });
}
/**
* ShieldX — LLM prompt injection defense
*/
export function createShieldXClient(baseUrl?: string): LLMGatewayClient {
return new LLMGatewayClient({ caller: 'shieldx', baseUrl, timeout: 10_000 });
}
/**
* CtxEvent — event management platform
*/
export function createCtxEventClient(baseUrl?: string): LLMGatewayClient {
return new LLMGatewayClient({ caller: 'ctxevent', baseUrl, timeout: 20_000 });
}