@paymanai/payman-typescript-ask-sdk 1.0.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.mjs ADDED
@@ -0,0 +1,596 @@
1
+ import { useState, useRef, useCallback } from 'react';
2
+
3
+ // src/hooks/useChat.ts
4
+
5
+ // src/utils/generateId.ts
6
+ function generateId() {
7
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
8
+ const r = Math.random() * 16 | 0;
9
+ const v = c === "x" ? r : r & 3 | 8;
10
+ return v.toString(16);
11
+ });
12
+ }
13
+
14
+ // src/utils/streamingClient.ts
15
+ function parseJSONBuffer(buffer) {
16
+ const events = [];
17
+ let braceCount = 0;
18
+ let startIndex = 0;
19
+ let inString = false;
20
+ let escapeNext = false;
21
+ let lastParsedIndex = -1;
22
+ for (let i = 0; i < buffer.length; i++) {
23
+ const char = buffer[i];
24
+ if (escapeNext) {
25
+ escapeNext = false;
26
+ continue;
27
+ }
28
+ if (char === "\\") {
29
+ escapeNext = true;
30
+ continue;
31
+ }
32
+ if (char === '"' && !escapeNext) {
33
+ inString = !inString;
34
+ continue;
35
+ }
36
+ if (inString) {
37
+ continue;
38
+ }
39
+ if (char === "{") {
40
+ if (braceCount === 0) {
41
+ startIndex = i;
42
+ }
43
+ braceCount++;
44
+ } else if (char === "}") {
45
+ braceCount--;
46
+ if (braceCount === 0) {
47
+ const jsonStr = buffer.substring(startIndex, i + 1);
48
+ try {
49
+ const parsed = JSON.parse(jsonStr);
50
+ const event = parsed;
51
+ events.push(event);
52
+ lastParsedIndex = i;
53
+ } catch (err) {
54
+ console.error("Failed to parse JSON event:", jsonStr, err);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ const remaining = lastParsedIndex >= 0 ? buffer.substring(lastParsedIndex + 1) : buffer;
60
+ return { events, remaining };
61
+ }
62
+ async function streamWorkflowEvents(url, body, headers, options = {}) {
63
+ const { signal, onEvent, onError, onComplete } = options;
64
+ try {
65
+ const response = await fetch(url, {
66
+ method: "POST",
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ ...headers
70
+ },
71
+ body: JSON.stringify(body),
72
+ signal
73
+ });
74
+ if (!response.ok) {
75
+ const errorText = await response.text();
76
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
77
+ }
78
+ const reader = response.body?.getReader();
79
+ if (!reader) {
80
+ throw new Error("No response body");
81
+ }
82
+ const decoder = new TextDecoder();
83
+ let buffer = "";
84
+ while (true) {
85
+ const { done, value } = await reader.read();
86
+ if (done) {
87
+ if (buffer.trim()) {
88
+ const { events: events2 } = parseJSONBuffer(buffer);
89
+ for (const event of events2) {
90
+ onEvent?.(event);
91
+ }
92
+ }
93
+ break;
94
+ }
95
+ buffer += decoder.decode(value, { stream: true });
96
+ const { events, remaining } = parseJSONBuffer(buffer);
97
+ for (const event of events) {
98
+ onEvent?.(event);
99
+ }
100
+ buffer = remaining;
101
+ }
102
+ onComplete?.();
103
+ } catch (error) {
104
+ if (error instanceof Error && error.name === "AbortError") {
105
+ return;
106
+ }
107
+ onError?.(error);
108
+ }
109
+ }
110
+
111
+ // src/utils/eventProcessor.ts
112
+ function getEventMessage(event) {
113
+ if (event.message?.trim()) {
114
+ return event.message;
115
+ }
116
+ if (event.errorMessage?.trim()) {
117
+ return event.errorMessage;
118
+ }
119
+ const eventType = event.eventType;
120
+ switch (eventType) {
121
+ case "STARTED":
122
+ case "WORKFLOW_STARTED":
123
+ return "Starting workflow...";
124
+ case "ORCHESTRATOR_THINKING":
125
+ return "Planning execution strategy...";
126
+ case "ORCHESTRATOR_COMPLETED":
127
+ return "Planning complete";
128
+ case "INTENT_STARTED":
129
+ return event.workerName ? `${event.workerName} started` : "Processing intent...";
130
+ case "INTENT_PROGRESS":
131
+ return event.workerName ? `${event.workerName} in progress` : "Thinking...";
132
+ case "INTENT_COMPLETED":
133
+ return event.workerName ? `${event.workerName} completed` : "Intent completed";
134
+ case "AGGREGATOR_THINKING":
135
+ return "Combining results...";
136
+ case "AGGREGATOR_COMPLETED":
137
+ return "Results combined";
138
+ case "COMPLETED":
139
+ case "WORKFLOW_COMPLETED":
140
+ return "Workflow completed successfully";
141
+ case "ERROR":
142
+ case "WORKFLOW_ERROR":
143
+ case "INTENT_ERROR":
144
+ return event.errorMessage || "An error occurred";
145
+ default:
146
+ return eventType;
147
+ }
148
+ }
149
+ function extractResponseContent(response) {
150
+ if (typeof response === "string") {
151
+ return response;
152
+ }
153
+ if (typeof response === "object" && response !== null) {
154
+ const resp = response;
155
+ if ("text" in resp && typeof resp.text === "string") {
156
+ return resp.text;
157
+ }
158
+ if ("content" in resp && typeof resp.content === "string") {
159
+ return resp.content;
160
+ }
161
+ if ("message" in resp && typeof resp.message === "string") {
162
+ return resp.message;
163
+ }
164
+ if ("answer" in resp && typeof resp.answer === "string") {
165
+ return resp.answer;
166
+ }
167
+ return JSON.stringify(response);
168
+ }
169
+ return "";
170
+ }
171
+ function processStreamEvent(event, state) {
172
+ const eventType = event.eventType;
173
+ const message = getEventMessage(event);
174
+ if ((eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") && event.response !== void 0) {
175
+ const content = extractResponseContent(event.response);
176
+ if (content) {
177
+ state.accumulatedContent = content;
178
+ state.finalData = event.response;
179
+ }
180
+ }
181
+ if (eventType === "STARTED" || eventType === "WORKFLOW_STARTED") ; else if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
182
+ state.steps.forEach((step) => {
183
+ if (step.status === "in_progress") {
184
+ step.status = "completed";
185
+ }
186
+ });
187
+ } else if (eventType === "INTENT_ERROR") {
188
+ state.hasError = true;
189
+ state.errorMessage = message || event.errorMessage || "An error occurred";
190
+ const intentStep = state.steps.find(
191
+ (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
192
+ );
193
+ if (intentStep) {
194
+ intentStep.status = "error";
195
+ }
196
+ } else if (eventType === "ERROR" || eventType === "WORKFLOW_ERROR") {
197
+ state.hasError = true;
198
+ state.errorMessage = message || event.errorMessage || "An error occurred";
199
+ } else if (eventType === "ORCHESTRATOR_COMPLETED") {
200
+ const orchestratorStep = state.steps.find(
201
+ (s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress"
202
+ );
203
+ if (orchestratorStep) {
204
+ orchestratorStep.status = "completed";
205
+ if (event.elapsedMs) {
206
+ orchestratorStep.elapsedMs = event.elapsedMs;
207
+ }
208
+ if (orchestratorStep.id === state.currentExecutingStepId) {
209
+ state.currentExecutingStepId = void 0;
210
+ }
211
+ }
212
+ } else if (eventType === "INTENT_COMPLETED") {
213
+ const intentStep = state.steps.find(
214
+ (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
215
+ );
216
+ if (intentStep) {
217
+ intentStep.status = "completed";
218
+ if (event.elapsedMs) {
219
+ intentStep.elapsedMs = event.elapsedMs;
220
+ }
221
+ if (intentStep.id === state.currentExecutingStepId) {
222
+ state.currentExecutingStepId = void 0;
223
+ }
224
+ }
225
+ } else if (eventType === "AGGREGATOR_COMPLETED") {
226
+ const aggregatorStep = state.steps.find(
227
+ (s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress"
228
+ );
229
+ if (aggregatorStep) {
230
+ aggregatorStep.status = "completed";
231
+ if (event.elapsedMs) {
232
+ aggregatorStep.elapsedMs = event.elapsedMs;
233
+ }
234
+ if (aggregatorStep.id === state.currentExecutingStepId) {
235
+ state.currentExecutingStepId = void 0;
236
+ }
237
+ }
238
+ } else if (eventType === "ORCHESTRATOR_THINKING" || eventType === "INTENT_STARTED" || eventType === "INTENT_PROGRESS" || eventType === "AGGREGATOR_THINKING") {
239
+ if (eventType === "INTENT_PROGRESS") {
240
+ const intentStep = state.steps.find(
241
+ (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
242
+ );
243
+ if (intentStep) {
244
+ intentStep.message = message;
245
+ state.currentExecutingStepId = intentStep.id;
246
+ } else {
247
+ const stepId = `step-${state.stepCounter++}`;
248
+ state.steps.push({
249
+ id: stepId,
250
+ eventType: "INTENT_STARTED",
251
+ message,
252
+ status: "in_progress",
253
+ timestamp: Date.now(),
254
+ elapsedMs: event.elapsedMs
255
+ });
256
+ state.currentExecutingStepId = stepId;
257
+ }
258
+ } else {
259
+ const stepId = `step-${state.stepCounter++}`;
260
+ state.steps.push({
261
+ id: stepId,
262
+ eventType,
263
+ message,
264
+ status: "in_progress",
265
+ timestamp: Date.now(),
266
+ elapsedMs: event.elapsedMs
267
+ });
268
+ state.currentExecutingStepId = stepId;
269
+ }
270
+ }
271
+ return state;
272
+ }
273
+
274
+ // src/utils/messageStateManager.ts
275
+ function createStreamingMessageUpdate(state) {
276
+ const hasCompletedContent = state.accumulatedContent && state.finalData !== void 0;
277
+ return {
278
+ streamingContent: state.hasError ? `Oops, something went wrong. Please try again.
279
+
280
+ ${state.errorMessage}` : hasCompletedContent ? state.accumulatedContent : "",
281
+ content: state.hasError ? `Oops, something went wrong. Please try again.
282
+
283
+ ${state.errorMessage}` : "",
284
+ currentMessage: state.hasError ? void 0 : state.currentMessage,
285
+ streamProgress: state.hasError ? "error" : "processing",
286
+ isError: state.hasError,
287
+ errorDetails: state.hasError ? state.errorMessage : void 0,
288
+ executionId: state.executionId,
289
+ sessionId: state.sessionId,
290
+ steps: state.hasError ? [] : [...state.steps],
291
+ // Don't show steps on error
292
+ currentExecutingStepId: state.hasError ? void 0 : state.currentExecutingStepId,
293
+ isCancelled: false
294
+ };
295
+ }
296
+ function createErrorMessageUpdate(error, state) {
297
+ const isAborted = error.name === "AbortError";
298
+ return {
299
+ isStreaming: false,
300
+ streamProgress: isAborted ? "processing" : "error",
301
+ isError: !isAborted,
302
+ isCancelled: isAborted,
303
+ errorDetails: isAborted ? void 0 : error.message,
304
+ content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || `Oops, something went wrong. Please try again.
305
+
306
+ ${error.message}`,
307
+ // Preserve currentMessage when cancelled so UI can show it
308
+ currentMessage: isAborted ? state.currentMessage || "Thinking..." : void 0,
309
+ steps: [...state.steps].map((step) => {
310
+ if (step.status === "in_progress" && isAborted) {
311
+ return { ...step, status: "pending" };
312
+ }
313
+ return step;
314
+ }),
315
+ currentExecutingStepId: void 0
316
+ };
317
+ }
318
+ function createFinalMessage(streamingId, state) {
319
+ return {
320
+ id: streamingId,
321
+ sessionId: state.sessionId,
322
+ role: "assistant",
323
+ content: state.hasError ? `Oops, something went wrong. Please try again.
324
+
325
+ ${state.errorMessage}` : state.accumulatedContent || "",
326
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
327
+ isStreaming: false,
328
+ streamProgress: state.hasError ? "error" : "completed",
329
+ isError: state.hasError,
330
+ errorDetails: state.hasError ? state.errorMessage : void 0,
331
+ executionId: state.executionId,
332
+ tracingData: state.finalData,
333
+ steps: state.hasError ? [] : [...state.steps],
334
+ // Don't show steps on error
335
+ isCancelled: false,
336
+ currentExecutingStepId: void 0
337
+ };
338
+ }
339
+ function createCancelledMessageUpdate(steps, currentMessage) {
340
+ const updatedSteps = steps.map((step) => {
341
+ if (step.status === "in_progress") {
342
+ return { ...step, status: "pending" };
343
+ }
344
+ return step;
345
+ });
346
+ return {
347
+ isStreaming: false,
348
+ isCancelled: true,
349
+ steps: updatedSteps,
350
+ currentExecutingStepId: void 0,
351
+ // Preserve currentMessage so UI can show it with X icon
352
+ currentMessage: currentMessage || "Thinking..."
353
+ };
354
+ }
355
+
356
+ // src/utils/requestBuilder.ts
357
+ function buildRequestBody(config, userMessage, sessionId) {
358
+ const sessionOwner = config.sessionParams;
359
+ const sessionAttributes = sessionOwner?.attributes && Object.keys(sessionOwner.attributes).length > 0 ? sessionOwner.attributes : void 0;
360
+ return {
361
+ workflowName: config.workflowName,
362
+ userInput: userMessage,
363
+ sessionId,
364
+ sessionOwnerId: sessionOwner?.id || "",
365
+ sessionOwnerLabel: sessionOwner?.name || "",
366
+ sessionAttributes,
367
+ options: {
368
+ clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
369
+ }
370
+ };
371
+ }
372
+ function buildStreamingUrl(config) {
373
+ const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
374
+ const stage = config.stage || "DEV";
375
+ const version = config.workflowVersion || 1;
376
+ return `${config.api.baseUrl}${endpoint}?stage=${stage}&workflowVersion=${version}`;
377
+ }
378
+ function buildRequestHeaders(config) {
379
+ const headers = {
380
+ ...config.api.headers
381
+ };
382
+ if (config.api.authToken) {
383
+ headers.Authorization = `Bearer ${config.api.authToken}`;
384
+ }
385
+ return headers;
386
+ }
387
+ function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
388
+ const abortControllerRef = useRef(null);
389
+ const startStream = useCallback(
390
+ async (userMessage, streamingId, sessionId) => {
391
+ abortControllerRef.current?.abort();
392
+ const abortController = new AbortController();
393
+ abortControllerRef.current = abortController;
394
+ const state = {
395
+ accumulatedContent: "",
396
+ executionId: void 0,
397
+ currentSessionId: void 0,
398
+ finalData: void 0,
399
+ steps: [],
400
+ stepCounter: 0,
401
+ currentExecutingStepId: void 0,
402
+ hasError: false,
403
+ errorMessage: ""
404
+ };
405
+ const requestBody = buildRequestBody(config, userMessage, sessionId);
406
+ const url = buildStreamingUrl(config);
407
+ const headers = buildRequestHeaders(config);
408
+ try {
409
+ await streamWorkflowEvents(url, requestBody, headers, {
410
+ signal: abortController.signal,
411
+ onEvent: (event) => {
412
+ if (abortController.signal.aborted) {
413
+ return;
414
+ }
415
+ if (event.executionId) state.executionId = event.executionId;
416
+ if (event.sessionId) state.currentSessionId = event.sessionId;
417
+ processStreamEvent(event, state);
418
+ const currentMessage = event.message?.trim() || event.errorMessage?.trim();
419
+ setMessages(
420
+ (prev) => prev.map(
421
+ (msg) => msg.id === streamingId ? {
422
+ ...msg,
423
+ ...createStreamingMessageUpdate({
424
+ ...state,
425
+ currentMessage
426
+ })
427
+ } : msg
428
+ )
429
+ );
430
+ },
431
+ onError: (error) => {
432
+ setIsWaitingForResponse(false);
433
+ if (error.name !== "AbortError") {
434
+ callbacks.onError?.(error);
435
+ }
436
+ setMessages(
437
+ (prev) => prev.map(
438
+ (msg) => msg.id === streamingId ? {
439
+ ...msg,
440
+ ...createErrorMessageUpdate(error, state)
441
+ } : msg
442
+ )
443
+ );
444
+ },
445
+ onComplete: () => {
446
+ setIsWaitingForResponse(false);
447
+ if (state.currentSessionId && state.currentSessionId !== sessionId) {
448
+ callbacks.onSessionIdChange?.(state.currentSessionId);
449
+ }
450
+ const finalMessage = createFinalMessage(streamingId, {
451
+ ...state,
452
+ sessionId: state.currentSessionId || sessionId
453
+ });
454
+ setMessages(
455
+ (prev) => prev.map(
456
+ (msg) => msg.id === streamingId ? finalMessage : msg
457
+ )
458
+ );
459
+ callbacks.onStreamComplete?.(finalMessage);
460
+ }
461
+ });
462
+ return state.currentSessionId;
463
+ } catch (error) {
464
+ setIsWaitingForResponse(false);
465
+ if (error.name !== "AbortError") {
466
+ callbacks.onError?.(error);
467
+ }
468
+ setMessages(
469
+ (prev) => prev.map(
470
+ (msg) => msg.id === streamingId ? {
471
+ ...msg,
472
+ ...createErrorMessageUpdate(error, state)
473
+ } : msg
474
+ )
475
+ );
476
+ return state.currentSessionId;
477
+ }
478
+ },
479
+ [config, callbacks, setMessages, setIsWaitingForResponse]
480
+ );
481
+ const cancelStream = useCallback(() => {
482
+ abortControllerRef.current?.abort();
483
+ }, []);
484
+ return {
485
+ startStream,
486
+ cancelStream,
487
+ abortControllerRef
488
+ };
489
+ }
490
+
491
+ // src/hooks/useChat.ts
492
+ function useChat(config, callbacks = {}) {
493
+ const [messages, setMessages] = useState([]);
494
+ const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
495
+ const sessionIdRef = useRef(void 0);
496
+ const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
497
+ config,
498
+ callbacks,
499
+ setMessages,
500
+ setIsWaitingForResponse
501
+ );
502
+ const sendMessage = useCallback(
503
+ async (userMessage) => {
504
+ if (!userMessage.trim()) return;
505
+ if (!sessionIdRef.current && config.autoGenerateSessionId !== false) {
506
+ sessionIdRef.current = generateId();
507
+ callbacks.onSessionIdChange?.(sessionIdRef.current);
508
+ }
509
+ const userMessageId = `user-${Date.now()}`;
510
+ const userMsg = {
511
+ id: userMessageId,
512
+ sessionId: sessionIdRef.current,
513
+ role: "user",
514
+ content: userMessage,
515
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
516
+ };
517
+ setMessages((prev) => [...prev, userMsg]);
518
+ callbacks.onMessageSent?.(userMessage);
519
+ setIsWaitingForResponse(true);
520
+ callbacks.onStreamStart?.();
521
+ const streamingId = `assistant-${Date.now()}`;
522
+ const streamingMsg = {
523
+ id: streamingId,
524
+ sessionId: sessionIdRef.current,
525
+ role: "assistant",
526
+ content: "",
527
+ streamingContent: "",
528
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
529
+ isStreaming: true,
530
+ streamProgress: "started",
531
+ steps: [],
532
+ currentExecutingStepId: void 0,
533
+ isCancelled: false,
534
+ currentMessage: void 0
535
+ };
536
+ setMessages((prev) => [...prev, streamingMsg]);
537
+ const newSessionId = await startStream(
538
+ userMessage,
539
+ streamingId,
540
+ sessionIdRef.current
541
+ );
542
+ if (newSessionId && newSessionId !== sessionIdRef.current) {
543
+ sessionIdRef.current = newSessionId;
544
+ }
545
+ },
546
+ [config, callbacks, startStream, setMessages, setIsWaitingForResponse]
547
+ );
548
+ const clearMessages = useCallback(() => {
549
+ setMessages([]);
550
+ }, []);
551
+ const cancelStream = useCallback(() => {
552
+ cancelStreamManager();
553
+ setIsWaitingForResponse(false);
554
+ setMessages(
555
+ (prev) => prev.map((msg) => {
556
+ if (msg.isStreaming) {
557
+ return {
558
+ ...msg,
559
+ ...createCancelledMessageUpdate(
560
+ msg.steps || [],
561
+ msg.currentMessage
562
+ )
563
+ };
564
+ }
565
+ return msg;
566
+ })
567
+ );
568
+ }, [cancelStreamManager]);
569
+ const resetSession = useCallback(() => {
570
+ setMessages([]);
571
+ sessionIdRef.current = void 0;
572
+ abortControllerRef.current?.abort();
573
+ setIsWaitingForResponse(false);
574
+ }, []);
575
+ const getSessionId = useCallback(() => {
576
+ return sessionIdRef.current;
577
+ }, []);
578
+ const getMessages = useCallback(() => {
579
+ return messages;
580
+ }, [messages]);
581
+ return {
582
+ messages,
583
+ sendMessage,
584
+ clearMessages,
585
+ cancelStream,
586
+ resetSession,
587
+ getSessionId,
588
+ getMessages,
589
+ isWaitingForResponse,
590
+ sessionId: sessionIdRef.current
591
+ };
592
+ }
593
+
594
+ export { generateId, streamWorkflowEvents, useChat };
595
+ //# sourceMappingURL=index.mjs.map
596
+ //# sourceMappingURL=index.mjs.map