@paymanai/payman-typescript-ask-sdk 2.0.3 → 4.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.
@@ -136,7 +136,38 @@ async function streamWorkflowEvents(url, body, headers, options = {}) {
136
136
  });
137
137
  }
138
138
 
139
- // src/utils/eventProcessor.ts
139
+ // src/utils/stageLabels.ts
140
+ var STAGE_LABELS = {
141
+ sanitizer: "Checking your request",
142
+ analyzer: "Understanding what you're asking",
143
+ prefetcher: "Gathering context",
144
+ planner: "Planning how to handle this",
145
+ execution: "Working on it",
146
+ formatter: "Writing the response"
147
+ };
148
+ function stageLabel(stage) {
149
+ const label = STAGE_LABELS[stage];
150
+ if (label) return label;
151
+ const pretty = stage.replace(/[_-]/g, " ");
152
+ return `Running ${pretty}`;
153
+ }
154
+
155
+ // src/utils/v2EventProcessor.ts
156
+ function isBlandStatus(message) {
157
+ if (!message) return true;
158
+ const normalized = message.trim().toLowerCase().replace(/[…\.]+$/, "").trim();
159
+ return BLAND_STATUS_LABELS.has(normalized);
160
+ }
161
+ var BLAND_STATUS_LABELS = /* @__PURE__ */ new Set([
162
+ "executing",
163
+ "working on it",
164
+ "thinking",
165
+ "processing",
166
+ "reviewing your request",
167
+ "composing response",
168
+ "checking your request",
169
+ "polishing the response"
170
+ ]);
140
171
  function getEventMessage(event) {
141
172
  if (event.message?.trim()) {
142
173
  return event.message.trim();
@@ -146,54 +177,104 @@ function getEventMessage(event) {
146
177
  }
147
178
  const eventType = event.eventType;
148
179
  switch (eventType) {
149
- case "STARTED":
150
- case "WORKFLOW_STARTED":
151
- return "Starting workflow...";
152
- case "ORCHESTRATOR_THINKING":
153
- return "Planning execution strategy...";
154
- case "ORCHESTRATOR_COMPLETED":
155
- return "Planning complete";
156
- case "INTENT_STARTED":
157
- return event.workerName ? `${event.workerName} started` : "Processing intent...";
158
- case "INTENT_PROGRESS":
159
- return event.workerName ? `${event.workerName} in progress` : "Thinking...";
160
- case "INTENT_COMPLETED":
161
- return event.workerName ? `${event.workerName} completed` : "Intent completed";
162
- case "AGGREGATOR_THINKING":
163
- return "Combining results...";
164
- case "AGGREGATOR_COMPLETED":
165
- return "Results combined";
166
- case "COMPLETED":
167
- case "WORKFLOW_COMPLETED":
168
- return "Workflow completed successfully";
169
- case "ERROR":
170
- case "WORKFLOW_ERROR":
171
- case "INTENT_ERROR":
172
- return event.errorMessage || "An error occurred";
173
- case "USER_ACTION_REQUIRED":
174
- return "Waiting for verification...";
175
- case "USER_ACTION_SUCCESS":
176
- return "Verification approved";
177
- case "USER_ACTION_EXPIRED":
178
- return "Verification expired";
179
- case "USER_ACTION_INVALID":
180
- return "Invalid code. Please try again.";
181
- case "USER_ACTION_REJECTED":
182
- return "Verification cancelled";
183
- case "USER_ACTION_RESENT":
184
- return "Verification code resent";
185
- case "USER_ACTION_FAILED":
186
- return "Verification failed";
187
- case "INTENT_THINKING":
188
- return event.workerName ? `${event.workerName} thinking...` : "Thinking...";
189
- case "INTENT_THINKING_CONT":
190
- return event.message || "";
180
+ case "RUN_STARTED":
181
+ return "Starting agent run...";
182
+ case "TOOL_CALL_STARTED": {
183
+ const description = typeof event.description === "string" ? event.description.trim() : "";
184
+ if (description) return description;
185
+ const toolName = typeof event.toolName === "string" ? event.toolName : "";
186
+ return toolName ? `Calling ${toolName}...` : "Calling tool...";
187
+ }
188
+ case "TOOL_CALL_COMPLETED": {
189
+ const description = typeof event.description === "string" ? event.description.trim() : "";
190
+ if (description) return description;
191
+ const toolName = typeof event.toolName === "string" ? event.toolName : "";
192
+ return toolName ? `${toolName} completed` : "Tool call completed";
193
+ }
194
+ case "RUN_IN_PROGRESS":
195
+ return event.partialText || "Thinking...";
196
+ case "RUN_COMPLETED":
197
+ return "Agent run completed";
198
+ case "RUN_FAILED":
199
+ return event.errorMessage || "Agent run failed";
191
200
  case "KEEP_ALIVE":
192
- return "";
201
+ return event.description || "";
202
+ case "THINKING_DELTA":
203
+ return event.text || "";
193
204
  default:
194
205
  return eventType;
195
206
  }
196
207
  }
208
+ function extractResponseContent(response) {
209
+ if (typeof response === "string") {
210
+ return response;
211
+ }
212
+ if (typeof response === "object" && response !== null) {
213
+ const resp = response;
214
+ if ("text" in resp && typeof resp.text === "string") {
215
+ return resp.text;
216
+ }
217
+ if ("content" in resp && typeof resp.content === "string") {
218
+ return resp.content;
219
+ }
220
+ if ("message" in resp && typeof resp.message === "string") {
221
+ return resp.message;
222
+ }
223
+ if ("answer" in resp && typeof resp.answer === "string") {
224
+ return resp.answer;
225
+ }
226
+ return JSON.stringify(response);
227
+ }
228
+ return "";
229
+ }
230
+ function normalizeEvent(event) {
231
+ const type = event.eventType;
232
+ switch (type) {
233
+ case "RUN_STARTED":
234
+ return { ...event, eventType: "WORKFLOW_STARTED" };
235
+ case "TOOL_CALL_STARTED": {
236
+ const toolName = typeof event.toolName === "string" ? event.toolName : void 0;
237
+ const description = typeof event.description === "string" ? event.description.trim() : "";
238
+ return {
239
+ ...event,
240
+ eventType: "INTENT_STARTED",
241
+ workerName: toolName ?? event.workerName,
242
+ message: description || event.message
243
+ };
244
+ }
245
+ case "TOOL_CALL_COMPLETED": {
246
+ const toolName = typeof event.toolName === "string" ? event.toolName : void 0;
247
+ const description = typeof event.description === "string" ? event.description.trim() : "";
248
+ return {
249
+ ...event,
250
+ eventType: "INTENT_COMPLETED",
251
+ workerName: toolName ?? event.workerName,
252
+ message: description || event.message
253
+ };
254
+ }
255
+ case "RUN_IN_PROGRESS":
256
+ return {
257
+ ...event,
258
+ eventType: "INTENT_PROGRESS",
259
+ message: event.partialText ?? event.message
260
+ };
261
+ case "RUN_COMPLETED":
262
+ return {
263
+ ...event,
264
+ eventType: "WORKFLOW_COMPLETED",
265
+ // state machine reads event.response for the final content
266
+ response: event.response ?? event.message
267
+ };
268
+ case "RUN_FAILED":
269
+ return {
270
+ ...event,
271
+ eventType: "WORKFLOW_ERROR",
272
+ errorMessage: event.errorMessage ?? event.message
273
+ };
274
+ default:
275
+ return event;
276
+ }
277
+ }
197
278
  function isUserActionPrompt(text) {
198
279
  return /\benter\b.+\b(code|otp)\b/i.test(text) || /\b(authorization|verification)\s+code\b/i.test(text) || /\bsent\s+to\s+your\b/i.test(text);
199
280
  }
@@ -227,306 +308,6 @@ function workingPhaseDetailForDisplay(raw) {
227
308
  }
228
309
  return t;
229
310
  }
230
- function extractResponseContent(response) {
231
- if (typeof response === "string") {
232
- return response;
233
- }
234
- if (typeof response === "object" && response !== null) {
235
- const resp = response;
236
- if ("text" in resp && typeof resp.text === "string") {
237
- return resp.text;
238
- }
239
- if ("content" in resp && typeof resp.content === "string") {
240
- return resp.content;
241
- }
242
- if ("message" in resp && typeof resp.message === "string") {
243
- return resp.message;
244
- }
245
- if ("answer" in resp && typeof resp.answer === "string") {
246
- return resp.answer;
247
- }
248
- return JSON.stringify(response);
249
- }
250
- return "";
251
- }
252
-
253
- // src/utils/messageStateManager.ts
254
- function buildFormattedThinking(steps, allThinkingText) {
255
- const parts = [];
256
- const safeSteps = steps ?? [];
257
- const cleanAll = allThinkingText.replace(/^\s+/, "");
258
- if (cleanAll) {
259
- const firstStepWithThinking = safeSteps.find(
260
- (s) => s.thinkingText && s.thinkingText.trim()
261
- );
262
- if (!firstStepWithThinking) {
263
- parts.push("**Preflight**");
264
- parts.push(cleanAll);
265
- } else {
266
- const stepText = firstStepWithThinking.thinkingText.trim();
267
- const idx = cleanAll.indexOf(stepText);
268
- if (idx > 0) {
269
- const orphaned = cleanAll.substring(0, idx).replace(/\s+$/, "");
270
- if (orphaned) {
271
- parts.push("**Preflight**");
272
- parts.push(orphaned);
273
- }
274
- }
275
- }
276
- }
277
- for (const step of safeSteps) {
278
- switch (step.eventType) {
279
- case "ORCHESTRATOR_THINKING":
280
- parts.push("**Planning**");
281
- if (step.message) parts.push(step.message);
282
- break;
283
- case "WORKING": {
284
- const detail = workingPhaseDetailForDisplay(step.message || "");
285
- parts.push("**Working**");
286
- if (detail) parts.push(detail);
287
- break;
288
- }
289
- case "INTENT_STARTED": {
290
- let label = step.message || "Processing";
291
- const started = label.match(/^(.+?)\s+started$/i);
292
- const progress = label.match(/^(.+?)\s+in progress$/i);
293
- if (started) label = started[1];
294
- else if (progress) label = progress[1];
295
- parts.push(`**${label}**`);
296
- if (step.thinkingText) parts.push(step.thinkingText);
297
- break;
298
- }
299
- case "INTENT_PROGRESS": {
300
- if (step.thinkingText) parts.push(step.thinkingText);
301
- else if (step.message) parts.push(step.message);
302
- break;
303
- }
304
- case "AGGREGATOR_THINKING":
305
- parts.push("**Finalizing**");
306
- if (step.message) parts.push(step.message);
307
- break;
308
- case "USER_ACTION_REQUIRED":
309
- parts.push("**Verification Required**");
310
- if (step.message) {
311
- parts.push(getUserActionDisplayMessage(step.eventType, step.message));
312
- }
313
- break;
314
- case "USER_ACTION_SUCCESS":
315
- parts.push(`\u2713 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
316
- break;
317
- case "USER_ACTION_REJECTED":
318
- parts.push(getUserActionDisplayMessage(step.eventType, step.message));
319
- break;
320
- case "USER_ACTION_EXPIRED":
321
- parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
322
- break;
323
- case "USER_ACTION_INVALID":
324
- parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
325
- break;
326
- case "USER_ACTION_RESENT":
327
- parts.push(getUserActionDisplayMessage(step.eventType, step.message));
328
- break;
329
- case "USER_ACTION_FAILED":
330
- parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
331
- break;
332
- }
333
- }
334
- return parts.length > 0 ? parts.join("\n") : allThinkingText;
335
- }
336
- function createCancelledMessageUpdate(steps, currentMessage) {
337
- const updatedSteps = steps.map((step) => {
338
- if (step.status === "in_progress") {
339
- return { ...step, status: "pending" };
340
- }
341
- return step;
342
- });
343
- return {
344
- isStreaming: false,
345
- isCancelled: true,
346
- steps: updatedSteps,
347
- currentExecutingStepId: void 0,
348
- // Preserve currentMessage so UI can show it with X icon
349
- currentMessage: currentMessage || "Thinking..."
350
- };
351
- }
352
-
353
- // src/utils/requestBuilder.ts
354
- function buildRequestBody(config, userMessage, sessionId) {
355
- const owner = config.session?.owner;
356
- const sessionAttributes = owner?.attributes && Object.keys(owner.attributes).length > 0 ? owner.attributes : void 0;
357
- return {
358
- workflowName: config.workflow.name,
359
- userInput: userMessage,
360
- sessionId,
361
- sessionOwnerId: owner?.id || "",
362
- sessionOwnerLabel: owner?.name || "",
363
- sessionAttributes,
364
- options: {
365
- clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
366
- }
367
- };
368
- }
369
- function getStageParamName(config) {
370
- return config.api.stageQueryParam ?? "stage";
371
- }
372
- function getStage(config) {
373
- return config.workflow.stage ?? "DEV";
374
- }
375
- function buildStreamingUrl(config) {
376
- const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
377
- const queryParams = new URLSearchParams({
378
- [getStageParamName(config)]: getStage(config)
379
- });
380
- if (config.workflow.version !== void 0) {
381
- queryParams.append("workflowVersion", String(config.workflow.version));
382
- }
383
- return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
384
- }
385
- function deriveBasePath(config) {
386
- const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
387
- const [endpointPath] = endpoint.split("?");
388
- const normalized = endpointPath.replace(/\/+$/, "");
389
- return normalized.endsWith("/stream") ? normalized.slice(0, -"/stream".length) : normalized;
390
- }
391
- function buildUserActionUrl(config, userActionId, action) {
392
- const basePath = deriveBasePath(config);
393
- const encodedUserActionId = encodeURIComponent(userActionId);
394
- return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
395
- }
396
- function buildResolveImagesUrl(config) {
397
- if (config.api.resolveImagesEndpoint) {
398
- return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
399
- }
400
- return `${config.api.baseUrl}${deriveBasePath(config)}/resolve-image-urls`;
401
- }
402
- function buildRequestHeaders(config) {
403
- const headers = { ...config.api.headers };
404
- if (config.api.authToken) {
405
- headers.Authorization = `Bearer ${config.api.authToken}`;
406
- }
407
- return headers;
408
- }
409
- function buildScopeKey(config) {
410
- return [
411
- config.session?.userId ?? "",
412
- config.workflow.id ?? "",
413
- config.workflow.version ?? "",
414
- config.workflow.stage ?? ""
415
- ].join("|");
416
- }
417
-
418
- // src/utils/userActionClient.ts
419
- async function sendUserActionRequest(config, userActionId, action, data) {
420
- const url = buildUserActionUrl(config, userActionId, action);
421
- const baseHeaders = buildRequestHeaders(config);
422
- const hasBody = data !== void 0;
423
- const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
424
- const response = await fetch(url, {
425
- method: "POST",
426
- headers,
427
- body: hasBody ? JSON.stringify(data) : void 0
428
- });
429
- if (!response.ok) {
430
- const errorText = await response.text();
431
- throw new Error(`HTTP ${response.status}: ${errorText}`);
432
- }
433
- return await response.json();
434
- }
435
- async function submitUserAction(config, userActionId, data) {
436
- return sendUserActionRequest(config, userActionId, "submit", data);
437
- }
438
- async function cancelUserAction(config, userActionId) {
439
- return sendUserActionRequest(config, userActionId, "cancel");
440
- }
441
- async function resendUserAction(config, userActionId) {
442
- return sendUserActionRequest(config, userActionId, "resend");
443
- }
444
- var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
445
- var chatStore = {
446
- get(key) {
447
- const raw = storage.getString(key);
448
- if (!raw) return [];
449
- try {
450
- return JSON.parse(raw);
451
- } catch {
452
- return [];
453
- }
454
- },
455
- set(key, messages) {
456
- storage.set(key, JSON.stringify(messages));
457
- },
458
- delete(key) {
459
- const s = storage;
460
- if (typeof s.remove === "function") s.remove(key);
461
- else if (typeof s.delete === "function") s.delete(key);
462
- }
463
- };
464
-
465
- // src/utils/activeStreamStore.ts
466
- var streams = /* @__PURE__ */ new Map();
467
- var activeStreamStore = {
468
- has(key) {
469
- return streams.has(key);
470
- },
471
- get(key) {
472
- const entry = streams.get(key);
473
- if (!entry) return null;
474
- return { messages: entry.messages, isWaiting: entry.isWaiting };
475
- },
476
- // Called before startStream — registers the controller and initial messages
477
- start(key, abortController, initialMessages) {
478
- const existing = streams.get(key);
479
- streams.set(key, {
480
- messages: initialMessages,
481
- isWaiting: true,
482
- abortController,
483
- listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
484
- });
485
- },
486
- // Called by the stream on every event — applies the same updater pattern React uses
487
- applyMessages(key, updater) {
488
- const entry = streams.get(key);
489
- if (!entry) return;
490
- const next = typeof updater === "function" ? updater(entry.messages) : updater;
491
- entry.messages = next;
492
- entry.listeners.forEach((l) => l(next, entry.isWaiting));
493
- },
494
- setWaiting(key, waiting) {
495
- const entry = streams.get(key);
496
- if (!entry) return;
497
- entry.isWaiting = waiting;
498
- entry.listeners.forEach((l) => l(entry.messages, waiting));
499
- },
500
- // Called when stream completes — persists to chatStore and cleans up
501
- complete(key) {
502
- const entry = streams.get(key);
503
- if (!entry) return;
504
- entry.isWaiting = false;
505
- entry.listeners.forEach((l) => l(entry.messages, false));
506
- const toSave = entry.messages.filter((m) => !m.isStreaming);
507
- if (toSave.length > 0) chatStore.set(key, toSave);
508
- streams.delete(key);
509
- },
510
- // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
511
- subscribe(key, listener) {
512
- const entry = streams.get(key);
513
- if (!entry) return () => {
514
- };
515
- entry.listeners.add(listener);
516
- return () => {
517
- streams.get(key)?.listeners.delete(listener);
518
- };
519
- },
520
- // Explicit user cancel — aborts the controller and removes the entry
521
- abort(key) {
522
- const entry = streams.get(key);
523
- if (!entry) return;
524
- entry.abortController.abort();
525
- streams.delete(key);
526
- }
527
- };
528
-
529
- // src/utils/v2EventProcessor.ts
530
311
  function getEventText(event, field) {
531
312
  const value = event[field];
532
313
  return typeof value === "string" ? value.trim() : "";
@@ -550,6 +331,16 @@ function addThinkingLine(state, header, detail) {
550
331
  function appendThinkingText(state, text) {
551
332
  state.formattedThinkingText += text;
552
333
  }
334
+ function updateExecutionStageMessage(state, msg) {
335
+ if (!msg) return;
336
+ for (let i = state.steps.length - 1; i >= 0; i--) {
337
+ const s = state.steps[i];
338
+ if (s.eventType === "STAGE_STARTED" && (s.stage === "executor" || s.stage === "execution") && s.status === "in_progress") {
339
+ s.message = msg;
340
+ return;
341
+ }
342
+ }
343
+ }
553
344
  function completeLastInProgressStep(steps) {
554
345
  for (let i = steps.length - 1; i >= 0; i--) {
555
346
  if (steps[i].status === "in_progress") {
@@ -577,11 +368,31 @@ function createInitialV2State() {
577
368
  currentExecutingStepId: void 0
578
369
  };
579
370
  }
580
- function processStreamEventV2(event, state) {
371
+ function processStreamEventV2(rawEvent, state) {
372
+ const event = normalizeEvent(rawEvent);
581
373
  const eventType = event.eventType;
582
374
  if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
583
375
  if (event.executionId) state.executionId = event.executionId;
584
376
  if (event.sessionId) state.sessionId = event.sessionId;
377
+ const description = typeof event.description === "string" ? event.description : "";
378
+ if (description) {
379
+ for (let i = state.steps.length - 1; i >= 0; i--) {
380
+ if (state.steps[i].status === "in_progress") {
381
+ state.steps[i].message = description;
382
+ break;
383
+ }
384
+ }
385
+ }
386
+ return state;
387
+ }
388
+ if (typeof eventType === "string" && eventType.toUpperCase() === "THINKING_DELTA") {
389
+ const text = typeof event.text === "string" ? event.text : "";
390
+ if (text) {
391
+ appendThinkingText(state, text);
392
+ }
393
+ if (event.executionId) state.executionId = event.executionId;
394
+ if (event.sessionId) state.sessionId = event.sessionId;
395
+ state.lastEventType = "THINKING_DELTA";
585
396
  return state;
586
397
  }
587
398
  if (event.executionId) state.executionId = event.executionId;
@@ -592,6 +403,16 @@ function processStreamEventV2(event, state) {
592
403
  case "STARTED":
593
404
  state.lastEventType = eventType;
594
405
  break;
406
+ case "INTENT_PROGRESS": {
407
+ const rawMessage = typeof event.message === "string" ? event.message : "";
408
+ const rawPartial = typeof event.partialText === "string" ? event.partialText : "";
409
+ const delta = rawMessage || rawPartial;
410
+ if (delta.length > 0) {
411
+ state.finalResponse += delta;
412
+ }
413
+ state.lastEventType = eventType;
414
+ break;
415
+ }
595
416
  case "INTENT_THINKING": {
596
417
  const worker = getEventText(event, "workerName") || "Worker";
597
418
  const msg = getEventText(event, "message") || "Thinking...";
@@ -693,6 +514,7 @@ function processStreamEventV2(event, state) {
693
514
  elapsedMs: event.elapsedMs
694
515
  });
695
516
  state.currentExecutingStepId = stepId;
517
+ updateExecutionStageMessage(state, message);
696
518
  state.lastEventType = eventType;
697
519
  break;
698
520
  }
@@ -735,6 +557,10 @@ function processStreamEventV2(event, state) {
735
557
  }
736
558
  case "WORKFLOW_COMPLETED":
737
559
  case "COMPLETED": {
560
+ const totalTime = Number(event.totalTimeMs);
561
+ if (Number.isFinite(totalTime) && totalTime > 0) {
562
+ state.totalElapsedMs = totalTime;
563
+ }
738
564
  let content = extractResponseContent(event.response);
739
565
  const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
740
566
  if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
@@ -747,7 +573,9 @@ function processStreamEventV2(event, state) {
747
573
  }
748
574
  if (content) {
749
575
  state.finalResponse = content;
750
- state.finalData = event.response ?? event.trace;
576
+ if (event.trace && typeof event.trace === "object") {
577
+ state.finalData = event.trace;
578
+ }
751
579
  state.hasError = false;
752
580
  state.errorMessage = "";
753
581
  } else {
@@ -929,12 +757,373 @@ function processStreamEventV2(event, state) {
929
757
  state.lastEventType = eventType;
930
758
  break;
931
759
  }
932
- default:
933
- state.lastEventType = eventType;
934
- break;
760
+ // ---- K2 pipeline stage lifecycle events ----
761
+ //
762
+ // The k2-server playground streaming API emits
763
+ // STAGE_STARTED / STAGE_COMPLETED / STAGE_FAILED /
764
+ // STAGE_SKIPPED around each pipeline stage so the UI can
765
+ // show real progress (sanitizer → analyzer → planner → …)
766
+ // instead of a static placeholder. Each STAGE_STARTED
767
+ // pushes an in-progress step with a user-facing label; the
768
+ // matching STAGE_COMPLETED/FAILED closes it WITHOUT
769
+ // rewriting the label — a terse "completed" flash between
770
+ // stages was more noise than signal. STAGE_SKIPPED removes
771
+ // the just-opened step entirely (skipping is expected on
772
+ // DIRECT_RESPONSE short-circuits).
773
+ //
774
+ // Finding the matching step uses `stepId` directly rather
775
+ // than comparing labels — the processor remembers which id
776
+ // it minted for the latest open STAGE_STARTED and the
777
+ // closing events look it up from state.currentExecutingStepId.
778
+ case "STAGE_STARTED": {
779
+ const stage = getEventText(event, "stage");
780
+ if (!stage) {
781
+ state.lastEventType = eventType;
782
+ break;
783
+ }
784
+ const serverMessage = getEventText(event, "message");
785
+ let initialMessage = serverMessage || stageLabel(stage);
786
+ if (stage === "executor" || stage === "execution") {
787
+ for (let i = state.steps.length - 1; i >= 0; i--) {
788
+ const s = state.steps[i];
789
+ if (s.eventType === "STAGE_STARTED" && s.stage === "analyzer" && s.status === "completed" && s.message) {
790
+ initialMessage = s.message;
791
+ break;
792
+ }
793
+ }
794
+ }
795
+ const stepId = `stage-${stage}-${state.stepCounter++}`;
796
+ state.steps.push({
797
+ id: stepId,
798
+ eventType,
799
+ message: initialMessage,
800
+ status: "in_progress",
801
+ timestamp: Date.now(),
802
+ stage
803
+ });
804
+ state.currentExecutingStepId = stepId;
805
+ state.lastEventType = eventType;
806
+ break;
807
+ }
808
+ case "STAGE_COMPLETED": {
809
+ const stage = getEventText(event, "stage");
810
+ if (!stage) {
811
+ state.lastEventType = eventType;
812
+ break;
813
+ }
814
+ const serverMessage = getEventText(event, "message");
815
+ for (let i = state.steps.length - 1; i >= 0; i--) {
816
+ const s = state.steps[i];
817
+ if (s.eventType === "STAGE_STARTED" && s.stage === stage && s.status === "in_progress") {
818
+ if (serverMessage) s.message = serverMessage;
819
+ s.status = "completed";
820
+ const durationMs = Number(event.durationMs);
821
+ if (Number.isFinite(durationMs)) s.elapsedMs = durationMs;
822
+ if (s.id === state.currentExecutingStepId) {
823
+ state.currentExecutingStepId = void 0;
824
+ }
825
+ break;
826
+ }
827
+ }
828
+ state.lastEventType = eventType;
829
+ break;
830
+ }
831
+ case "STAGE_FAILED": {
832
+ const stage = getEventText(event, "stage");
833
+ if (!stage) {
834
+ state.lastEventType = eventType;
835
+ break;
836
+ }
837
+ const serverMessage = getEventText(event, "message");
838
+ for (let i = state.steps.length - 1; i >= 0; i--) {
839
+ const s = state.steps[i];
840
+ if (s.eventType === "STAGE_STARTED" && s.stage === stage && s.status === "in_progress") {
841
+ if (serverMessage) s.message = serverMessage;
842
+ s.status = "error";
843
+ const durationMs = Number(event.durationMs);
844
+ if (Number.isFinite(durationMs)) s.elapsedMs = durationMs;
845
+ if (s.id === state.currentExecutingStepId) {
846
+ state.currentExecutingStepId = void 0;
847
+ }
848
+ break;
849
+ }
850
+ }
851
+ state.lastEventType = eventType;
852
+ break;
853
+ }
854
+ case "STAGE_SKIPPED": {
855
+ const stage = getEventText(event, "stage");
856
+ if (!stage) {
857
+ state.lastEventType = eventType;
858
+ break;
859
+ }
860
+ for (let i = state.steps.length - 1; i >= 0; i--) {
861
+ const s = state.steps[i];
862
+ if (s.eventType === "STAGE_STARTED" && s.stage === stage && s.status === "in_progress") {
863
+ if (s.id === state.currentExecutingStepId) {
864
+ state.currentExecutingStepId = void 0;
865
+ }
866
+ s.status = "skipped";
867
+ break;
868
+ }
869
+ }
870
+ state.lastEventType = eventType;
871
+ break;
872
+ }
873
+ default:
874
+ state.lastEventType = eventType;
875
+ break;
876
+ }
877
+ return state;
878
+ }
879
+
880
+ // src/utils/messageStateManager.ts
881
+ function buildFormattedThinking(steps, allThinkingText) {
882
+ const parts = [];
883
+ const safeSteps = steps ?? [];
884
+ const cleanAll = allThinkingText.replace(/^\s+/, "");
885
+ if (cleanAll) {
886
+ const firstStepWithThinking = safeSteps.find(
887
+ (s) => s.thinkingText && s.thinkingText.trim()
888
+ );
889
+ if (!firstStepWithThinking) {
890
+ parts.push("**Preflight**");
891
+ parts.push(cleanAll);
892
+ } else {
893
+ const stepText = firstStepWithThinking.thinkingText.trim();
894
+ const idx = cleanAll.indexOf(stepText);
895
+ if (idx > 0) {
896
+ const orphaned = cleanAll.substring(0, idx).replace(/\s+$/, "");
897
+ if (orphaned) {
898
+ parts.push("**Preflight**");
899
+ parts.push(orphaned);
900
+ }
901
+ }
902
+ }
903
+ }
904
+ for (const step of safeSteps) {
905
+ switch (step.eventType) {
906
+ case "STAGE_STARTED": {
907
+ if (step.message) parts.push(`**${step.message}**`);
908
+ break;
909
+ }
910
+ case "ORCHESTRATOR_THINKING":
911
+ parts.push("**Planning**");
912
+ if (step.message) parts.push(step.message);
913
+ break;
914
+ case "INTENT_STARTED": {
915
+ let label = step.message || "Processing";
916
+ const started = label.match(/^(.+?)\s+started$/i);
917
+ const progress = label.match(/^(.+?)\s+in progress$/i);
918
+ if (started) label = started[1];
919
+ else if (progress) label = progress[1];
920
+ parts.push(`**${label}**`);
921
+ if (step.thinkingText) parts.push(step.thinkingText);
922
+ break;
923
+ }
924
+ case "INTENT_PROGRESS": {
925
+ if (step.thinkingText) parts.push(step.thinkingText);
926
+ else if (step.message) parts.push(step.message);
927
+ break;
928
+ }
929
+ case "AGGREGATOR_THINKING":
930
+ parts.push("**Finalizing**");
931
+ if (step.message) parts.push(step.message);
932
+ break;
933
+ case "USER_ACTION_REQUIRED":
934
+ parts.push("**Verification Required**");
935
+ if (step.message) parts.push(step.message);
936
+ break;
937
+ case "USER_ACTION_SUCCESS":
938
+ parts.push(`\u2713 ${step.message || "Verification successful"}`);
939
+ break;
940
+ case "USER_ACTION_REJECTED":
941
+ parts.push(`\u2717 ${step.message || "Verification rejected"}`);
942
+ break;
943
+ case "USER_ACTION_EXPIRED":
944
+ parts.push(`\u2717 ${step.message || "Verification expired"}`);
945
+ break;
946
+ case "USER_ACTION_FAILED":
947
+ parts.push(`\u2717 ${step.message || "Verification failed"}`);
948
+ break;
949
+ }
950
+ }
951
+ return parts.length > 0 ? parts.join("\n") : allThinkingText;
952
+ }
953
+ function createCancelledMessageUpdate(steps, currentMessage) {
954
+ const updatedSteps = steps.map((step) => {
955
+ if (step.status === "in_progress") {
956
+ return { ...step, status: "pending" };
957
+ }
958
+ return step;
959
+ });
960
+ return {
961
+ isStreaming: false,
962
+ isCancelled: true,
963
+ steps: updatedSteps,
964
+ currentExecutingStepId: void 0,
965
+ // Preserve currentMessage so UI can show it with X icon
966
+ currentMessage: currentMessage || "Thinking..."
967
+ };
968
+ }
969
+
970
+ // src/utils/requestBuilder.ts
971
+ var DEFAULT_STREAM_ENDPOINT = "/api/playground/ask/stream";
972
+ function buildRequestBody(config, userMessage, sessionId, options) {
973
+ const sessionOwner = config.sessionParams;
974
+ const sessionAttributes = sessionOwner?.attributes && Object.keys(sessionOwner.attributes).length > 0 ? sessionOwner.attributes : void 0;
975
+ return {
976
+ agentId: config.agentId,
977
+ userInput: userMessage,
978
+ sessionId,
979
+ sessionOwnerLabel: sessionOwner?.name || void 0,
980
+ sessionAttributes,
981
+ analysisMode: options?.analysisMode
982
+ };
983
+ }
984
+ function buildStreamingUrl(config) {
985
+ const endpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
986
+ const stage = config.stage || "DEV";
987
+ const stageParamName = config.stageQueryParam ?? "stage";
988
+ const queryParams = new URLSearchParams({ [stageParamName]: stage });
989
+ return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
990
+ }
991
+ function buildUserActionUrl(config, userActionId, action) {
992
+ const endpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
993
+ const [endpointPath] = endpoint.split("?");
994
+ const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
995
+ const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
996
+ const encodedUserActionId = encodeURIComponent(userActionId);
997
+ return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
998
+ }
999
+ function buildResolveImagesUrl(config) {
1000
+ if (config.api.resolveImagesEndpoint) {
1001
+ return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
935
1002
  }
936
- return state;
1003
+ const streamEndpoint = config.api.streamEndpoint || DEFAULT_STREAM_ENDPOINT;
1004
+ const [endpointPath] = streamEndpoint.split("?");
1005
+ const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
1006
+ const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
1007
+ return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
1008
+ }
1009
+ function buildRequestHeaders(config) {
1010
+ const headers = {
1011
+ ...config.api.headers
1012
+ };
1013
+ if (config.api.authToken) {
1014
+ headers.Authorization = `Bearer ${config.api.authToken}`;
1015
+ }
1016
+ return headers;
1017
+ }
1018
+
1019
+ // src/utils/userActionClient.ts
1020
+ async function sendUserActionRequest(config, userActionId, action, data) {
1021
+ const url = buildUserActionUrl(config, userActionId, action);
1022
+ const baseHeaders = buildRequestHeaders(config);
1023
+ const hasBody = data !== void 0;
1024
+ const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
1025
+ const response = await fetch(url, {
1026
+ method: "POST",
1027
+ headers,
1028
+ body: hasBody ? JSON.stringify(data) : void 0
1029
+ });
1030
+ if (!response.ok) {
1031
+ const errorText = await response.text();
1032
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
1033
+ }
1034
+ return await response.json();
1035
+ }
1036
+ async function submitUserAction(config, userActionId, data) {
1037
+ return sendUserActionRequest(config, userActionId, "submit", data);
1038
+ }
1039
+ async function cancelUserAction(config, userActionId) {
1040
+ return sendUserActionRequest(config, userActionId, "cancel");
1041
+ }
1042
+ async function resendUserAction(config, userActionId) {
1043
+ return sendUserActionRequest(config, userActionId, "resend");
937
1044
  }
1045
+ var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
1046
+ var chatStore = {
1047
+ get(key) {
1048
+ const raw = storage.getString(key);
1049
+ if (!raw) return [];
1050
+ try {
1051
+ return JSON.parse(raw);
1052
+ } catch {
1053
+ return [];
1054
+ }
1055
+ },
1056
+ set(key, messages) {
1057
+ storage.set(key, JSON.stringify(messages));
1058
+ },
1059
+ delete(key) {
1060
+ storage.delete(key);
1061
+ }
1062
+ };
1063
+
1064
+ // src/utils/activeStreamStore.ts
1065
+ var streams = /* @__PURE__ */ new Map();
1066
+ var activeStreamStore = {
1067
+ has(key) {
1068
+ return streams.has(key);
1069
+ },
1070
+ get(key) {
1071
+ const entry = streams.get(key);
1072
+ if (!entry) return null;
1073
+ return { messages: entry.messages, isWaiting: entry.isWaiting };
1074
+ },
1075
+ // Called before startStream — registers the controller and initial messages
1076
+ start(key, abortController, initialMessages) {
1077
+ const existing = streams.get(key);
1078
+ streams.set(key, {
1079
+ messages: initialMessages,
1080
+ isWaiting: true,
1081
+ abortController,
1082
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
1083
+ });
1084
+ },
1085
+ // Called by the stream on every event — applies the same updater pattern React uses
1086
+ applyMessages(key, updater) {
1087
+ const entry = streams.get(key);
1088
+ if (!entry) return;
1089
+ const next = typeof updater === "function" ? updater(entry.messages) : updater;
1090
+ entry.messages = next;
1091
+ entry.listeners.forEach((l) => l(next, entry.isWaiting));
1092
+ },
1093
+ setWaiting(key, waiting) {
1094
+ const entry = streams.get(key);
1095
+ if (!entry) return;
1096
+ entry.isWaiting = waiting;
1097
+ entry.listeners.forEach((l) => l(entry.messages, waiting));
1098
+ },
1099
+ // Called when stream completes — persists to chatStore and cleans up
1100
+ complete(key) {
1101
+ const entry = streams.get(key);
1102
+ if (!entry) return;
1103
+ entry.isWaiting = false;
1104
+ entry.listeners.forEach((l) => l(entry.messages, false));
1105
+ const toSave = entry.messages.filter((m) => !m.isStreaming);
1106
+ if (toSave.length > 0) chatStore.set(key, toSave);
1107
+ streams.delete(key);
1108
+ },
1109
+ // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
1110
+ subscribe(key, listener) {
1111
+ const entry = streams.get(key);
1112
+ if (!entry) return () => {
1113
+ };
1114
+ entry.listeners.add(listener);
1115
+ return () => {
1116
+ streams.get(key)?.listeners.delete(listener);
1117
+ };
1118
+ },
1119
+ // Explicit user cancel — aborts the controller and removes the entry
1120
+ abort(key) {
1121
+ const entry = streams.get(key);
1122
+ if (!entry) return;
1123
+ entry.abortController.abort();
1124
+ streams.delete(key);
1125
+ }
1126
+ };
938
1127
 
939
1128
  // src/utils/ragImageResolver.ts
940
1129
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
@@ -995,12 +1184,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
995
1184
  const callbacksRef = react.useRef(callbacks);
996
1185
  callbacksRef.current = callbacks;
997
1186
  const startStream = react.useCallback(
998
- async (userMessage, streamingId, sessionId, externalAbortController) => {
1187
+ async (userMessage, streamingId, sessionId, externalAbortController, options) => {
999
1188
  abortControllerRef.current?.abort();
1000
1189
  const abortController = externalAbortController ?? new AbortController();
1001
1190
  abortControllerRef.current = abortController;
1002
1191
  const state = createInitialV2State();
1003
- const streamStartedAt = Date.now();
1004
1192
  const updateMessage = (update) => {
1005
1193
  if (abortController.signal.aborted) return;
1006
1194
  setMessages(
@@ -1010,7 +1198,12 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1010
1198
  );
1011
1199
  };
1012
1200
  const currentConfig = configRef.current;
1013
- const requestBody = buildRequestBody(currentConfig, userMessage, sessionId);
1201
+ const requestBody = buildRequestBody(
1202
+ currentConfig,
1203
+ userMessage,
1204
+ sessionId,
1205
+ options
1206
+ );
1014
1207
  const url = buildStreamingUrl(currentConfig);
1015
1208
  const headers = buildRequestHeaders(currentConfig);
1016
1209
  try {
@@ -1018,11 +1211,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1018
1211
  signal: abortController.signal,
1019
1212
  onEvent: (event) => {
1020
1213
  if (abortController.signal.aborted) return;
1021
- if (typeof event.eventType === "string" && event.eventType.toUpperCase() === "KEEP_ALIVE") {
1022
- if (event.executionId) state.executionId = event.executionId;
1023
- if (event.sessionId) state.sessionId = event.sessionId;
1024
- return;
1025
- }
1026
1214
  processStreamEventV2(event, state);
1027
1215
  const eventType = event.eventType;
1028
1216
  if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
@@ -1036,7 +1224,18 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1036
1224
  }
1037
1225
  const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
1038
1226
  const lastInProgressStep = [...state.steps].reverse().find((s) => s.status === "in_progress");
1039
- const currentMessage = activeStep?.message || lastInProgressStep?.message || getEventMessage(event);
1227
+ const useful = (m) => m && !isBlandStatus(m) ? m : void 0;
1228
+ const latestUsefulStep = [...state.steps].reverse().find(
1229
+ (s) => s.message && !isBlandStatus(s.message)
1230
+ );
1231
+ const currentMessage = useful(activeStep?.message) ?? useful(lastInProgressStep?.message) ?? latestUsefulStep?.message ?? useful(getEventMessage(event)) ?? // Last-resort: every candidate is bland (very first event,
1232
+ // nothing useful seen yet). Render the bland label so the
1233
+ // bubble isn't blank.
1234
+ activeStep?.message ?? lastInProgressStep?.message ?? getEventMessage(event);
1235
+ if (currentMessage) {
1236
+ callbacksRef.current.onStatusMessage?.(currentMessage);
1237
+ }
1238
+ callbacksRef.current.onStepsUpdate?.([...state.steps]);
1040
1239
  if (state.hasError) {
1041
1240
  updateMessage({
1042
1241
  streamingContent: FRIENDLY_ERROR_MESSAGE,
@@ -1052,7 +1251,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1052
1251
  });
1053
1252
  } else {
1054
1253
  updateMessage({
1055
- streamingContent: "",
1254
+ streamingContent: state.finalResponse,
1056
1255
  content: "",
1057
1256
  currentMessage,
1058
1257
  streamProgress: "processing",
@@ -1069,6 +1268,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1069
1268
  },
1070
1269
  onError: (error) => {
1071
1270
  setIsWaitingForResponse(false);
1271
+ callbacksRef.current.onStatusMessage?.(null);
1072
1272
  if (error.name !== "AbortError") {
1073
1273
  callbacksRef.current.onError?.(error);
1074
1274
  }
@@ -1106,6 +1306,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1106
1306
  },
1107
1307
  onComplete: () => {
1108
1308
  setIsWaitingForResponse(false);
1309
+ callbacksRef.current.onStatusMessage?.(null);
1310
+ callbacksRef.current.onStepsUpdate?.([]);
1109
1311
  if (state.userActionPending) {
1110
1312
  state.userActionPending = false;
1111
1313
  state.userActionRequest = void 0;
@@ -1129,14 +1331,18 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1129
1331
  isError: state.hasError,
1130
1332
  errorDetails: state.hasError ? state.errorMessage : void 0,
1131
1333
  executionId: state.executionId,
1132
- tracingData: state.finalData,
1334
+ // Defensive: tracingData must be an object (or undefined)
1335
+ // so the JSON viewer doesn't char-iterate a string. The
1336
+ // event processor already drops bare strings; this is
1337
+ // the last stop before the message leaves the SDK.
1338
+ tracingData: state.finalData != null && typeof state.finalData === "object" ? state.finalData : void 0,
1133
1339
  steps: state.hasError ? [] : [...state.steps],
1134
1340
  isCancelled: false,
1135
1341
  currentExecutingStepId: void 0,
1136
1342
  userActionResult: state.userActionResult,
1137
1343
  formattedThinkingText: state.hasError ? void 0 : state.formattedThinkingText || void 0,
1138
1344
  isResolvingImages: needsImageResolve,
1139
- thinkingDurationSec: state.hasError ? void 0 : Math.max(0, Math.round((Date.now() - streamStartedAt) / 1e3))
1345
+ totalElapsedMs: state.hasError ? void 0 : state.totalElapsedMs
1140
1346
  };
1141
1347
  setMessages(
1142
1348
  (prev) => prev.map(
@@ -1221,199 +1427,45 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1221
1427
  };
1222
1428
  }
1223
1429
 
1224
- // src/utils/workflowUsersClient.ts
1225
- var DEFAULT_SESSION_PAGE_SIZE = 20;
1226
- var DEFAULT_CONVERSATION_PAGE_SIZE = 50;
1227
- function getStageParamName2(config) {
1228
- return config.api.stageQueryParam ?? "stage";
1229
- }
1230
- function requireOwnerId(config) {
1231
- const ownerId = config.session?.owner?.id;
1232
- if (!ownerId) {
1233
- throw new Error(
1234
- "workflowUsersClient: session.owner.id is required to call this endpoint."
1235
- );
1236
- }
1237
- return ownerId;
1238
- }
1239
- function requireWorkflowName(config) {
1240
- const workflowName = config.workflow.name;
1241
- if (!workflowName) {
1242
- throw new Error(
1243
- "workflowUsersClient: workflow.name is required to call this endpoint."
1244
- );
1245
- }
1246
- return workflowName;
1247
- }
1248
- function requireWorkflowId(config) {
1249
- const workflowId = config.workflow.id;
1250
- if (!workflowId) {
1251
- throw new Error(
1252
- "workflowUsersClient: workflow.id is required to call this endpoint."
1253
- );
1254
- }
1255
- return workflowId;
1256
- }
1257
- function isPlaygroundYaakAuth(config) {
1258
- if (config.api.authToken) return false;
1259
- const headers = config.api.headers;
1260
- if (!headers) return false;
1261
- for (const key of Object.keys(headers)) {
1262
- const lower = key.toLowerCase();
1263
- if (lower === "yaak-api-key" || lower === "x-yaak-api-key") return true;
1264
- }
1265
- return false;
1266
- }
1267
- async function fetchJson(url, headers, signal) {
1268
- const response = await fetch(url, {
1269
- method: "GET",
1270
- headers,
1271
- signal
1272
- });
1273
- if (!response.ok) {
1274
- const errorText = await response.text();
1275
- throw new Error(`HTTP ${response.status}: ${errorText}`);
1276
- }
1277
- return await response.json();
1278
- }
1279
- async function listSessions(config, opts = {}) {
1280
- const ownerId = requireOwnerId(config);
1281
- const useYaakEndpoint = isPlaygroundYaakAuth(config);
1282
- const params = new URLSearchParams({
1283
- page: String(opts.page ?? 0),
1284
- size: String(opts.size ?? DEFAULT_SESSION_PAGE_SIZE)
1285
- });
1286
- if (useYaakEndpoint) {
1287
- params.set("workflowUserId", ownerId);
1288
- params.set("workflowName", requireWorkflowName(config));
1289
- } else {
1290
- params.set("workflowId", requireWorkflowId(config));
1291
- }
1292
- if (config.workflow.stage) {
1293
- params.set(getStageParamName2(config), config.workflow.stage);
1294
- }
1295
- const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/sessions?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1296
- ownerId
1297
- )}/sessions?${params.toString()}`;
1298
- return fetchJson(
1299
- url,
1300
- buildRequestHeaders(config),
1301
- opts.signal
1302
- );
1303
- }
1304
- async function listConversations(config, opts) {
1305
- const ownerId = requireOwnerId(config);
1306
- const useYaakEndpoint = isPlaygroundYaakAuth(config);
1307
- const params = new URLSearchParams({
1308
- sessionId: opts.sessionId,
1309
- page: String(opts.page ?? 0),
1310
- size: String(opts.size ?? DEFAULT_CONVERSATION_PAGE_SIZE)
1311
- });
1312
- if (useYaakEndpoint) {
1313
- params.set("workflowUserId", ownerId);
1314
- params.set("workflowName", requireWorkflowName(config));
1315
- }
1316
- if (config.workflow.stage) {
1317
- params.set(getStageParamName2(config), config.workflow.stage);
1318
- }
1319
- const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/conversations?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1320
- ownerId
1321
- )}/conversations?${params.toString()}`;
1322
- return fetchJson(
1323
- url,
1324
- buildRequestHeaders(config),
1325
- opts.signal
1326
- );
1327
- }
1328
-
1329
1430
  // src/hooks/useChatV2.ts
1330
- function conversationEntryToMessages(entry, sessionId) {
1331
- return [
1332
- {
1333
- id: `history-user-${entry.executionId}`,
1334
- sessionId,
1335
- role: "user",
1336
- content: entry.query,
1337
- timestamp: entry.createdAt,
1338
- isHistorical: true
1339
- },
1340
- {
1341
- id: `history-assistant-${entry.executionId}`,
1342
- sessionId,
1343
- role: "assistant",
1344
- content: entry.response,
1345
- timestamp: entry.createdAt,
1346
- executionId: entry.executionId,
1347
- isHistorical: true
1348
- }
1349
- ];
1350
- }
1351
- function streamKeyFor(scopeKey, sessionId) {
1352
- return `${scopeKey}|sid:${sessionId ?? ""}`;
1353
- }
1354
1431
  function useChatV2(config, callbacks = {}) {
1355
- const scopeKey = react.useMemo(() => buildScopeKey(config), [
1356
- config.session?.userId,
1357
- config.workflow?.id,
1358
- config.workflow?.version,
1359
- config.workflow?.stage
1360
- ]);
1361
- const initialSessionId = chatStore.get(scopeKey).find((m) => m.sessionId)?.sessionId ?? config.session?.initialId ?? void 0;
1362
1432
  const [messages, setMessages] = react.useState(() => {
1363
- const stored = chatStore.get(scopeKey);
1364
- if (stored.length > 0) return stored;
1365
- return config.session?.initialMessages ?? [];
1433
+ if (config.userId) return chatStore.get(config.userId);
1434
+ return config.initialMessages ?? [];
1366
1435
  });
1367
1436
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
1368
- const [loadingSessionId, setLoadingSessionId] = react.useState(void 0);
1369
- const [currentSessionId, setCurrentSessionId] = react.useState(initialSessionId);
1370
- const sessionIdRef = react.useRef(initialSessionId);
1371
- const prevScopeKeyRef = react.useRef(scopeKey);
1372
- const activeStreamSessionRef = react.useRef(void 0);
1437
+ const sessionIdRef = react.useRef(
1438
+ config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
1439
+ );
1440
+ const prevUserIdRef = react.useRef(config.userId);
1373
1441
  const callbacksRef = react.useRef(callbacks);
1374
1442
  callbacksRef.current = callbacks;
1375
1443
  const configRef = react.useRef(config);
1376
1444
  configRef.current = config;
1377
- const scopeKeyRef = react.useRef(scopeKey);
1378
- scopeKeyRef.current = scopeKey;
1379
1445
  const messagesRef = react.useRef(messages);
1380
1446
  messagesRef.current = messages;
1381
1447
  const storeAwareSetMessages = react.useCallback(
1382
1448
  (updater) => {
1383
- const scope = scopeKeyRef.current;
1384
- const streamSid = activeStreamSessionRef.current;
1385
- if (streamSid !== void 0) {
1386
- const streamKey = streamKeyFor(scope, streamSid);
1387
- if (activeStreamStore.has(streamKey)) {
1388
- activeStreamStore.applyMessages(
1389
- streamKey,
1390
- updater
1391
- );
1392
- }
1393
- if (sessionIdRef.current === streamSid) {
1394
- setMessages(updater);
1395
- }
1396
- return;
1449
+ const { userId } = configRef.current;
1450
+ if (userId && activeStreamStore.has(userId)) {
1451
+ activeStreamStore.applyMessages(userId, updater);
1397
1452
  }
1398
1453
  setMessages(updater);
1399
1454
  },
1455
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1400
1456
  []
1401
1457
  );
1402
- const storeAwareSetIsWaiting = react.useCallback((waiting) => {
1403
- const scope = scopeKeyRef.current;
1404
- const streamSid = activeStreamSessionRef.current;
1405
- if (streamSid !== void 0) {
1406
- const streamKey = streamKeyFor(scope, streamSid);
1407
- if (activeStreamStore.has(streamKey)) {
1408
- activeStreamStore.setWaiting(streamKey, waiting);
1458
+ const storeAwareSetIsWaiting = react.useCallback(
1459
+ (waiting) => {
1460
+ const { userId } = configRef.current;
1461
+ if (userId && activeStreamStore.has(userId)) {
1462
+ activeStreamStore.setWaiting(userId, waiting);
1409
1463
  }
1410
- if (sessionIdRef.current === streamSid) {
1411
- setIsWaitingForResponse(waiting);
1412
- }
1413
- return;
1414
- }
1415
- setIsWaitingForResponse(waiting);
1416
- }, []);
1464
+ setIsWaitingForResponse(waiting);
1465
+ },
1466
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1467
+ []
1468
+ );
1417
1469
  const [userActionState, setUserActionState] = react.useState({
1418
1470
  request: null,
1419
1471
  result: null,
@@ -1421,43 +1473,38 @@ function useChatV2(config, callbacks = {}) {
1421
1473
  });
1422
1474
  const userActionStateRef = react.useRef(userActionState);
1423
1475
  userActionStateRef.current = userActionState;
1424
- const wrappedCallbacks = react.useMemo(
1425
- () => ({
1426
- ...callbacksRef.current,
1427
- onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1428
- onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1429
- onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1430
- onError: (error) => callbacksRef.current.onError?.(error),
1431
- onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1432
- onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1433
- onUserActionRequired: (request) => {
1434
- setUserActionState((prev) => ({ ...prev, request, result: null }));
1435
- callbacksRef.current.onUserActionRequired?.(request);
1436
- },
1437
- onUserActionEvent: (eventType, message) => {
1438
- switch (eventType) {
1439
- case "USER_ACTION_SUCCESS":
1440
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1441
- break;
1442
- case "USER_ACTION_REJECTED":
1443
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1444
- break;
1445
- case "USER_ACTION_EXPIRED":
1446
- case "USER_ACTION_FAILED":
1447
- setUserActionState((prev) => ({ ...prev, request: null }));
1448
- break;
1449
- case "USER_ACTION_INVALID":
1450
- setUserActionState((prev) => ({
1451
- ...prev,
1452
- clearOtpTrigger: prev.clearOtpTrigger + 1
1453
- }));
1454
- break;
1455
- }
1456
- callbacksRef.current.onUserActionEvent?.(eventType, message);
1476
+ const wrappedCallbacks = react.useMemo(() => ({
1477
+ ...callbacksRef.current,
1478
+ onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1479
+ onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1480
+ onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1481
+ onError: (error) => callbacksRef.current.onError?.(error),
1482
+ onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1483
+ onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1484
+ onUserActionRequired: (request) => {
1485
+ setUserActionState((prev) => ({ ...prev, request, result: null }));
1486
+ callbacksRef.current.onUserActionRequired?.(request);
1487
+ },
1488
+ onUserActionEvent: (eventType, message) => {
1489
+ switch (eventType) {
1490
+ case "USER_ACTION_SUCCESS":
1491
+ setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1492
+ break;
1493
+ case "USER_ACTION_REJECTED":
1494
+ setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1495
+ break;
1496
+ case "USER_ACTION_EXPIRED":
1497
+ case "USER_ACTION_FAILED":
1498
+ setUserActionState((prev) => ({ ...prev, request: null }));
1499
+ break;
1500
+ case "USER_ACTION_INVALID":
1501
+ setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
1502
+ break;
1457
1503
  }
1458
- }),
1459
- []
1460
- );
1504
+ callbacksRef.current.onUserActionEvent?.(eventType, message);
1505
+ }
1506
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1507
+ }), []);
1461
1508
  const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
1462
1509
  config,
1463
1510
  wrappedCallbacks,
@@ -1465,12 +1512,10 @@ function useChatV2(config, callbacks = {}) {
1465
1512
  storeAwareSetIsWaiting
1466
1513
  );
1467
1514
  const sendMessage = react.useCallback(
1468
- async (userMessage) => {
1515
+ async (userMessage, options) => {
1469
1516
  if (!userMessage.trim()) return;
1470
- const autoGen = configRef.current.session?.autoGenerateId;
1471
- if (!sessionIdRef.current && autoGen !== false) {
1517
+ if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1472
1518
  sessionIdRef.current = generateId();
1473
- setCurrentSessionId(sessionIdRef.current);
1474
1519
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1475
1520
  }
1476
1521
  const userMessageId = `user-${Date.now()}`;
@@ -1502,40 +1547,39 @@ function useChatV2(config, callbacks = {}) {
1502
1547
  };
1503
1548
  setMessages((prev) => [...prev, streamingMsg]);
1504
1549
  const abortController = new AbortController();
1505
- const scope = scopeKeyRef.current;
1506
- const streamSessionId = sessionIdRef.current;
1507
- const streamKey = streamKeyFor(scope, streamSessionId);
1508
- activeStreamSessionRef.current = streamSessionId;
1509
- const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1510
- activeStreamStore.start(streamKey, abortController, initialMessages);
1550
+ const { userId } = configRef.current;
1551
+ if (userId) {
1552
+ const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1553
+ activeStreamStore.start(userId, abortController, initialMessages);
1554
+ }
1511
1555
  const newSessionId = await startStream(
1512
1556
  userMessage,
1513
1557
  streamingId,
1514
- streamSessionId,
1515
- abortController
1558
+ sessionIdRef.current,
1559
+ abortController,
1560
+ options
1516
1561
  );
1517
- activeStreamStore.complete(streamKey);
1518
- activeStreamSessionRef.current = void 0;
1519
- if (!abortController.signal.aborted && newSessionId && newSessionId !== streamSessionId && sessionIdRef.current === streamSessionId) {
1562
+ if (userId) {
1563
+ activeStreamStore.complete(userId);
1564
+ }
1565
+ if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1520
1566
  sessionIdRef.current = newSessionId;
1521
- setCurrentSessionId(newSessionId);
1522
1567
  }
1523
1568
  },
1524
1569
  [startStream]
1525
1570
  );
1526
1571
  const clearMessages = react.useCallback(() => {
1527
- chatStore.delete(scopeKeyRef.current);
1572
+ if (configRef.current.userId) {
1573
+ chatStore.delete(configRef.current.userId);
1574
+ }
1528
1575
  setMessages([]);
1529
1576
  }, []);
1530
1577
  const prependMessages = react.useCallback((msgs) => {
1531
1578
  setMessages((prev) => [...msgs, ...prev]);
1532
1579
  }, []);
1533
1580
  const cancelStream = react.useCallback(() => {
1534
- const scope = scopeKeyRef.current;
1535
- const viewSid = sessionIdRef.current;
1536
- const viewStreamKey = streamKeyFor(scope, viewSid);
1537
- if (activeStreamStore.has(viewStreamKey)) {
1538
- activeStreamStore.abort(viewStreamKey);
1581
+ if (configRef.current.userId) {
1582
+ activeStreamStore.abort(configRef.current.userId);
1539
1583
  }
1540
1584
  cancelStreamManager();
1541
1585
  setIsWaitingForResponse(false);
@@ -1545,7 +1589,10 @@ function useChatV2(config, callbacks = {}) {
1545
1589
  if (msg.isStreaming) {
1546
1590
  return {
1547
1591
  ...msg,
1548
- ...createCancelledMessageUpdate(msg.steps || [], msg.currentMessage)
1592
+ ...createCancelledMessageUpdate(
1593
+ msg.steps || [],
1594
+ msg.currentMessage
1595
+ )
1549
1596
  };
1550
1597
  }
1551
1598
  return msg;
@@ -1553,37 +1600,39 @@ function useChatV2(config, callbacks = {}) {
1553
1600
  );
1554
1601
  }, [cancelStreamManager]);
1555
1602
  const resetSession = react.useCallback(() => {
1556
- const scope = scopeKeyRef.current;
1557
- const viewSid = sessionIdRef.current;
1558
- const viewStreamKey = streamKeyFor(scope, viewSid);
1559
- if (activeStreamStore.has(viewStreamKey)) {
1560
- activeStreamStore.abort(viewStreamKey);
1603
+ if (configRef.current.userId) {
1604
+ activeStreamStore.abort(configRef.current.userId);
1605
+ chatStore.delete(configRef.current.userId);
1561
1606
  }
1562
- chatStore.delete(scope);
1563
1607
  setMessages([]);
1564
1608
  sessionIdRef.current = void 0;
1565
- setCurrentSessionId(void 0);
1566
- activeStreamSessionRef.current = void 0;
1567
1609
  abortControllerRef.current?.abort();
1568
1610
  setIsWaitingForResponse(false);
1569
1611
  setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1570
1612
  }, []);
1571
- const getSessionId = react.useCallback(() => sessionIdRef.current, []);
1572
- const getMessages = react.useCallback(() => messages, [messages]);
1573
- const approveUserAction = react.useCallback(async (otp) => {
1574
- const request = userActionStateRef.current.request;
1575
- if (!request) return;
1576
- try {
1577
- await submitUserAction(configRef.current, request.userActionId, { otp });
1578
- } catch (error) {
1579
- setUserActionState((prev) => ({
1580
- ...prev,
1581
- clearOtpTrigger: prev.clearOtpTrigger + 1
1582
- }));
1583
- callbacksRef.current.onError?.(error);
1584
- throw error;
1585
- }
1613
+ const getSessionId = react.useCallback(() => {
1614
+ return sessionIdRef.current;
1586
1615
  }, []);
1616
+ const getMessages = react.useCallback(() => {
1617
+ return messages;
1618
+ }, [messages]);
1619
+ const approveUserAction = react.useCallback(
1620
+ async (otp) => {
1621
+ const request = userActionStateRef.current.request;
1622
+ if (!request) return;
1623
+ try {
1624
+ await submitUserAction(configRef.current, request.userActionId, { otp });
1625
+ } catch (error) {
1626
+ setUserActionState((prev) => ({
1627
+ ...prev,
1628
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1629
+ }));
1630
+ callbacksRef.current.onError?.(error);
1631
+ throw error;
1632
+ }
1633
+ },
1634
+ []
1635
+ );
1587
1636
  const rejectUserAction = react.useCallback(async () => {
1588
1637
  const request = userActionStateRef.current.request;
1589
1638
  if (!request) return;
@@ -1617,91 +1666,43 @@ function useChatV2(config, callbacks = {}) {
1617
1666
  throw error;
1618
1667
  }
1619
1668
  }, []);
1620
- const inFlightLoadRef = react.useRef(null);
1621
- const loadingSessionIdRef = react.useRef(void 0);
1622
- loadingSessionIdRef.current = loadingSessionId;
1623
- const loadSession = react.useCallback(async (sessionId) => {
1624
- const inFlight = inFlightLoadRef.current;
1625
- if (inFlight && inFlight.sessionId === sessionId) {
1626
- return inFlight.promise;
1627
- }
1628
- if (sessionIdRef.current === sessionId && loadingSessionIdRef.current !== sessionId) {
1629
- return;
1630
- }
1631
- const run = async () => {
1632
- const scope = scopeKeyRef.current;
1633
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1634
- sessionIdRef.current = sessionId;
1635
- setCurrentSessionId(sessionId);
1636
- callbacksRef.current.onSessionIdChange?.(sessionId);
1637
- const streamKey = streamKeyFor(scope, sessionId);
1638
- const active = activeStreamStore.get(streamKey);
1639
- if (active) {
1640
- setMessages(active.messages);
1641
- setIsWaitingForResponse(active.isWaiting);
1642
- setLoadingSessionId(void 0);
1643
- return;
1644
- }
1645
- setIsWaitingForResponse(false);
1646
- setMessages([]);
1647
- chatStore.delete(scope);
1648
- setLoadingSessionId(sessionId);
1649
- try {
1650
- const response = await listConversations(configRef.current, { sessionId });
1651
- const entries = response.data ?? [];
1652
- const historical = entries.flatMap((entry) => conversationEntryToMessages(entry, sessionId));
1653
- if (sessionIdRef.current === sessionId) {
1654
- setMessages(historical);
1655
- }
1656
- if (historical.length > 0) {
1657
- chatStore.set(scope, historical);
1658
- }
1659
- } catch (error) {
1660
- callbacksRef.current.onError?.(error);
1661
- throw error;
1662
- } finally {
1663
- setLoadingSessionId((current) => current === sessionId ? void 0 : current);
1664
- }
1665
- };
1666
- const promise = run().finally(() => {
1667
- if (inFlightLoadRef.current?.sessionId === sessionId) {
1668
- inFlightLoadRef.current = null;
1669
- }
1670
- });
1671
- inFlightLoadRef.current = { sessionId, promise };
1672
- return promise;
1673
- }, []);
1674
1669
  react.useEffect(() => {
1675
- const key = streamKeyFor(scopeKey, currentSessionId);
1676
- const unsubscribe = activeStreamStore.subscribe(key, (msgs, isWaiting) => {
1670
+ const { userId } = config;
1671
+ if (!userId) return;
1672
+ const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1677
1673
  setMessages(msgs);
1678
1674
  setIsWaitingForResponse(isWaiting);
1679
1675
  });
1680
- const active = activeStreamStore.get(key);
1676
+ const active = activeStreamStore.get(userId);
1681
1677
  if (active) {
1682
1678
  setMessages(active.messages);
1683
1679
  setIsWaitingForResponse(active.isWaiting);
1684
1680
  }
1685
1681
  return unsubscribe;
1686
- }, [scopeKey, currentSessionId]);
1682
+ }, []);
1687
1683
  react.useEffect(() => {
1684
+ if (!config.userId) return;
1688
1685
  const toSave = messages.filter((m) => !m.isStreaming);
1689
1686
  if (toSave.length > 0) {
1690
- chatStore.set(scopeKey, toSave);
1687
+ chatStore.set(config.userId, toSave);
1691
1688
  }
1692
- }, [messages, scopeKey]);
1689
+ }, [messages, config.userId]);
1693
1690
  react.useEffect(() => {
1694
- const prevKey = prevScopeKeyRef.current;
1695
- prevScopeKeyRef.current = scopeKey;
1696
- if (prevKey === scopeKey) return;
1697
- const stored = chatStore.get(scopeKey);
1698
- setMessages(stored);
1699
- const restoredSessionId = stored.find((m) => m.sessionId)?.sessionId ?? configRef.current.session?.initialId ?? void 0;
1700
- sessionIdRef.current = restoredSessionId;
1701
- setCurrentSessionId(restoredSessionId);
1702
- setIsWaitingForResponse(false);
1703
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1704
- }, [scopeKey]);
1691
+ const prevUserId = prevUserIdRef.current;
1692
+ prevUserIdRef.current = config.userId;
1693
+ if (prevUserId === config.userId) return;
1694
+ if (prevUserId && !config.userId) {
1695
+ chatStore.delete(prevUserId);
1696
+ setMessages([]);
1697
+ sessionIdRef.current = void 0;
1698
+ setIsWaitingForResponse(false);
1699
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1700
+ } else if (config.userId) {
1701
+ const stored = chatStore.get(config.userId);
1702
+ setMessages(stored);
1703
+ sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
1704
+ }
1705
+ }, [config.userId]);
1705
1706
  return {
1706
1707
  messages,
1707
1708
  sendMessage,
@@ -1712,13 +1713,11 @@ function useChatV2(config, callbacks = {}) {
1712
1713
  getSessionId,
1713
1714
  getMessages,
1714
1715
  isWaitingForResponse,
1715
- sessionId: currentSessionId,
1716
+ sessionId: sessionIdRef.current,
1716
1717
  userActionState,
1717
1718
  approveUserAction,
1718
1719
  rejectUserAction,
1719
- resendOtp,
1720
- loadSession,
1721
- loadingSessionId
1720
+ resendOtp
1722
1721
  };
1723
1722
  }
1724
1723
  function useVoice() {
@@ -1838,18 +1837,14 @@ function useVoice() {
1838
1837
  }
1839
1838
 
1840
1839
  exports.buildFormattedThinking = buildFormattedThinking;
1841
- exports.buildScopeKey = buildScopeKey;
1842
1840
  exports.cancelUserAction = cancelUserAction;
1843
1841
  exports.createInitialV2State = createInitialV2State;
1844
1842
  exports.generateId = generateId;
1845
- exports.listConversations = listConversations;
1846
- exports.listSessions = listSessions;
1847
1843
  exports.processStreamEventV2 = processStreamEventV2;
1848
1844
  exports.resendUserAction = resendUserAction;
1849
1845
  exports.streamWorkflowEvents = streamWorkflowEvents;
1850
1846
  exports.submitUserAction = submitUserAction;
1851
1847
  exports.useChatV2 = useChatV2;
1852
1848
  exports.useVoice = useVoice;
1853
- exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;
1854
1849
  //# sourceMappingURL=index.native.js.map
1855
1850
  //# sourceMappingURL=index.native.js.map