@usepanacea/client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ // src/token.ts
4
+ var TokenManager = class {
5
+ constructor(tenantId, apiBase) {
6
+ this.tenantId = tenantId;
7
+ this.apiBase = apiBase;
8
+ }
9
+ tenantId;
10
+ apiBase;
11
+ state = null;
12
+ refreshPromise = null;
13
+ async getToken() {
14
+ if (!this.state || Date.now() >= this.state.expiresAt - 6e4) {
15
+ if (!this.refreshPromise) {
16
+ this.refreshPromise = this.refresh().finally(() => {
17
+ this.refreshPromise = null;
18
+ });
19
+ }
20
+ return this.refreshPromise;
21
+ }
22
+ return this.state.token;
23
+ }
24
+ async refresh() {
25
+ const res = await fetch(`${this.apiBase}/api/v1/auth/widget-token`, {
26
+ method: "POST",
27
+ headers: { "Content-Type": "application/json" },
28
+ body: JSON.stringify({ tenantId: this.tenantId })
29
+ });
30
+ if (!res.ok) {
31
+ throw new Error(`Panacea: token refresh failed (${res.status})`);
32
+ }
33
+ const data = await res.json();
34
+ this.state = {
35
+ token: data.token,
36
+ expiresAt: new Date(data.expiresAt).getTime()
37
+ };
38
+ return this.state.token;
39
+ }
40
+ };
41
+
42
+ // src/api.ts
43
+ async function queryRest(opts) {
44
+ const res = await fetch(`${opts.apiBase}/api/v1/query`, {
45
+ method: "POST",
46
+ headers: {
47
+ "Content-Type": "application/json",
48
+ Authorization: `Bearer ${opts.token}`,
49
+ ...opts.extraHeaders
50
+ },
51
+ body: JSON.stringify({ question: opts.question, sessionId: opts.sessionId })
52
+ });
53
+ if (!res.ok) {
54
+ const err = await res.json().catch(() => null);
55
+ throw new Error(
56
+ err?.error?.message ?? `Panacea: query failed (${res.status})`
57
+ );
58
+ }
59
+ return res.json();
60
+ }
61
+ async function querySSE(opts, onToken) {
62
+ const res = await fetch(`${opts.apiBase}/api/v1/query`, {
63
+ method: "POST",
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ Authorization: `Bearer ${opts.token}`,
67
+ Accept: "text/event-stream",
68
+ ...opts.extraHeaders
69
+ },
70
+ body: JSON.stringify({ question: opts.question, sessionId: opts.sessionId })
71
+ });
72
+ if (!res.ok || !res.body) {
73
+ throw new Error(`Panacea: SSE query failed (${res.status})`);
74
+ }
75
+ const reader = res.body.getReader();
76
+ const decoder = new TextDecoder();
77
+ let buffer = "";
78
+ let finalResponse = null;
79
+ while (true) {
80
+ const { done, value } = await reader.read();
81
+ if (done) break;
82
+ buffer += decoder.decode(value, { stream: true });
83
+ const lines = buffer.split("\n");
84
+ buffer = lines.pop() ?? "";
85
+ for (const line of lines) {
86
+ if (!line.startsWith("data: ")) continue;
87
+ const raw = line.slice(6).trim();
88
+ if (!raw || raw === "[DONE]") continue;
89
+ try {
90
+ const frame = JSON.parse(raw);
91
+ if (frame.type === "token" && frame.delta) {
92
+ onToken(frame.delta);
93
+ } else if (frame.type === "done") {
94
+ finalResponse = {
95
+ answer: frame.answer ?? "",
96
+ sources: frame.sources ?? [],
97
+ confidence: frame.confidence ?? 0,
98
+ flaggedForReview: frame.flaggedForReview ?? false,
99
+ sessionId: frame.sessionId ?? "",
100
+ escalated: frame.escalated
101
+ };
102
+ } else if (frame.type === "error") {
103
+ throw new Error(frame.message ?? "SSE stream error");
104
+ }
105
+ } catch {
106
+ }
107
+ }
108
+ }
109
+ if (!finalResponse) throw new Error("Panacea: SSE stream ended without done frame");
110
+ return finalResponse;
111
+ }
112
+ async function fetchRecentSessionId(customerToken, widgetToken, apiBase) {
113
+ const url = new URL(`${apiBase}/api/v1/sessions/resume`);
114
+ url.searchParams.set("customerToken", customerToken);
115
+ const res = await fetch(url.toString(), {
116
+ headers: { Authorization: `Bearer ${widgetToken}` }
117
+ });
118
+ if (!res.ok) return null;
119
+ const data = await res.json();
120
+ return data?.session?.sessionId ?? null;
121
+ }
122
+ async function fetchSessionTurns(sessionId, widgetToken, apiBase) {
123
+ const res = await fetch(`${apiBase}/api/v1/sessions/${sessionId}/turns`, {
124
+ headers: { Authorization: `Bearer ${widgetToken}` }
125
+ });
126
+ if (!res.ok) return [];
127
+ const data = await res.json();
128
+ return data?.data?.turns ?? data?.turns ?? [];
129
+ }
130
+ async function postReaction(sessionId, reaction, citedPaths, token, apiBase) {
131
+ await fetch(`${apiBase}/api/v1/sessions/${sessionId}/reaction`, {
132
+ method: "PATCH",
133
+ headers: {
134
+ "Content-Type": "application/json",
135
+ Authorization: `Bearer ${token}`
136
+ },
137
+ body: JSON.stringify({ reaction, citedPaths })
138
+ });
139
+ }
140
+ async function postCustomerMessage(escalationId, content, token, apiBase) {
141
+ await fetch(`${apiBase}/api/v1/inbox/${escalationId}/customer-message`, {
142
+ method: "POST",
143
+ headers: {
144
+ "Content-Type": "application/json",
145
+ Authorization: `Bearer ${token}`
146
+ },
147
+ body: JSON.stringify({ content })
148
+ });
149
+ }
150
+ async function fetchLiveMessages(escalationId, token, apiBase) {
151
+ const res = await fetch(`${apiBase}/api/v1/inbox/${escalationId}/messages`, {
152
+ headers: { Authorization: `Bearer ${token}` }
153
+ });
154
+ if (!res.ok) return [];
155
+ const data = await res.json();
156
+ return data?.data?.messages ?? data?.messages ?? [];
157
+ }
158
+ async function patchPageContext(sessionId, ctx, token, apiBase) {
159
+ await fetch(`${apiBase}/api/v1/sessions/${sessionId}`, {
160
+ method: "PATCH",
161
+ headers: {
162
+ "Content-Type": "application/json",
163
+ Authorization: `Bearer ${token}`
164
+ },
165
+ body: JSON.stringify({ pageContext: ctx })
166
+ });
167
+ }
168
+ async function postEscalate(sessionId, reason, token, apiBase) {
169
+ const res = await fetch(`${apiBase}/api/v1/sessions/${sessionId}/escalate`, {
170
+ method: "POST",
171
+ headers: {
172
+ "Content-Type": "application/json",
173
+ Authorization: `Bearer ${token}`
174
+ },
175
+ body: JSON.stringify({ reason })
176
+ });
177
+ if (!res.ok) return null;
178
+ return res.json();
179
+ }
180
+
181
+ exports.TokenManager = TokenManager;
182
+ exports.fetchLiveMessages = fetchLiveMessages;
183
+ exports.fetchRecentSessionId = fetchRecentSessionId;
184
+ exports.fetchSessionTurns = fetchSessionTurns;
185
+ exports.patchPageContext = patchPageContext;
186
+ exports.postCustomerMessage = postCustomerMessage;
187
+ exports.postEscalate = postEscalate;
188
+ exports.postReaction = postReaction;
189
+ exports.queryRest = queryRest;
190
+ exports.querySSE = querySSE;
191
+ //# sourceMappingURL=index.cjs.map
192
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/token.ts","../src/api.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["interface TokenState {\n token: string;\n /** unix ms */\n expiresAt: number;\n}\n\n/**\n * Manages the short-lived widget JWT (15 min TTL).\n * Auto-refreshes 60 s before expiry. Concurrent callers share one in-flight\n * refresh promise so the token is only fetched once even under parallel requests.\n */\nexport class TokenManager {\n private state: TokenState | null = null;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(\n private readonly tenantId: string,\n private readonly apiBase: string,\n ) {}\n\n async getToken(): Promise<string> {\n if (!this.state || Date.now() >= this.state.expiresAt - 60_000) {\n if (!this.refreshPromise) {\n this.refreshPromise = this.refresh().finally(() => {\n this.refreshPromise = null;\n });\n }\n return this.refreshPromise;\n }\n return this.state.token;\n }\n\n private async refresh(): Promise<string> {\n const res = await fetch(`${this.apiBase}/api/v1/auth/widget-token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ tenantId: this.tenantId }),\n });\n\n if (!res.ok) {\n throw new Error(`Panacea: token refresh failed (${res.status})`);\n }\n\n const data = (await res.json()) as { token: string; expiresAt: string };\n this.state = {\n token: data.token,\n expiresAt: new Date(data.expiresAt).getTime(),\n };\n return this.state.token;\n }\n}\n","import type { QueryResponse, HistoryTurn, LiveMessage } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Query\n// ---------------------------------------------------------------------------\n\ninterface QueryOptions {\n question: string;\n sessionId: string | null;\n token: string;\n apiBase: string;\n /** Extra headers to include (e.g. x-customer-token). */\n extraHeaders?: Record<string, string>;\n}\n\n/**\n * Send a query via REST POST /api/v1/query.\n * Used as fallback when SSE is not available.\n */\nexport async function queryRest(opts: QueryOptions): Promise<QueryResponse> {\n const res = await fetch(`${opts.apiBase}/api/v1/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${opts.token}`,\n ...opts.extraHeaders,\n },\n body: JSON.stringify({ question: opts.question, sessionId: opts.sessionId }),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => null);\n throw new Error(\n (err as { error?: { message?: string } })?.error?.message ??\n `Panacea: query failed (${res.status})`,\n );\n }\n\n return res.json() as Promise<QueryResponse>;\n}\n\n/**\n * Send a query via Server-Sent Events streaming.\n * Calls `onToken` with each streamed delta, then resolves with the final response.\n */\nexport async function querySSE(\n opts: QueryOptions,\n onToken: (delta: string) => void,\n): Promise<QueryResponse> {\n const res = await fetch(`${opts.apiBase}/api/v1/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${opts.token}`,\n Accept: \"text/event-stream\",\n ...opts.extraHeaders,\n },\n body: JSON.stringify({ question: opts.question, sessionId: opts.sessionId }),\n });\n\n if (!res.ok || !res.body) {\n throw new Error(`Panacea: SSE query failed (${res.status})`);\n }\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let finalResponse: QueryResponse | null = null;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n const raw = line.slice(6).trim();\n if (!raw || raw === \"[DONE]\") continue;\n try {\n const frame = JSON.parse(raw) as {\n type: \"token\" | \"done\" | \"error\";\n delta?: string;\n answer?: string;\n sources?: QueryResponse[\"sources\"];\n confidence?: number;\n sessionId?: string;\n flaggedForReview?: boolean;\n escalated?: boolean;\n message?: string;\n };\n if (frame.type === \"token\" && frame.delta) {\n onToken(frame.delta);\n } else if (frame.type === \"done\") {\n finalResponse = {\n answer: frame.answer ?? \"\",\n sources: frame.sources ?? [],\n confidence: frame.confidence ?? 0,\n flaggedForReview: frame.flaggedForReview ?? false,\n sessionId: frame.sessionId ?? \"\",\n escalated: frame.escalated,\n };\n } else if (frame.type === \"error\") {\n throw new Error(frame.message ?? \"SSE stream error\");\n }\n } catch {\n // ignore malformed frames\n }\n }\n }\n\n if (!finalResponse) throw new Error(\"Panacea: SSE stream ended without done frame\");\n return finalResponse;\n}\n\n// ---------------------------------------------------------------------------\n// Session helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch the most recent resumable session for an identified customer.\n * Returns the sessionId if found within the resume window, null otherwise.\n */\nexport async function fetchRecentSessionId(\n customerToken: string,\n widgetToken: string,\n apiBase: string,\n): Promise<string | null> {\n const url = new URL(`${apiBase}/api/v1/sessions/resume`);\n url.searchParams.set(\"customerToken\", customerToken);\n\n const res = await fetch(url.toString(), {\n headers: { Authorization: `Bearer ${widgetToken}` },\n });\n\n if (!res.ok) return null;\n\n const data = (await res.json()) as { session: { sessionId: string } | null };\n return data?.session?.sessionId ?? null;\n}\n\n/**\n * Fetch all turns for a session (read-only history view).\n */\nexport async function fetchSessionTurns(\n sessionId: string,\n widgetToken: string,\n apiBase: string,\n): Promise<HistoryTurn[]> {\n const res = await fetch(`${apiBase}/api/v1/sessions/${sessionId}/turns`, {\n headers: { Authorization: `Bearer ${widgetToken}` },\n });\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as {\n data?: { turns?: HistoryTurn[] };\n turns?: HistoryTurn[];\n };\n return data?.data?.turns ?? data?.turns ?? [];\n}\n\n// ---------------------------------------------------------------------------\n// Reaction\n// ---------------------------------------------------------------------------\n\n/**\n * POST a reaction (thumbs up/down) for a turn.\n * D-013: explicit reaction capture via PATCH /api/v1/sessions/:id/reaction.\n */\nexport async function postReaction(\n sessionId: string,\n reaction: \"helpful\" | \"unhelpful\",\n citedPaths: string[],\n token: string,\n apiBase: string,\n): Promise<void> {\n await fetch(`${apiBase}/api/v1/sessions/${sessionId}/reaction`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ reaction, citedPaths }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Live handover helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Send a customer message during a live handover session.\n */\nexport async function postCustomerMessage(\n escalationId: string,\n content: string,\n token: string,\n apiBase: string,\n): Promise<void> {\n await fetch(`${apiBase}/api/v1/inbox/${escalationId}/customer-message`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ content }),\n });\n}\n\n/**\n * Fetch live messages from a claimed escalation (human agent replies).\n * Used to poll for new messages during live handover.\n */\nexport async function fetchLiveMessages(\n escalationId: string,\n token: string,\n apiBase: string,\n): Promise<LiveMessage[]> {\n const res = await fetch(`${apiBase}/api/v1/inbox/${escalationId}/messages`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as {\n data?: { messages?: LiveMessage[] };\n messages?: LiveMessage[];\n };\n return data?.data?.messages ?? data?.messages ?? [];\n}\n\n// ---------------------------------------------------------------------------\n// Page context\n// ---------------------------------------------------------------------------\n\n/**\n * Update the pageContext for a session (SPA navigation support).\n */\nexport async function patchPageContext(\n sessionId: string,\n ctx: Record<string, unknown>,\n token: string,\n apiBase: string,\n): Promise<void> {\n await fetch(`${apiBase}/api/v1/sessions/${sessionId}`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ pageContext: ctx }),\n });\n}\n\n/**\n * Trigger escalation to a human agent.\n * Returns the raw JSON response or null on failure.\n */\nexport async function postEscalate(\n sessionId: string,\n reason: string,\n token: string,\n apiBase: string,\n): Promise<unknown> {\n const res = await fetch(`${apiBase}/api/v1/sessions/${sessionId}/escalate`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ reason }),\n });\n if (!res.ok) return null;\n return res.json();\n}\n"]}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Manages the short-lived widget JWT (15 min TTL).
3
+ * Auto-refreshes 60 s before expiry. Concurrent callers share one in-flight
4
+ * refresh promise so the token is only fetched once even under parallel requests.
5
+ */
6
+ declare class TokenManager {
7
+ private readonly tenantId;
8
+ private readonly apiBase;
9
+ private state;
10
+ private refreshPromise;
11
+ constructor(tenantId: string, apiBase: string);
12
+ getToken(): Promise<string>;
13
+ private refresh;
14
+ }
15
+
16
+ interface Source {
17
+ path: string;
18
+ title: string;
19
+ confidence: number;
20
+ excerpt?: string;
21
+ /** Original public URL from wiki entry frontmatter (sourceUrl). Present only for URL-ingested entries. */
22
+ url?: string;
23
+ }
24
+ interface Turn {
25
+ role: "user" | "assistant";
26
+ content: string;
27
+ confidence?: number;
28
+ sources?: Source[];
29
+ flaggedForReview?: boolean;
30
+ reaction?: "helpful" | "unhelpful" | null;
31
+ /** True when this answer triggered an auto-escalation */
32
+ escalated?: boolean;
33
+ /** True when this message came from a live human agent */
34
+ isLiveAgent?: boolean;
35
+ }
36
+ interface QueryResponse {
37
+ answer: string;
38
+ sources: Source[];
39
+ confidence: number;
40
+ flaggedForReview: boolean;
41
+ sessionId: string;
42
+ escalated?: boolean;
43
+ escalationReason?: string;
44
+ }
45
+ interface EscalateResponse {
46
+ data?: {
47
+ escalationId?: string;
48
+ handoverMode?: string;
49
+ handoverUrl?: string;
50
+ };
51
+ escalationId?: string;
52
+ handoverMode?: string;
53
+ handoverUrl?: string;
54
+ }
55
+ interface HistoryTurn {
56
+ id: string;
57
+ query: string;
58
+ response: string;
59
+ createdAt: string;
60
+ }
61
+ interface HistorySession {
62
+ sessionId: string;
63
+ createdAt: string;
64
+ updatedAt?: string;
65
+ /** Preview of the first message in the session */
66
+ preview?: string;
67
+ turnCount?: number;
68
+ }
69
+ interface LiveMessage {
70
+ id: string;
71
+ senderRole?: "agent" | "customer";
72
+ content: string;
73
+ createdAt: string;
74
+ }
75
+
76
+ interface QueryOptions {
77
+ question: string;
78
+ sessionId: string | null;
79
+ token: string;
80
+ apiBase: string;
81
+ /** Extra headers to include (e.g. x-customer-token). */
82
+ extraHeaders?: Record<string, string>;
83
+ }
84
+ /**
85
+ * Send a query via REST POST /api/v1/query.
86
+ * Used as fallback when SSE is not available.
87
+ */
88
+ declare function queryRest(opts: QueryOptions): Promise<QueryResponse>;
89
+ /**
90
+ * Send a query via Server-Sent Events streaming.
91
+ * Calls `onToken` with each streamed delta, then resolves with the final response.
92
+ */
93
+ declare function querySSE(opts: QueryOptions, onToken: (delta: string) => void): Promise<QueryResponse>;
94
+ /**
95
+ * Fetch the most recent resumable session for an identified customer.
96
+ * Returns the sessionId if found within the resume window, null otherwise.
97
+ */
98
+ declare function fetchRecentSessionId(customerToken: string, widgetToken: string, apiBase: string): Promise<string | null>;
99
+ /**
100
+ * Fetch all turns for a session (read-only history view).
101
+ */
102
+ declare function fetchSessionTurns(sessionId: string, widgetToken: string, apiBase: string): Promise<HistoryTurn[]>;
103
+ /**
104
+ * POST a reaction (thumbs up/down) for a turn.
105
+ * D-013: explicit reaction capture via PATCH /api/v1/sessions/:id/reaction.
106
+ */
107
+ declare function postReaction(sessionId: string, reaction: "helpful" | "unhelpful", citedPaths: string[], token: string, apiBase: string): Promise<void>;
108
+ /**
109
+ * Send a customer message during a live handover session.
110
+ */
111
+ declare function postCustomerMessage(escalationId: string, content: string, token: string, apiBase: string): Promise<void>;
112
+ /**
113
+ * Fetch live messages from a claimed escalation (human agent replies).
114
+ * Used to poll for new messages during live handover.
115
+ */
116
+ declare function fetchLiveMessages(escalationId: string, token: string, apiBase: string): Promise<LiveMessage[]>;
117
+ /**
118
+ * Update the pageContext for a session (SPA navigation support).
119
+ */
120
+ declare function patchPageContext(sessionId: string, ctx: Record<string, unknown>, token: string, apiBase: string): Promise<void>;
121
+ /**
122
+ * Trigger escalation to a human agent.
123
+ * Returns the raw JSON response or null on failure.
124
+ */
125
+ declare function postEscalate(sessionId: string, reason: string, token: string, apiBase: string): Promise<unknown>;
126
+
127
+ export { type EscalateResponse, type HistorySession, type HistoryTurn, type LiveMessage, type QueryResponse, type Source, TokenManager, type Turn, fetchLiveMessages, fetchRecentSessionId, fetchSessionTurns, patchPageContext, postCustomerMessage, postEscalate, postReaction, queryRest, querySSE };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Manages the short-lived widget JWT (15 min TTL).
3
+ * Auto-refreshes 60 s before expiry. Concurrent callers share one in-flight
4
+ * refresh promise so the token is only fetched once even under parallel requests.
5
+ */
6
+ declare class TokenManager {
7
+ private readonly tenantId;
8
+ private readonly apiBase;
9
+ private state;
10
+ private refreshPromise;
11
+ constructor(tenantId: string, apiBase: string);
12
+ getToken(): Promise<string>;
13
+ private refresh;
14
+ }
15
+
16
+ interface Source {
17
+ path: string;
18
+ title: string;
19
+ confidence: number;
20
+ excerpt?: string;
21
+ /** Original public URL from wiki entry frontmatter (sourceUrl). Present only for URL-ingested entries. */
22
+ url?: string;
23
+ }
24
+ interface Turn {
25
+ role: "user" | "assistant";
26
+ content: string;
27
+ confidence?: number;
28
+ sources?: Source[];
29
+ flaggedForReview?: boolean;
30
+ reaction?: "helpful" | "unhelpful" | null;
31
+ /** True when this answer triggered an auto-escalation */
32
+ escalated?: boolean;
33
+ /** True when this message came from a live human agent */
34
+ isLiveAgent?: boolean;
35
+ }
36
+ interface QueryResponse {
37
+ answer: string;
38
+ sources: Source[];
39
+ confidence: number;
40
+ flaggedForReview: boolean;
41
+ sessionId: string;
42
+ escalated?: boolean;
43
+ escalationReason?: string;
44
+ }
45
+ interface EscalateResponse {
46
+ data?: {
47
+ escalationId?: string;
48
+ handoverMode?: string;
49
+ handoverUrl?: string;
50
+ };
51
+ escalationId?: string;
52
+ handoverMode?: string;
53
+ handoverUrl?: string;
54
+ }
55
+ interface HistoryTurn {
56
+ id: string;
57
+ query: string;
58
+ response: string;
59
+ createdAt: string;
60
+ }
61
+ interface HistorySession {
62
+ sessionId: string;
63
+ createdAt: string;
64
+ updatedAt?: string;
65
+ /** Preview of the first message in the session */
66
+ preview?: string;
67
+ turnCount?: number;
68
+ }
69
+ interface LiveMessage {
70
+ id: string;
71
+ senderRole?: "agent" | "customer";
72
+ content: string;
73
+ createdAt: string;
74
+ }
75
+
76
+ interface QueryOptions {
77
+ question: string;
78
+ sessionId: string | null;
79
+ token: string;
80
+ apiBase: string;
81
+ /** Extra headers to include (e.g. x-customer-token). */
82
+ extraHeaders?: Record<string, string>;
83
+ }
84
+ /**
85
+ * Send a query via REST POST /api/v1/query.
86
+ * Used as fallback when SSE is not available.
87
+ */
88
+ declare function queryRest(opts: QueryOptions): Promise<QueryResponse>;
89
+ /**
90
+ * Send a query via Server-Sent Events streaming.
91
+ * Calls `onToken` with each streamed delta, then resolves with the final response.
92
+ */
93
+ declare function querySSE(opts: QueryOptions, onToken: (delta: string) => void): Promise<QueryResponse>;
94
+ /**
95
+ * Fetch the most recent resumable session for an identified customer.
96
+ * Returns the sessionId if found within the resume window, null otherwise.
97
+ */
98
+ declare function fetchRecentSessionId(customerToken: string, widgetToken: string, apiBase: string): Promise<string | null>;
99
+ /**
100
+ * Fetch all turns for a session (read-only history view).
101
+ */
102
+ declare function fetchSessionTurns(sessionId: string, widgetToken: string, apiBase: string): Promise<HistoryTurn[]>;
103
+ /**
104
+ * POST a reaction (thumbs up/down) for a turn.
105
+ * D-013: explicit reaction capture via PATCH /api/v1/sessions/:id/reaction.
106
+ */
107
+ declare function postReaction(sessionId: string, reaction: "helpful" | "unhelpful", citedPaths: string[], token: string, apiBase: string): Promise<void>;
108
+ /**
109
+ * Send a customer message during a live handover session.
110
+ */
111
+ declare function postCustomerMessage(escalationId: string, content: string, token: string, apiBase: string): Promise<void>;
112
+ /**
113
+ * Fetch live messages from a claimed escalation (human agent replies).
114
+ * Used to poll for new messages during live handover.
115
+ */
116
+ declare function fetchLiveMessages(escalationId: string, token: string, apiBase: string): Promise<LiveMessage[]>;
117
+ /**
118
+ * Update the pageContext for a session (SPA navigation support).
119
+ */
120
+ declare function patchPageContext(sessionId: string, ctx: Record<string, unknown>, token: string, apiBase: string): Promise<void>;
121
+ /**
122
+ * Trigger escalation to a human agent.
123
+ * Returns the raw JSON response or null on failure.
124
+ */
125
+ declare function postEscalate(sessionId: string, reason: string, token: string, apiBase: string): Promise<unknown>;
126
+
127
+ export { type EscalateResponse, type HistorySession, type HistoryTurn, type LiveMessage, type QueryResponse, type Source, TokenManager, type Turn, fetchLiveMessages, fetchRecentSessionId, fetchSessionTurns, patchPageContext, postCustomerMessage, postEscalate, postReaction, queryRest, querySSE };
package/dist/index.js ADDED
@@ -0,0 +1,181 @@
1
+ // src/token.ts
2
+ var TokenManager = class {
3
+ constructor(tenantId, apiBase) {
4
+ this.tenantId = tenantId;
5
+ this.apiBase = apiBase;
6
+ }
7
+ tenantId;
8
+ apiBase;
9
+ state = null;
10
+ refreshPromise = null;
11
+ async getToken() {
12
+ if (!this.state || Date.now() >= this.state.expiresAt - 6e4) {
13
+ if (!this.refreshPromise) {
14
+ this.refreshPromise = this.refresh().finally(() => {
15
+ this.refreshPromise = null;
16
+ });
17
+ }
18
+ return this.refreshPromise;
19
+ }
20
+ return this.state.token;
21
+ }
22
+ async refresh() {
23
+ const res = await fetch(`${this.apiBase}/api/v1/auth/widget-token`, {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify({ tenantId: this.tenantId })
27
+ });
28
+ if (!res.ok) {
29
+ throw new Error(`Panacea: token refresh failed (${res.status})`);
30
+ }
31
+ const data = await res.json();
32
+ this.state = {
33
+ token: data.token,
34
+ expiresAt: new Date(data.expiresAt).getTime()
35
+ };
36
+ return this.state.token;
37
+ }
38
+ };
39
+
40
+ // src/api.ts
41
+ async function queryRest(opts) {
42
+ const res = await fetch(`${opts.apiBase}/api/v1/query`, {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ Authorization: `Bearer ${opts.token}`,
47
+ ...opts.extraHeaders
48
+ },
49
+ body: JSON.stringify({ question: opts.question, sessionId: opts.sessionId })
50
+ });
51
+ if (!res.ok) {
52
+ const err = await res.json().catch(() => null);
53
+ throw new Error(
54
+ err?.error?.message ?? `Panacea: query failed (${res.status})`
55
+ );
56
+ }
57
+ return res.json();
58
+ }
59
+ async function querySSE(opts, onToken) {
60
+ const res = await fetch(`${opts.apiBase}/api/v1/query`, {
61
+ method: "POST",
62
+ headers: {
63
+ "Content-Type": "application/json",
64
+ Authorization: `Bearer ${opts.token}`,
65
+ Accept: "text/event-stream",
66
+ ...opts.extraHeaders
67
+ },
68
+ body: JSON.stringify({ question: opts.question, sessionId: opts.sessionId })
69
+ });
70
+ if (!res.ok || !res.body) {
71
+ throw new Error(`Panacea: SSE query failed (${res.status})`);
72
+ }
73
+ const reader = res.body.getReader();
74
+ const decoder = new TextDecoder();
75
+ let buffer = "";
76
+ let finalResponse = null;
77
+ while (true) {
78
+ const { done, value } = await reader.read();
79
+ if (done) break;
80
+ buffer += decoder.decode(value, { stream: true });
81
+ const lines = buffer.split("\n");
82
+ buffer = lines.pop() ?? "";
83
+ for (const line of lines) {
84
+ if (!line.startsWith("data: ")) continue;
85
+ const raw = line.slice(6).trim();
86
+ if (!raw || raw === "[DONE]") continue;
87
+ try {
88
+ const frame = JSON.parse(raw);
89
+ if (frame.type === "token" && frame.delta) {
90
+ onToken(frame.delta);
91
+ } else if (frame.type === "done") {
92
+ finalResponse = {
93
+ answer: frame.answer ?? "",
94
+ sources: frame.sources ?? [],
95
+ confidence: frame.confidence ?? 0,
96
+ flaggedForReview: frame.flaggedForReview ?? false,
97
+ sessionId: frame.sessionId ?? "",
98
+ escalated: frame.escalated
99
+ };
100
+ } else if (frame.type === "error") {
101
+ throw new Error(frame.message ?? "SSE stream error");
102
+ }
103
+ } catch {
104
+ }
105
+ }
106
+ }
107
+ if (!finalResponse) throw new Error("Panacea: SSE stream ended without done frame");
108
+ return finalResponse;
109
+ }
110
+ async function fetchRecentSessionId(customerToken, widgetToken, apiBase) {
111
+ const url = new URL(`${apiBase}/api/v1/sessions/resume`);
112
+ url.searchParams.set("customerToken", customerToken);
113
+ const res = await fetch(url.toString(), {
114
+ headers: { Authorization: `Bearer ${widgetToken}` }
115
+ });
116
+ if (!res.ok) return null;
117
+ const data = await res.json();
118
+ return data?.session?.sessionId ?? null;
119
+ }
120
+ async function fetchSessionTurns(sessionId, widgetToken, apiBase) {
121
+ const res = await fetch(`${apiBase}/api/v1/sessions/${sessionId}/turns`, {
122
+ headers: { Authorization: `Bearer ${widgetToken}` }
123
+ });
124
+ if (!res.ok) return [];
125
+ const data = await res.json();
126
+ return data?.data?.turns ?? data?.turns ?? [];
127
+ }
128
+ async function postReaction(sessionId, reaction, citedPaths, token, apiBase) {
129
+ await fetch(`${apiBase}/api/v1/sessions/${sessionId}/reaction`, {
130
+ method: "PATCH",
131
+ headers: {
132
+ "Content-Type": "application/json",
133
+ Authorization: `Bearer ${token}`
134
+ },
135
+ body: JSON.stringify({ reaction, citedPaths })
136
+ });
137
+ }
138
+ async function postCustomerMessage(escalationId, content, token, apiBase) {
139
+ await fetch(`${apiBase}/api/v1/inbox/${escalationId}/customer-message`, {
140
+ method: "POST",
141
+ headers: {
142
+ "Content-Type": "application/json",
143
+ Authorization: `Bearer ${token}`
144
+ },
145
+ body: JSON.stringify({ content })
146
+ });
147
+ }
148
+ async function fetchLiveMessages(escalationId, token, apiBase) {
149
+ const res = await fetch(`${apiBase}/api/v1/inbox/${escalationId}/messages`, {
150
+ headers: { Authorization: `Bearer ${token}` }
151
+ });
152
+ if (!res.ok) return [];
153
+ const data = await res.json();
154
+ return data?.data?.messages ?? data?.messages ?? [];
155
+ }
156
+ async function patchPageContext(sessionId, ctx, token, apiBase) {
157
+ await fetch(`${apiBase}/api/v1/sessions/${sessionId}`, {
158
+ method: "PATCH",
159
+ headers: {
160
+ "Content-Type": "application/json",
161
+ Authorization: `Bearer ${token}`
162
+ },
163
+ body: JSON.stringify({ pageContext: ctx })
164
+ });
165
+ }
166
+ async function postEscalate(sessionId, reason, token, apiBase) {
167
+ const res = await fetch(`${apiBase}/api/v1/sessions/${sessionId}/escalate`, {
168
+ method: "POST",
169
+ headers: {
170
+ "Content-Type": "application/json",
171
+ Authorization: `Bearer ${token}`
172
+ },
173
+ body: JSON.stringify({ reason })
174
+ });
175
+ if (!res.ok) return null;
176
+ return res.json();
177
+ }
178
+
179
+ export { TokenManager, fetchLiveMessages, fetchRecentSessionId, fetchSessionTurns, patchPageContext, postCustomerMessage, postEscalate, postReaction, queryRest, querySSE };
180
+ //# sourceMappingURL=index.js.map
181
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/token.ts","../src/api.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["interface TokenState {\n token: string;\n /** unix ms */\n expiresAt: number;\n}\n\n/**\n * Manages the short-lived widget JWT (15 min TTL).\n * Auto-refreshes 60 s before expiry. Concurrent callers share one in-flight\n * refresh promise so the token is only fetched once even under parallel requests.\n */\nexport class TokenManager {\n private state: TokenState | null = null;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(\n private readonly tenantId: string,\n private readonly apiBase: string,\n ) {}\n\n async getToken(): Promise<string> {\n if (!this.state || Date.now() >= this.state.expiresAt - 60_000) {\n if (!this.refreshPromise) {\n this.refreshPromise = this.refresh().finally(() => {\n this.refreshPromise = null;\n });\n }\n return this.refreshPromise;\n }\n return this.state.token;\n }\n\n private async refresh(): Promise<string> {\n const res = await fetch(`${this.apiBase}/api/v1/auth/widget-token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ tenantId: this.tenantId }),\n });\n\n if (!res.ok) {\n throw new Error(`Panacea: token refresh failed (${res.status})`);\n }\n\n const data = (await res.json()) as { token: string; expiresAt: string };\n this.state = {\n token: data.token,\n expiresAt: new Date(data.expiresAt).getTime(),\n };\n return this.state.token;\n }\n}\n","import type { QueryResponse, HistoryTurn, LiveMessage } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Query\n// ---------------------------------------------------------------------------\n\ninterface QueryOptions {\n question: string;\n sessionId: string | null;\n token: string;\n apiBase: string;\n /** Extra headers to include (e.g. x-customer-token). */\n extraHeaders?: Record<string, string>;\n}\n\n/**\n * Send a query via REST POST /api/v1/query.\n * Used as fallback when SSE is not available.\n */\nexport async function queryRest(opts: QueryOptions): Promise<QueryResponse> {\n const res = await fetch(`${opts.apiBase}/api/v1/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${opts.token}`,\n ...opts.extraHeaders,\n },\n body: JSON.stringify({ question: opts.question, sessionId: opts.sessionId }),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => null);\n throw new Error(\n (err as { error?: { message?: string } })?.error?.message ??\n `Panacea: query failed (${res.status})`,\n );\n }\n\n return res.json() as Promise<QueryResponse>;\n}\n\n/**\n * Send a query via Server-Sent Events streaming.\n * Calls `onToken` with each streamed delta, then resolves with the final response.\n */\nexport async function querySSE(\n opts: QueryOptions,\n onToken: (delta: string) => void,\n): Promise<QueryResponse> {\n const res = await fetch(`${opts.apiBase}/api/v1/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${opts.token}`,\n Accept: \"text/event-stream\",\n ...opts.extraHeaders,\n },\n body: JSON.stringify({ question: opts.question, sessionId: opts.sessionId }),\n });\n\n if (!res.ok || !res.body) {\n throw new Error(`Panacea: SSE query failed (${res.status})`);\n }\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let finalResponse: QueryResponse | null = null;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n const raw = line.slice(6).trim();\n if (!raw || raw === \"[DONE]\") continue;\n try {\n const frame = JSON.parse(raw) as {\n type: \"token\" | \"done\" | \"error\";\n delta?: string;\n answer?: string;\n sources?: QueryResponse[\"sources\"];\n confidence?: number;\n sessionId?: string;\n flaggedForReview?: boolean;\n escalated?: boolean;\n message?: string;\n };\n if (frame.type === \"token\" && frame.delta) {\n onToken(frame.delta);\n } else if (frame.type === \"done\") {\n finalResponse = {\n answer: frame.answer ?? \"\",\n sources: frame.sources ?? [],\n confidence: frame.confidence ?? 0,\n flaggedForReview: frame.flaggedForReview ?? false,\n sessionId: frame.sessionId ?? \"\",\n escalated: frame.escalated,\n };\n } else if (frame.type === \"error\") {\n throw new Error(frame.message ?? \"SSE stream error\");\n }\n } catch {\n // ignore malformed frames\n }\n }\n }\n\n if (!finalResponse) throw new Error(\"Panacea: SSE stream ended without done frame\");\n return finalResponse;\n}\n\n// ---------------------------------------------------------------------------\n// Session helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch the most recent resumable session for an identified customer.\n * Returns the sessionId if found within the resume window, null otherwise.\n */\nexport async function fetchRecentSessionId(\n customerToken: string,\n widgetToken: string,\n apiBase: string,\n): Promise<string | null> {\n const url = new URL(`${apiBase}/api/v1/sessions/resume`);\n url.searchParams.set(\"customerToken\", customerToken);\n\n const res = await fetch(url.toString(), {\n headers: { Authorization: `Bearer ${widgetToken}` },\n });\n\n if (!res.ok) return null;\n\n const data = (await res.json()) as { session: { sessionId: string } | null };\n return data?.session?.sessionId ?? null;\n}\n\n/**\n * Fetch all turns for a session (read-only history view).\n */\nexport async function fetchSessionTurns(\n sessionId: string,\n widgetToken: string,\n apiBase: string,\n): Promise<HistoryTurn[]> {\n const res = await fetch(`${apiBase}/api/v1/sessions/${sessionId}/turns`, {\n headers: { Authorization: `Bearer ${widgetToken}` },\n });\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as {\n data?: { turns?: HistoryTurn[] };\n turns?: HistoryTurn[];\n };\n return data?.data?.turns ?? data?.turns ?? [];\n}\n\n// ---------------------------------------------------------------------------\n// Reaction\n// ---------------------------------------------------------------------------\n\n/**\n * POST a reaction (thumbs up/down) for a turn.\n * D-013: explicit reaction capture via PATCH /api/v1/sessions/:id/reaction.\n */\nexport async function postReaction(\n sessionId: string,\n reaction: \"helpful\" | \"unhelpful\",\n citedPaths: string[],\n token: string,\n apiBase: string,\n): Promise<void> {\n await fetch(`${apiBase}/api/v1/sessions/${sessionId}/reaction`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ reaction, citedPaths }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Live handover helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Send a customer message during a live handover session.\n */\nexport async function postCustomerMessage(\n escalationId: string,\n content: string,\n token: string,\n apiBase: string,\n): Promise<void> {\n await fetch(`${apiBase}/api/v1/inbox/${escalationId}/customer-message`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ content }),\n });\n}\n\n/**\n * Fetch live messages from a claimed escalation (human agent replies).\n * Used to poll for new messages during live handover.\n */\nexport async function fetchLiveMessages(\n escalationId: string,\n token: string,\n apiBase: string,\n): Promise<LiveMessage[]> {\n const res = await fetch(`${apiBase}/api/v1/inbox/${escalationId}/messages`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as {\n data?: { messages?: LiveMessage[] };\n messages?: LiveMessage[];\n };\n return data?.data?.messages ?? data?.messages ?? [];\n}\n\n// ---------------------------------------------------------------------------\n// Page context\n// ---------------------------------------------------------------------------\n\n/**\n * Update the pageContext for a session (SPA navigation support).\n */\nexport async function patchPageContext(\n sessionId: string,\n ctx: Record<string, unknown>,\n token: string,\n apiBase: string,\n): Promise<void> {\n await fetch(`${apiBase}/api/v1/sessions/${sessionId}`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ pageContext: ctx }),\n });\n}\n\n/**\n * Trigger escalation to a human agent.\n * Returns the raw JSON response or null on failure.\n */\nexport async function postEscalate(\n sessionId: string,\n reason: string,\n token: string,\n apiBase: string,\n): Promise<unknown> {\n const res = await fetch(`${apiBase}/api/v1/sessions/${sessionId}/escalate`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ reason }),\n });\n if (!res.ok) return null;\n return res.json();\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@usepanacea/client",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Framework-agnostic API client for Panacea — shared by widget and React packages",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/aevrHQ/panacea.git",
11
+ "directory": "packages/client"
12
+ },
13
+ "keywords": ["panacea", "client", "api", "support", "chat"],
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.cjs"
22
+ }
23
+ },
24
+ "files": ["dist"],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "typecheck": "tsc --noEmit",
29
+ "clean": "rm -rf dist"
30
+ },
31
+ "devDependencies": {
32
+ "tsup": "^8.5.0",
33
+ "typescript": "^5.8.3"
34
+ }
35
+ }