import type { IngestInteractionRequest } from '@insignia/iios-contracts'; import type { Message, InboxItem, InboxState, Ticket, TicketState, CallbackRequest, RouteBinding, RouteDecision, AiArtifact, AiJobResult, Meeting, MeetingActionItem } from './types'; export interface RestConfig { serviceUrl: string; token?: string; } /** REST/polling client for kernel reads and the native-send fallback. */ export class RestClient { constructor(private readonly config: RestConfig) {} private url(path: string): string { return `${this.config.serviceUrl.replace(/\/$/, '')}${path}`; } private headers(extra: Record = {}): Record { const h: Record = { 'content-type': 'application/json', ...extra }; if (this.config.token) h.authorization = `Bearer ${this.config.token}`; return h; } async getThreadMessages(threadId: string): Promise<{ threadId: string; messages: unknown[] }> { const r = await fetch(this.url(`/v1/threads/${threadId}/messages`), { headers: this.headers() }); if (!r.ok) throw new Error(`getThreadMessages ${r.status}`); return (await r.json()) as { threadId: string; messages: unknown[] }; } async sendMessage( threadId: string, content: string, opts?: { contentRef?: string; idempotencyKey?: string }, ): Promise { const r = await fetch(this.url(`/v1/threads/${threadId}/messages`), { method: 'POST', headers: this.headers(opts?.idempotencyKey ? { 'idempotency-key': opts.idempotencyKey } : {}), body: JSON.stringify({ content, contentRef: opts?.contentRef }), }); if (!r.ok) throw new Error(`sendMessage ${r.status}`); return (await r.json()) as Message; } async listInboxItems(state?: InboxState): Promise { const q = state ? `?state=${encodeURIComponent(state)}` : ''; const r = await fetch(this.url(`/v1/inbox/items${q}`), { headers: this.headers() }); if (!r.ok) throw new Error(`listInboxItems ${r.status}`); return (await r.json()) as InboxItem[]; } async patchInboxItem(id: string, body: { state: InboxState; reason?: string }): Promise { const r = await fetch(this.url(`/v1/inbox/items/${id}`), { method: 'PATCH', headers: this.headers(), body: JSON.stringify(body), }); if (!r.ok) throw new Error(`patchInboxItem ${r.status}`); return (await r.json()) as InboxItem; } // ─── support ────────────────────────────────────────────────── async createTicket(body: { subject: string; priority?: string; threadId?: string }): Promise { return this.post('/v1/support/tickets', body); } async escalate(threadId: string, subject?: string): Promise { return this.post('/v1/support/escalate', { threadId, subject }); } async listTickets(scope: 'mine' | 'assigned' = 'mine'): Promise { const r = await fetch(this.url(`/v1/support/tickets?scope=${scope}`), { headers: this.headers() }); if (!r.ok) throw new Error(`listTickets ${r.status}`); return (await r.json()) as Ticket[]; } async patchTicket(id: string, state: TicketState): Promise { const r = await fetch(this.url(`/v1/support/tickets/${id}`), { method: 'PATCH', headers: this.headers(), body: JSON.stringify({ state }), }); if (!r.ok) throw new Error(`patchTicket ${r.status}`); return (await r.json()) as Ticket; } async requestCallback(body: { preferChannel: string; preferredTime?: string; notes?: string; ticketId?: string }): Promise { return this.post('/v1/support/callbacks', body); } async createQueue(name: string): Promise<{ id: string }> { return this.post<{ id: string }>('/v1/support/queues', { name }); } async joinQueue(queueId: string): Promise { return this.post('/v1/support/queues/' + queueId + '/members', {}); } async joinDefaultQueue(): Promise { return this.post('/v1/support/agents/me/join', {}); } async setAvailability(state: string): Promise { const r = await fetch(this.url('/v1/support/members/me/availability'), { method: 'PATCH', headers: this.headers(), body: JSON.stringify({ state }), }); if (!r.ok) throw new Error(`setAvailability ${r.status}`); return r.json(); } // ─── routing (P6) ───────────────────────────────────────────── async createBinding(input: Partial): Promise { return this.post('/v1/routes/bindings', input); } async listBindings(): Promise { const r = await fetch(this.url('/v1/routes/bindings'), { headers: this.headers() }); if (!r.ok) throw new Error(`listBindings ${r.status}`); return (await r.json()) as RouteBinding[]; } async simulateRoute(input: { interactionId: string; originChannelType: string; originRef?: string }): Promise<{ simulationId: string; decisions: RouteDecision[] }> { return this.post('/v1/routes/simulate', input); } async listRouteDecisions(state?: string): Promise { const r = await fetch(this.url(`/v1/routes/decisions${state ? `?state=${state}` : ''}`), { headers: this.headers() }); if (!r.ok) throw new Error(`listRouteDecisions ${r.status}`); return (await r.json()) as RouteDecision[]; } async approveDecision(id: string): Promise { return this.post(`/v1/routes/decisions/${id}/approve`, {}); } async denyDecision(id: string): Promise { return this.post(`/v1/routes/decisions/${id}/deny`, {}); } // ─── AI proposal layer (P7) ─────────────────────────────────── async runAiJob(input: { interactionId: string; jobType: 'CLASSIFY' | 'SUMMARIZE' | 'EXTRACT' }): Promise { return this.post('/v1/ai/jobs', input); } async listArtifacts(opts?: { interactionId?: string; status?: string }): Promise { const q = new URLSearchParams(); if (opts?.interactionId) q.set('interactionId', opts.interactionId); if (opts?.status) q.set('status', opts.status); const qs = q.toString(); const r = await fetch(this.url(`/v1/ai/artifacts${qs ? `?${qs}` : ''}`), { headers: this.headers() }); if (!r.ok) throw new Error(`listArtifacts ${r.status}`); return (await r.json()) as AiArtifact[]; } async getArtifact(id: string): Promise { const r = await fetch(this.url(`/v1/ai/artifacts/${id}`), { headers: this.headers() }); if (!r.ok) throw new Error(`getArtifact ${r.status}`); return (await r.json()) as AiArtifact; } async acceptArtifact(id: string): Promise { return this.post(`/v1/ai/artifacts/${id}/accept`, {}); } async rejectArtifact(id: string): Promise { return this.post(`/v1/ai/artifacts/${id}/reject`, {}); } // ─── Calendar & Meeting (P8) ────────────────────────────────── async scheduleMeeting(input: { meetingType: string; title: string; startAt: string; endAt?: string; timezone?: string; attendees?: { userId: string; displayName?: string; role?: string; visibility?: string }[]; }): Promise { return this.post('/v1/calendar/meetings', input); } async requestMeeting(input: { requestedWindow: Record; meetingType?: string }, opts?: { fromCallback?: string; fromClaim?: string }): Promise { const q = new URLSearchParams(); if (opts?.fromCallback) q.set('fromCallback', opts.fromCallback); if (opts?.fromClaim) q.set('fromClaim', opts.fromClaim); const qs = q.toString(); return this.post(`/v1/calendar/requests${qs ? `?${qs}` : ''}`, input); } async listMeetings(): Promise { const r = await fetch(this.url('/v1/calendar/meetings'), { headers: this.headers() }); if (!r.ok) throw new Error(`listMeetings ${r.status}`); return (await r.json()) as Meeting[]; } async getMeeting(id: string): Promise { const r = await fetch(this.url(`/v1/calendar/meetings/${id}`), { headers: this.headers() }); if (!r.ok) throw new Error(`getMeeting ${r.status}`); return (await r.json()) as Meeting; } async setConsent(meetingId: string, actorRefId: string, status: string): Promise { return this.post(`/v1/calendar/meetings/${meetingId}/consent`, { actorRefId, status }); } async generateTranscript(meetingId: string): Promise { return this.post(`/v1/calendar/meetings/${meetingId}/transcript`, {}); } async summarizeMeeting(meetingId: string): Promise { return this.post(`/v1/calendar/meetings/${meetingId}/summarize`, {}); } async listActionItems(meetingId: string): Promise { const r = await fetch(this.url(`/v1/calendar/meetings/${meetingId}/action-items`), { headers: this.headers() }); if (!r.ok) throw new Error(`listActionItems ${r.status}`); return (await r.json()) as MeetingActionItem[]; } async connectCalendarProvider(): Promise<{ id: string }> { return this.post<{ id: string }>('/v1/calendar/providers', {}); } async syncCalendarProvider(id: string): Promise<{ pulled: number; cursor: string }> { return this.post(`/v1/calendar/providers/${id}/sync`, {}); } private async post(path: string, body: unknown): Promise { const r = await fetch(this.url(path), { method: 'POST', headers: this.headers(), body: JSON.stringify(body) }); if (!r.ok) throw new Error(`POST ${path} ${r.status}`); return (await r.json()) as T; } async ingest(body: IngestInteractionRequest, idempotencyKey: string): Promise { const r = await fetch(this.url('/v1/interactions/ingest'), { method: 'POST', headers: this.headers({ 'idempotency-key': idempotencyKey }), body: JSON.stringify(body), }); if (!r.ok) throw new Error(`ingest ${r.status}`); return r.json(); } }