learning_ai_common_plat/services/mcp-server/src/lib/platform-client.ts

292 lines
8.8 KiB
TypeScript

import { config } from './config.js';
export interface PlatformClientOptions {
/** Bearer token for the upstream request */
token?: string;
/** x-request-id to propagate */
requestId?: string;
/** x-product-id to forward */
productId?: string;
}
async function platformFetch<T>(
path: string,
init: RequestInit,
opts: PlatformClientOptions
): Promise<T> {
const url = `${config.PLATFORM_SERVICE_URL}${path}`;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
...(opts.productId ? { 'x-product-id': opts.productId } : {}),
};
const res = await fetch(url, {
...init,
headers: { ...headers, ...(init.headers ?? {}) },
signal: AbortSignal.timeout(10_000),
});
if (!res.ok) {
const body = await res.text().catch(() => '');
throw new Error(`platform-service ${init.method ?? 'GET'} ${path}${res.status}: ${body}`);
}
return res.json() as Promise<T>;
}
// ── Telemetry ─────────────────────────────────────────────────────────────────
export interface TelemetryQueryResult {
events: unknown[];
total: number;
continuationToken?: string;
}
export async function telemetryQuery(
params: {
productId: string;
eventType?: string;
from?: string;
to?: string;
limit?: number;
continuationToken?: string;
},
opts: PlatformClientOptions
): Promise<TelemetryQueryResult> {
const qs = new URLSearchParams();
qs.set('productId', params.productId);
if (params.eventType) qs.set('eventType', params.eventType);
if (params.from) qs.set('from', params.from);
if (params.to) qs.set('to', params.to);
qs.set(
'limit',
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
);
if (params.continuationToken) qs.set('continuationToken', params.continuationToken);
return platformFetch<TelemetryQueryResult>(`/api/telemetry/query?${qs}`, { method: 'GET' }, opts);
}
export interface TelemetryCluster {
/** Cluster doc ID: ${fingerprint}:${yyyyMM} */
id: string;
/** Partition key: ${productId}:${platform}:${module} */
pk: string;
fingerprint: string;
severity: 'warn' | 'error' | 'fatal';
totalCount: number;
lastSeenAt: string;
firstSeenAt: string;
platform: string;
module: string;
eventName: string;
sampleMessage?: string;
status: 'open' | 'resolved' | 'ignored';
resolvedBy?: string;
resolvedAt?: string;
}
export async function telemetryClusters(
params: { from?: string; to?: string; platform?: string; module?: string },
opts: PlatformClientOptions
): Promise<{ clusters: TelemetryCluster[]; total: number }> {
const qs = new URLSearchParams();
if (params.from) qs.set('from', params.from);
if (params.to) qs.set('to', params.to);
if (params.platform) qs.set('platform', params.platform);
if (params.module) qs.set('module', params.module);
return platformFetch<{ clusters: TelemetryCluster[]; total: number }>(
`/api/telemetry/clusters?${qs}`,
{ method: 'GET' },
opts
);
}
export async function telemetryMetrics(opts: PlatformClientOptions): Promise<unknown> {
return platformFetch<unknown>('/api/telemetry/metrics', { method: 'GET' }, opts);
}
export async function telemetryListPolicies(opts: PlatformClientOptions): Promise<unknown> {
return platformFetch<unknown>('/api/telemetry/policies', { method: 'GET' }, opts);
}
export async function telemetryPreviewPolicy(
body: { targeting?: Record<string, unknown> },
opts: PlatformClientOptions
): Promise<unknown> {
return platformFetch<unknown>(
'/api/telemetry/policies/preview',
{ method: 'POST', body: JSON.stringify(body) },
opts
);
}
export async function telemetryCreatePolicy(
body: Record<string, unknown>,
opts: PlatformClientOptions
): Promise<unknown> {
return platformFetch<unknown>(
'/api/telemetry/policies',
{ method: 'POST', body: JSON.stringify(body) },
opts
);
}
export async function telemetryUpdatePolicy(
policyId: string,
body: Record<string, unknown>,
opts: PlatformClientOptions
): Promise<unknown> {
return platformFetch<unknown>(
`/api/telemetry/policies/${encodeURIComponent(policyId)}`,
{ method: 'PUT', body: JSON.stringify(body) },
opts
);
}
export async function telemetryDeletePolicy(
policyId: string,
opts: PlatformClientOptions
): Promise<{ success: boolean }> {
return platformFetch<{ success: boolean }>(
`/api/telemetry/policies/${encodeURIComponent(policyId)}`,
{ method: 'DELETE' },
opts
);
}
export async function telemetryUpdateCluster(
clusterId: string,
pk: string,
status: 'open' | 'resolved' | 'ignored',
opts: PlatformClientOptions
): Promise<unknown> {
const qs = new URLSearchParams({ pk });
return platformFetch<unknown>(
`/api/telemetry/clusters/${encodeURIComponent(clusterId)}?${qs}`,
{ method: 'PATCH', body: JSON.stringify({ status }) },
opts
);
}
// ── Diagnostics ───────────────────────────────────────────────────────────────
export interface DebugSession {
id: string;
productId: string;
status: string;
collectionLevel: string;
captureLogs: boolean;
captureNetwork: boolean;
captureScreenshots: boolean;
maxDurationMinutes: number;
logCount: number;
traceCount: number;
createdAt: string;
expiresAt: string;
targetUserId?: string;
targetAnonymousId?: string;
}
export async function diagnosticsListSessions(
params: {
productId?: string;
status?: string;
limit?: number;
offset?: number;
},
opts: PlatformClientOptions
): Promise<{ sessions: DebugSession[]; total: number }> {
const qs = new URLSearchParams();
if (params.productId) qs.set('productId', params.productId);
if (params.status) qs.set('status', params.status);
qs.set(
'limit',
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
);
if (params.offset) qs.set('offset', String(params.offset));
return platformFetch<{ sessions: DebugSession[]; total: number }>(
`/api/diagnostics/sessions?${qs}`,
{ method: 'GET' },
opts
);
}
export async function diagnosticsCreateSession(
body: {
productId: string;
targetUserId?: string;
targetAnonymousId?: string;
collectionLevel?: 'standard' | 'debug' | 'trace';
captureLogs?: boolean;
captureNetwork?: boolean;
maxDurationMinutes?: number;
},
opts: PlatformClientOptions
): Promise<DebugSession> {
return platformFetch<DebugSession>(
'/api/diagnostics/sessions',
{ method: 'POST', body: JSON.stringify(body) },
opts
);
}
export async function diagnosticsGetSession(
sessionId: string,
opts: PlatformClientOptions
): Promise<DebugSession> {
return platformFetch<DebugSession>(
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}`,
{ method: 'GET' },
opts
);
}
export async function diagnosticsUpdateSession(
sessionId: string,
body: { status?: string; collectionLevel?: string; maxDurationMinutes?: number },
opts: PlatformClientOptions
): Promise<DebugSession> {
return platformFetch<DebugSession>(
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}`,
{ method: 'PATCH', body: JSON.stringify(body) },
opts
);
}
export async function diagnosticsGetLogs(
sessionId: string,
params: { level?: string; from?: string; to?: string; limit?: number },
opts: PlatformClientOptions
): Promise<{ logs: unknown[]; continuationToken?: string }> {
const qs = new URLSearchParams();
if (params.level) qs.set('level', params.level);
if (params.from) qs.set('from', params.from);
if (params.to) qs.set('to', params.to);
qs.set(
'limit',
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
);
return platformFetch<{ logs: unknown[]; continuationToken?: string }>(
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}/logs?${qs}`,
{ method: 'GET' },
opts
);
}
export async function diagnosticsGetTraces(
sessionId: string,
params: { limit?: number; continuationToken?: string },
opts: PlatformClientOptions
): Promise<{ traces: unknown[]; continuationToken?: string }> {
const qs = new URLSearchParams();
qs.set(
'limit',
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
);
if (params.continuationToken) qs.set('continuationToken', params.continuationToken);
return platformFetch<{ traces: unknown[]; continuationToken?: string }>(
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}/traces?${qs}`,
{ method: 'GET' },
opts
);
}