learning_ai_common_plat/services/mcp-server/src/lib/jarvis-client.ts
saravanakumardb1 5a3987bd9f fix(mcp-server): jarvis.marketplace.certify routes to wrong endpoint on rejection
The backend has two separate endpoints:
  POST /marketplace/admin/:id/approve
  POST /marketplace/admin/:id/reject

Previously jarvisMarketplaceCertify always called /approve regardless of
the decision field — rejections would silently approve listings.

Fix: derive action from decision.decision ('approved' → 'approve', else 'reject')
and use the correct endpoint path.
2026-03-05 14:06:19 -08:00

244 lines
7.7 KiB
TypeScript

/**
* JarvisJr backend client — typed HTTP wrappers for the jarvisjr-backend (port 4012).
*
* Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service).
*/
import { config } from './config.js';
export interface JarvisClientOptions {
token?: string;
requestId?: string;
}
// ── Shared fetch helper ────────────────────────────────────────────────────
async function jarvisFetch<T>(
path: string,
init: RequestInit,
opts: JarvisClientOptions
): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
};
const res = await fetch(`${config.JARVISJR_BACKEND_URL}/api${path}`, {
...init,
headers: { ...((init.headers as Record<string, string>) ?? {}), ...headers },
signal: AbortSignal.timeout(15_000),
});
if (!res.ok) {
const body = await res.text().catch(() => '');
throw new Error(`jarvisjr-backend ${init.method ?? 'GET'} ${path}${res.status}: ${body}`);
}
return res.json() as Promise<T>;
}
// ── Agents ─────────────────────────────────────────────────────────────────
export interface JarvisAgentDoc {
id: string;
userId: string;
productId: string;
name: string;
role: string;
systemPrompt: string;
voiceId?: string;
coachingFramework?: string;
accentColor?: string;
welcomeMessage?: string;
sessionLength?: number;
difficultyLevel?: string;
language?: string;
privacyLevel?: string;
checkInSchedule?: string;
isTemplate?: boolean;
templateSource?: string;
totalSessions: number;
lastSessionAt: string | null;
createdAt: string;
updatedAt: string;
}
export interface AgentCreateInput {
name: string;
role: string;
systemPrompt: string;
voiceId?: string;
coachingFramework?: string;
accentColor?: string;
welcomeMessage?: string;
sessionLength?: number;
difficultyLevel?: 'beginner' | 'intermediate' | 'advanced';
language?: string;
privacyLevel?: 'standard' | 'strict';
}
export function jarvisAgentCreate(
input: AgentCreateInput,
opts: JarvisClientOptions
): Promise<JarvisAgentDoc> {
return jarvisFetch('/jarvis/agents', { method: 'POST', body: JSON.stringify(input) }, opts);
}
export function jarvisAgentsList(
params: { limit?: number; offset?: number },
opts: JarvisClientOptions
): Promise<{ agents: JarvisAgentDoc[]; total: number }> {
const qs = new URLSearchParams();
if (params.limit !== undefined) qs.set('limit', String(params.limit));
if (params.offset !== undefined) qs.set('offset', String(params.offset));
const q = qs.toString();
return jarvisFetch(`/jarvis/agents${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
}
export function jarvisAgentDuplicate(
agentId: string,
opts: JarvisClientOptions
): Promise<JarvisAgentDoc> {
return jarvisFetch(`/jarvis/agents/${agentId}/duplicate`, { method: 'POST' }, opts);
}
// ── Sessions ───────────────────────────────────────────────────────────────
export interface JarvisSessionDoc {
id: string;
userId: string;
productId: string;
agentId: string;
mode: string;
status: 'active' | 'completed';
summary?: string;
coachingNotes?: string[];
skillMetrics?: Record<string, number>;
duration?: number;
messageCount: number;
createdAt: string;
completedAt: string | null;
}
export interface JarvisSessionStats {
totalSessions: number;
totalDurationMinutes: number;
currentStreak: number;
longestStreak: number;
perAgent: Record<string, { sessions: number; avgDuration: number }>;
}
export function jarvisSessionsList(
params: { limit?: number; offset?: number; agentId?: string },
opts: JarvisClientOptions
): Promise<{ sessions: JarvisSessionDoc[]; total: number }> {
const qs = new URLSearchParams();
if (params.limit !== undefined) qs.set('limit', String(params.limit));
if (params.offset !== undefined) qs.set('offset', String(params.offset));
if (params.agentId) qs.set('agentId', params.agentId);
const q = qs.toString();
return jarvisFetch(`/jarvis/sessions${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
}
export function jarvisSessionsGetStats(opts: JarvisClientOptions): Promise<JarvisSessionStats> {
return jarvisFetch('/jarvis/sessions/stats', { method: 'GET' }, opts);
}
// ── Memory ─────────────────────────────────────────────────────────────────
export interface JarvisMemoryDoc {
id: string;
agentId: string;
userId: string;
productId: string;
sessionId?: string;
type: 'skill_note' | 'preference' | 'goal' | 'context' | 'exercise';
content: string;
importance: number;
tags?: string[];
createdAt: string;
expiresAt?: string;
}
export function jarvisMemoryList(
agentId: string,
params: {
type?: string;
minImportance?: number;
limit?: number;
offset?: number;
},
opts: JarvisClientOptions
): Promise<{ memories: JarvisMemoryDoc[]; total: number }> {
const qs = new URLSearchParams();
if (params.type) qs.set('type', params.type);
if (params.minImportance !== undefined) qs.set('minImportance', String(params.minImportance));
if (params.limit !== undefined) qs.set('limit', String(params.limit));
if (params.offset !== undefined) qs.set('offset', String(params.offset));
const q = qs.toString();
return jarvisFetch(
`/jarvis/agents/${agentId}/memory${q ? `?${q}` : ''}`,
{ method: 'GET' },
opts
);
}
// ── Marketplace admin ─────────────────────────────────────────────────────
export function jarvisMarketplaceListPending(
opts: JarvisClientOptions
): Promise<{ listings: unknown[]; total: number }> {
return jarvisFetch('/marketplace/admin/pending', { method: 'GET' }, opts);
}
export function jarvisMarketplaceCertify(
listingId: string,
decision: { decision: 'approved' | 'rejected'; notes?: string },
opts: JarvisClientOptions
): Promise<unknown> {
const action = decision.decision === 'approved' ? 'approve' : 'reject';
return jarvisFetch(
`/marketplace/admin/${listingId}/${action}`,
{ method: 'POST', body: JSON.stringify(decision) },
opts
);
}
export function jarvisMarketplaceSuspend(
listingId: string,
reason: string,
opts: JarvisClientOptions
): Promise<unknown> {
return jarvisFetch(
`/marketplace/admin/${listingId}/suspend`,
{ method: 'POST', body: JSON.stringify({ reason }) },
opts
);
}
export function jarvisMarketplaceFeature(
listingId: string,
featured: boolean,
opts: JarvisClientOptions
): Promise<unknown> {
return jarvisFetch(
`/marketplace/admin/${listingId}/feature`,
{ method: 'POST', body: JSON.stringify({ featured }) },
opts
);
}
export function jarvisMemoryPrune(
agentId: string,
opts: JarvisClientOptions
): Promise<{ pruned: number }> {
return jarvisFetch(`/jarvis/agents/${agentId}/memory/prune`, { method: 'POST' }, opts);
}
export function jarvisMemoryGetContext(
agentId: string,
limit: number | undefined,
opts: JarvisClientOptions
): Promise<{ memories: JarvisMemoryDoc[]; count: number }> {
const qs = limit !== undefined ? `?limit=${limit}` : '';
return jarvisFetch(`/jarvis/agents/${agentId}/memory/context${qs}`, { method: 'GET' }, opts);
}