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.
244 lines
7.7 KiB
TypeScript
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);
|
|
}
|