@paymanai/payman-typescript-ask-sdk 2.0.2 → 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.
- package/dist/index.d.mts +190 -148
- package/dist/index.d.ts +190 -148
- package/dist/index.js +698 -692
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +700 -690
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +690 -693
- package/dist/index.native.js.map +1 -1
- package/package.json +2 -2
package/dist/index.native.js
CHANGED
|
@@ -136,7 +136,38 @@ async function streamWorkflowEvents(url, body, headers, options = {}) {
|
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// src/utils/
|
|
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 "
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return "
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
case "
|
|
168
|
-
return "
|
|
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,304 +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
|
-
storage.delete(key);
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
// src/utils/activeStreamStore.ts
|
|
464
|
-
var streams = /* @__PURE__ */ new Map();
|
|
465
|
-
var activeStreamStore = {
|
|
466
|
-
has(key) {
|
|
467
|
-
return streams.has(key);
|
|
468
|
-
},
|
|
469
|
-
get(key) {
|
|
470
|
-
const entry = streams.get(key);
|
|
471
|
-
if (!entry) return null;
|
|
472
|
-
return { messages: entry.messages, isWaiting: entry.isWaiting };
|
|
473
|
-
},
|
|
474
|
-
// Called before startStream — registers the controller and initial messages
|
|
475
|
-
start(key, abortController, initialMessages) {
|
|
476
|
-
const existing = streams.get(key);
|
|
477
|
-
streams.set(key, {
|
|
478
|
-
messages: initialMessages,
|
|
479
|
-
isWaiting: true,
|
|
480
|
-
abortController,
|
|
481
|
-
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
482
|
-
});
|
|
483
|
-
},
|
|
484
|
-
// Called by the stream on every event — applies the same updater pattern React uses
|
|
485
|
-
applyMessages(key, updater) {
|
|
486
|
-
const entry = streams.get(key);
|
|
487
|
-
if (!entry) return;
|
|
488
|
-
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
489
|
-
entry.messages = next;
|
|
490
|
-
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
491
|
-
},
|
|
492
|
-
setWaiting(key, waiting) {
|
|
493
|
-
const entry = streams.get(key);
|
|
494
|
-
if (!entry) return;
|
|
495
|
-
entry.isWaiting = waiting;
|
|
496
|
-
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
497
|
-
},
|
|
498
|
-
// Called when stream completes — persists to chatStore and cleans up
|
|
499
|
-
complete(key) {
|
|
500
|
-
const entry = streams.get(key);
|
|
501
|
-
if (!entry) return;
|
|
502
|
-
entry.isWaiting = false;
|
|
503
|
-
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
504
|
-
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
505
|
-
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
506
|
-
streams.delete(key);
|
|
507
|
-
},
|
|
508
|
-
// Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
|
|
509
|
-
subscribe(key, listener) {
|
|
510
|
-
const entry = streams.get(key);
|
|
511
|
-
if (!entry) return () => {
|
|
512
|
-
};
|
|
513
|
-
entry.listeners.add(listener);
|
|
514
|
-
return () => {
|
|
515
|
-
streams.get(key)?.listeners.delete(listener);
|
|
516
|
-
};
|
|
517
|
-
},
|
|
518
|
-
// Explicit user cancel — aborts the controller and removes the entry
|
|
519
|
-
abort(key) {
|
|
520
|
-
const entry = streams.get(key);
|
|
521
|
-
if (!entry) return;
|
|
522
|
-
entry.abortController.abort();
|
|
523
|
-
streams.delete(key);
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
// src/utils/v2EventProcessor.ts
|
|
528
311
|
function getEventText(event, field) {
|
|
529
312
|
const value = event[field];
|
|
530
313
|
return typeof value === "string" ? value.trim() : "";
|
|
@@ -548,6 +331,16 @@ function addThinkingLine(state, header, detail) {
|
|
|
548
331
|
function appendThinkingText(state, text) {
|
|
549
332
|
state.formattedThinkingText += text;
|
|
550
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
|
+
}
|
|
551
344
|
function completeLastInProgressStep(steps) {
|
|
552
345
|
for (let i = steps.length - 1; i >= 0; i--) {
|
|
553
346
|
if (steps[i].status === "in_progress") {
|
|
@@ -575,11 +368,31 @@ function createInitialV2State() {
|
|
|
575
368
|
currentExecutingStepId: void 0
|
|
576
369
|
};
|
|
577
370
|
}
|
|
578
|
-
function processStreamEventV2(
|
|
371
|
+
function processStreamEventV2(rawEvent, state) {
|
|
372
|
+
const event = normalizeEvent(rawEvent);
|
|
579
373
|
const eventType = event.eventType;
|
|
580
374
|
if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
581
375
|
if (event.executionId) state.executionId = event.executionId;
|
|
582
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";
|
|
583
396
|
return state;
|
|
584
397
|
}
|
|
585
398
|
if (event.executionId) state.executionId = event.executionId;
|
|
@@ -590,6 +403,16 @@ function processStreamEventV2(event, state) {
|
|
|
590
403
|
case "STARTED":
|
|
591
404
|
state.lastEventType = eventType;
|
|
592
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
|
+
}
|
|
593
416
|
case "INTENT_THINKING": {
|
|
594
417
|
const worker = getEventText(event, "workerName") || "Worker";
|
|
595
418
|
const msg = getEventText(event, "message") || "Thinking...";
|
|
@@ -691,6 +514,7 @@ function processStreamEventV2(event, state) {
|
|
|
691
514
|
elapsedMs: event.elapsedMs
|
|
692
515
|
});
|
|
693
516
|
state.currentExecutingStepId = stepId;
|
|
517
|
+
updateExecutionStageMessage(state, message);
|
|
694
518
|
state.lastEventType = eventType;
|
|
695
519
|
break;
|
|
696
520
|
}
|
|
@@ -733,6 +557,10 @@ function processStreamEventV2(event, state) {
|
|
|
733
557
|
}
|
|
734
558
|
case "WORKFLOW_COMPLETED":
|
|
735
559
|
case "COMPLETED": {
|
|
560
|
+
const totalTime = Number(event.totalTimeMs);
|
|
561
|
+
if (Number.isFinite(totalTime) && totalTime > 0) {
|
|
562
|
+
state.totalElapsedMs = totalTime;
|
|
563
|
+
}
|
|
736
564
|
let content = extractResponseContent(event.response);
|
|
737
565
|
const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
|
|
738
566
|
if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
|
|
@@ -745,7 +573,9 @@ function processStreamEventV2(event, state) {
|
|
|
745
573
|
}
|
|
746
574
|
if (content) {
|
|
747
575
|
state.finalResponse = content;
|
|
748
|
-
|
|
576
|
+
if (event.trace && typeof event.trace === "object") {
|
|
577
|
+
state.finalData = event.trace;
|
|
578
|
+
}
|
|
749
579
|
state.hasError = false;
|
|
750
580
|
state.errorMessage = "";
|
|
751
581
|
} else {
|
|
@@ -927,12 +757,373 @@ function processStreamEventV2(event, state) {
|
|
|
927
757
|
state.lastEventType = eventType;
|
|
928
758
|
break;
|
|
929
759
|
}
|
|
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
|
+
}
|
|
930
873
|
default:
|
|
931
874
|
state.lastEventType = eventType;
|
|
932
875
|
break;
|
|
933
876
|
}
|
|
934
|
-
return state;
|
|
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}`;
|
|
1002
|
+
}
|
|
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");
|
|
935
1041
|
}
|
|
1042
|
+
async function resendUserAction(config, userActionId) {
|
|
1043
|
+
return sendUserActionRequest(config, userActionId, "resend");
|
|
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
|
+
};
|
|
936
1127
|
|
|
937
1128
|
// src/utils/ragImageResolver.ts
|
|
938
1129
|
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
@@ -993,12 +1184,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
993
1184
|
const callbacksRef = react.useRef(callbacks);
|
|
994
1185
|
callbacksRef.current = callbacks;
|
|
995
1186
|
const startStream = react.useCallback(
|
|
996
|
-
async (userMessage, streamingId, sessionId, externalAbortController) => {
|
|
1187
|
+
async (userMessage, streamingId, sessionId, externalAbortController, options) => {
|
|
997
1188
|
abortControllerRef.current?.abort();
|
|
998
1189
|
const abortController = externalAbortController ?? new AbortController();
|
|
999
1190
|
abortControllerRef.current = abortController;
|
|
1000
1191
|
const state = createInitialV2State();
|
|
1001
|
-
const streamStartedAt = Date.now();
|
|
1002
1192
|
const updateMessage = (update) => {
|
|
1003
1193
|
if (abortController.signal.aborted) return;
|
|
1004
1194
|
setMessages(
|
|
@@ -1008,7 +1198,12 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1008
1198
|
);
|
|
1009
1199
|
};
|
|
1010
1200
|
const currentConfig = configRef.current;
|
|
1011
|
-
const requestBody = buildRequestBody(
|
|
1201
|
+
const requestBody = buildRequestBody(
|
|
1202
|
+
currentConfig,
|
|
1203
|
+
userMessage,
|
|
1204
|
+
sessionId,
|
|
1205
|
+
options
|
|
1206
|
+
);
|
|
1012
1207
|
const url = buildStreamingUrl(currentConfig);
|
|
1013
1208
|
const headers = buildRequestHeaders(currentConfig);
|
|
1014
1209
|
try {
|
|
@@ -1016,16 +1211,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1016
1211
|
signal: abortController.signal,
|
|
1017
1212
|
onEvent: (event) => {
|
|
1018
1213
|
if (abortController.signal.aborted) return;
|
|
1019
|
-
if (typeof event.eventType === "string" && event.eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
1020
|
-
if (event.executionId) state.executionId = event.executionId;
|
|
1021
|
-
if (event.sessionId) state.sessionId = event.sessionId;
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
1214
|
processStreamEventV2(event, state);
|
|
1025
1215
|
const eventType = event.eventType;
|
|
1026
1216
|
if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
|
|
1027
1217
|
callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
|
|
1028
|
-
} else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
|
|
1218
|
+
} else if (typeof eventType === "string" && eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
|
|
1029
1219
|
const msg = getUserActionDisplayMessage(
|
|
1030
1220
|
eventType,
|
|
1031
1221
|
event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event)
|
|
@@ -1034,7 +1224,18 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1034
1224
|
}
|
|
1035
1225
|
const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
|
|
1036
1226
|
const lastInProgressStep = [...state.steps].reverse().find((s) => s.status === "in_progress");
|
|
1037
|
-
const
|
|
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]);
|
|
1038
1239
|
if (state.hasError) {
|
|
1039
1240
|
updateMessage({
|
|
1040
1241
|
streamingContent: FRIENDLY_ERROR_MESSAGE,
|
|
@@ -1050,7 +1251,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1050
1251
|
});
|
|
1051
1252
|
} else {
|
|
1052
1253
|
updateMessage({
|
|
1053
|
-
streamingContent:
|
|
1254
|
+
streamingContent: state.finalResponse,
|
|
1054
1255
|
content: "",
|
|
1055
1256
|
currentMessage,
|
|
1056
1257
|
streamProgress: "processing",
|
|
@@ -1067,6 +1268,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1067
1268
|
},
|
|
1068
1269
|
onError: (error) => {
|
|
1069
1270
|
setIsWaitingForResponse(false);
|
|
1271
|
+
callbacksRef.current.onStatusMessage?.(null);
|
|
1070
1272
|
if (error.name !== "AbortError") {
|
|
1071
1273
|
callbacksRef.current.onError?.(error);
|
|
1072
1274
|
}
|
|
@@ -1104,6 +1306,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1104
1306
|
},
|
|
1105
1307
|
onComplete: () => {
|
|
1106
1308
|
setIsWaitingForResponse(false);
|
|
1309
|
+
callbacksRef.current.onStatusMessage?.(null);
|
|
1310
|
+
callbacksRef.current.onStepsUpdate?.([]);
|
|
1107
1311
|
if (state.userActionPending) {
|
|
1108
1312
|
state.userActionPending = false;
|
|
1109
1313
|
state.userActionRequest = void 0;
|
|
@@ -1127,14 +1331,18 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1127
1331
|
isError: state.hasError,
|
|
1128
1332
|
errorDetails: state.hasError ? state.errorMessage : void 0,
|
|
1129
1333
|
executionId: state.executionId,
|
|
1130
|
-
|
|
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,
|
|
1131
1339
|
steps: state.hasError ? [] : [...state.steps],
|
|
1132
1340
|
isCancelled: false,
|
|
1133
1341
|
currentExecutingStepId: void 0,
|
|
1134
1342
|
userActionResult: state.userActionResult,
|
|
1135
1343
|
formattedThinkingText: state.hasError ? void 0 : state.formattedThinkingText || void 0,
|
|
1136
1344
|
isResolvingImages: needsImageResolve,
|
|
1137
|
-
|
|
1345
|
+
totalElapsedMs: state.hasError ? void 0 : state.totalElapsedMs
|
|
1138
1346
|
};
|
|
1139
1347
|
setMessages(
|
|
1140
1348
|
(prev) => prev.map(
|
|
@@ -1219,199 +1427,45 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
1219
1427
|
};
|
|
1220
1428
|
}
|
|
1221
1429
|
|
|
1222
|
-
// src/utils/workflowUsersClient.ts
|
|
1223
|
-
var DEFAULT_SESSION_PAGE_SIZE = 20;
|
|
1224
|
-
var DEFAULT_CONVERSATION_PAGE_SIZE = 50;
|
|
1225
|
-
function getStageParamName2(config) {
|
|
1226
|
-
return config.api.stageQueryParam ?? "stage";
|
|
1227
|
-
}
|
|
1228
|
-
function requireOwnerId(config) {
|
|
1229
|
-
const ownerId = config.session?.owner?.id;
|
|
1230
|
-
if (!ownerId) {
|
|
1231
|
-
throw new Error(
|
|
1232
|
-
"workflowUsersClient: session.owner.id is required to call this endpoint."
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
return ownerId;
|
|
1236
|
-
}
|
|
1237
|
-
function requireWorkflowName(config) {
|
|
1238
|
-
const workflowName = config.workflow.name;
|
|
1239
|
-
if (!workflowName) {
|
|
1240
|
-
throw new Error(
|
|
1241
|
-
"workflowUsersClient: workflow.name is required to call this endpoint."
|
|
1242
|
-
);
|
|
1243
|
-
}
|
|
1244
|
-
return workflowName;
|
|
1245
|
-
}
|
|
1246
|
-
function requireWorkflowId(config) {
|
|
1247
|
-
const workflowId = config.workflow.id;
|
|
1248
|
-
if (!workflowId) {
|
|
1249
|
-
throw new Error(
|
|
1250
|
-
"workflowUsersClient: workflow.id is required to call this endpoint."
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
return workflowId;
|
|
1254
|
-
}
|
|
1255
|
-
function isPlaygroundYaakAuth(config) {
|
|
1256
|
-
if (config.api.authToken) return false;
|
|
1257
|
-
const headers = config.api.headers;
|
|
1258
|
-
if (!headers) return false;
|
|
1259
|
-
for (const key of Object.keys(headers)) {
|
|
1260
|
-
const lower = key.toLowerCase();
|
|
1261
|
-
if (lower === "yaak-api-key" || lower === "x-yaak-api-key") return true;
|
|
1262
|
-
}
|
|
1263
|
-
return false;
|
|
1264
|
-
}
|
|
1265
|
-
async function fetchJson(url, headers, signal) {
|
|
1266
|
-
const response = await fetch(url, {
|
|
1267
|
-
method: "GET",
|
|
1268
|
-
headers,
|
|
1269
|
-
signal
|
|
1270
|
-
});
|
|
1271
|
-
if (!response.ok) {
|
|
1272
|
-
const errorText = await response.text();
|
|
1273
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1274
|
-
}
|
|
1275
|
-
return await response.json();
|
|
1276
|
-
}
|
|
1277
|
-
async function listSessions(config, opts = {}) {
|
|
1278
|
-
const ownerId = requireOwnerId(config);
|
|
1279
|
-
const useYaakEndpoint = isPlaygroundYaakAuth(config);
|
|
1280
|
-
const params = new URLSearchParams({
|
|
1281
|
-
page: String(opts.page ?? 0),
|
|
1282
|
-
size: String(opts.size ?? DEFAULT_SESSION_PAGE_SIZE)
|
|
1283
|
-
});
|
|
1284
|
-
if (useYaakEndpoint) {
|
|
1285
|
-
params.set("workflowUserId", ownerId);
|
|
1286
|
-
params.set("workflowName", requireWorkflowName(config));
|
|
1287
|
-
} else {
|
|
1288
|
-
params.set("workflowId", requireWorkflowId(config));
|
|
1289
|
-
}
|
|
1290
|
-
if (config.workflow.stage) {
|
|
1291
|
-
params.set(getStageParamName2(config), config.workflow.stage);
|
|
1292
|
-
}
|
|
1293
|
-
const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/sessions?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
|
|
1294
|
-
ownerId
|
|
1295
|
-
)}/sessions?${params.toString()}`;
|
|
1296
|
-
return fetchJson(
|
|
1297
|
-
url,
|
|
1298
|
-
buildRequestHeaders(config),
|
|
1299
|
-
opts.signal
|
|
1300
|
-
);
|
|
1301
|
-
}
|
|
1302
|
-
async function listConversations(config, opts) {
|
|
1303
|
-
const ownerId = requireOwnerId(config);
|
|
1304
|
-
const useYaakEndpoint = isPlaygroundYaakAuth(config);
|
|
1305
|
-
const params = new URLSearchParams({
|
|
1306
|
-
sessionId: opts.sessionId,
|
|
1307
|
-
page: String(opts.page ?? 0),
|
|
1308
|
-
size: String(opts.size ?? DEFAULT_CONVERSATION_PAGE_SIZE)
|
|
1309
|
-
});
|
|
1310
|
-
if (useYaakEndpoint) {
|
|
1311
|
-
params.set("workflowUserId", ownerId);
|
|
1312
|
-
params.set("workflowName", requireWorkflowName(config));
|
|
1313
|
-
}
|
|
1314
|
-
if (config.workflow.stage) {
|
|
1315
|
-
params.set(getStageParamName2(config), config.workflow.stage);
|
|
1316
|
-
}
|
|
1317
|
-
const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/conversations?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
|
|
1318
|
-
ownerId
|
|
1319
|
-
)}/conversations?${params.toString()}`;
|
|
1320
|
-
return fetchJson(
|
|
1321
|
-
url,
|
|
1322
|
-
buildRequestHeaders(config),
|
|
1323
|
-
opts.signal
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
1430
|
// src/hooks/useChatV2.ts
|
|
1328
|
-
function conversationEntryToMessages(entry, sessionId) {
|
|
1329
|
-
return [
|
|
1330
|
-
{
|
|
1331
|
-
id: `history-user-${entry.executionId}`,
|
|
1332
|
-
sessionId,
|
|
1333
|
-
role: "user",
|
|
1334
|
-
content: entry.query,
|
|
1335
|
-
timestamp: entry.createdAt,
|
|
1336
|
-
isHistorical: true
|
|
1337
|
-
},
|
|
1338
|
-
{
|
|
1339
|
-
id: `history-assistant-${entry.executionId}`,
|
|
1340
|
-
sessionId,
|
|
1341
|
-
role: "assistant",
|
|
1342
|
-
content: entry.response,
|
|
1343
|
-
timestamp: entry.createdAt,
|
|
1344
|
-
executionId: entry.executionId,
|
|
1345
|
-
isHistorical: true
|
|
1346
|
-
}
|
|
1347
|
-
];
|
|
1348
|
-
}
|
|
1349
|
-
function streamKeyFor(scopeKey, sessionId) {
|
|
1350
|
-
return `${scopeKey}|sid:${sessionId ?? ""}`;
|
|
1351
|
-
}
|
|
1352
1431
|
function useChatV2(config, callbacks = {}) {
|
|
1353
|
-
const scopeKey = react.useMemo(() => buildScopeKey(config), [
|
|
1354
|
-
config.session?.userId,
|
|
1355
|
-
config.workflow?.id,
|
|
1356
|
-
config.workflow?.version,
|
|
1357
|
-
config.workflow?.stage
|
|
1358
|
-
]);
|
|
1359
|
-
const initialSessionId = chatStore.get(scopeKey).find((m) => m.sessionId)?.sessionId ?? config.session?.initialId ?? void 0;
|
|
1360
1432
|
const [messages, setMessages] = react.useState(() => {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
return config.session?.initialMessages ?? [];
|
|
1433
|
+
if (config.userId) return chatStore.get(config.userId);
|
|
1434
|
+
return config.initialMessages ?? [];
|
|
1364
1435
|
});
|
|
1365
1436
|
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
const
|
|
1370
|
-
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);
|
|
1371
1441
|
const callbacksRef = react.useRef(callbacks);
|
|
1372
1442
|
callbacksRef.current = callbacks;
|
|
1373
1443
|
const configRef = react.useRef(config);
|
|
1374
1444
|
configRef.current = config;
|
|
1375
|
-
const scopeKeyRef = react.useRef(scopeKey);
|
|
1376
|
-
scopeKeyRef.current = scopeKey;
|
|
1377
1445
|
const messagesRef = react.useRef(messages);
|
|
1378
1446
|
messagesRef.current = messages;
|
|
1379
1447
|
const storeAwareSetMessages = react.useCallback(
|
|
1380
1448
|
(updater) => {
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
const streamKey = streamKeyFor(scope, streamSid);
|
|
1385
|
-
if (activeStreamStore.has(streamKey)) {
|
|
1386
|
-
activeStreamStore.applyMessages(
|
|
1387
|
-
streamKey,
|
|
1388
|
-
updater
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
|
-
if (sessionIdRef.current === streamSid) {
|
|
1392
|
-
setMessages(updater);
|
|
1393
|
-
}
|
|
1394
|
-
return;
|
|
1449
|
+
const { userId } = configRef.current;
|
|
1450
|
+
if (userId && activeStreamStore.has(userId)) {
|
|
1451
|
+
activeStreamStore.applyMessages(userId, updater);
|
|
1395
1452
|
}
|
|
1396
1453
|
setMessages(updater);
|
|
1397
1454
|
},
|
|
1455
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1398
1456
|
[]
|
|
1399
1457
|
);
|
|
1400
|
-
const storeAwareSetIsWaiting = react.useCallback(
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
if (activeStreamStore.has(streamKey)) {
|
|
1406
|
-
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);
|
|
1407
1463
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
setIsWaitingForResponse(waiting);
|
|
1414
|
-
}, []);
|
|
1464
|
+
setIsWaitingForResponse(waiting);
|
|
1465
|
+
},
|
|
1466
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1467
|
+
[]
|
|
1468
|
+
);
|
|
1415
1469
|
const [userActionState, setUserActionState] = react.useState({
|
|
1416
1470
|
request: null,
|
|
1417
1471
|
result: null,
|
|
@@ -1419,43 +1473,38 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1419
1473
|
});
|
|
1420
1474
|
const userActionStateRef = react.useRef(userActionState);
|
|
1421
1475
|
userActionStateRef.current = userActionState;
|
|
1422
|
-
const wrappedCallbacks = react.useMemo(
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
...prev,
|
|
1450
|
-
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
1451
|
-
}));
|
|
1452
|
-
break;
|
|
1453
|
-
}
|
|
1454
|
-
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;
|
|
1455
1503
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1504
|
+
callbacksRef.current.onUserActionEvent?.(eventType, message);
|
|
1505
|
+
}
|
|
1506
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1507
|
+
}), []);
|
|
1459
1508
|
const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
|
|
1460
1509
|
config,
|
|
1461
1510
|
wrappedCallbacks,
|
|
@@ -1463,12 +1512,10 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1463
1512
|
storeAwareSetIsWaiting
|
|
1464
1513
|
);
|
|
1465
1514
|
const sendMessage = react.useCallback(
|
|
1466
|
-
async (userMessage) => {
|
|
1515
|
+
async (userMessage, options) => {
|
|
1467
1516
|
if (!userMessage.trim()) return;
|
|
1468
|
-
|
|
1469
|
-
if (!sessionIdRef.current && autoGen !== false) {
|
|
1517
|
+
if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
|
|
1470
1518
|
sessionIdRef.current = generateId();
|
|
1471
|
-
setCurrentSessionId(sessionIdRef.current);
|
|
1472
1519
|
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
1473
1520
|
}
|
|
1474
1521
|
const userMessageId = `user-${Date.now()}`;
|
|
@@ -1500,40 +1547,39 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1500
1547
|
};
|
|
1501
1548
|
setMessages((prev) => [...prev, streamingMsg]);
|
|
1502
1549
|
const abortController = new AbortController();
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
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
|
+
}
|
|
1509
1555
|
const newSessionId = await startStream(
|
|
1510
1556
|
userMessage,
|
|
1511
1557
|
streamingId,
|
|
1512
|
-
|
|
1513
|
-
abortController
|
|
1558
|
+
sessionIdRef.current,
|
|
1559
|
+
abortController,
|
|
1560
|
+
options
|
|
1514
1561
|
);
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1562
|
+
if (userId) {
|
|
1563
|
+
activeStreamStore.complete(userId);
|
|
1564
|
+
}
|
|
1565
|
+
if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
|
|
1518
1566
|
sessionIdRef.current = newSessionId;
|
|
1519
|
-
setCurrentSessionId(newSessionId);
|
|
1520
1567
|
}
|
|
1521
1568
|
},
|
|
1522
1569
|
[startStream]
|
|
1523
1570
|
);
|
|
1524
1571
|
const clearMessages = react.useCallback(() => {
|
|
1525
|
-
|
|
1572
|
+
if (configRef.current.userId) {
|
|
1573
|
+
chatStore.delete(configRef.current.userId);
|
|
1574
|
+
}
|
|
1526
1575
|
setMessages([]);
|
|
1527
1576
|
}, []);
|
|
1528
1577
|
const prependMessages = react.useCallback((msgs) => {
|
|
1529
1578
|
setMessages((prev) => [...msgs, ...prev]);
|
|
1530
1579
|
}, []);
|
|
1531
1580
|
const cancelStream = react.useCallback(() => {
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
const viewStreamKey = streamKeyFor(scope, viewSid);
|
|
1535
|
-
if (activeStreamStore.has(viewStreamKey)) {
|
|
1536
|
-
activeStreamStore.abort(viewStreamKey);
|
|
1581
|
+
if (configRef.current.userId) {
|
|
1582
|
+
activeStreamStore.abort(configRef.current.userId);
|
|
1537
1583
|
}
|
|
1538
1584
|
cancelStreamManager();
|
|
1539
1585
|
setIsWaitingForResponse(false);
|
|
@@ -1543,7 +1589,10 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1543
1589
|
if (msg.isStreaming) {
|
|
1544
1590
|
return {
|
|
1545
1591
|
...msg,
|
|
1546
|
-
...createCancelledMessageUpdate(
|
|
1592
|
+
...createCancelledMessageUpdate(
|
|
1593
|
+
msg.steps || [],
|
|
1594
|
+
msg.currentMessage
|
|
1595
|
+
)
|
|
1547
1596
|
};
|
|
1548
1597
|
}
|
|
1549
1598
|
return msg;
|
|
@@ -1551,37 +1600,39 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1551
1600
|
);
|
|
1552
1601
|
}, [cancelStreamManager]);
|
|
1553
1602
|
const resetSession = react.useCallback(() => {
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
if (activeStreamStore.has(viewStreamKey)) {
|
|
1558
|
-
activeStreamStore.abort(viewStreamKey);
|
|
1603
|
+
if (configRef.current.userId) {
|
|
1604
|
+
activeStreamStore.abort(configRef.current.userId);
|
|
1605
|
+
chatStore.delete(configRef.current.userId);
|
|
1559
1606
|
}
|
|
1560
|
-
chatStore.delete(scope);
|
|
1561
1607
|
setMessages([]);
|
|
1562
1608
|
sessionIdRef.current = void 0;
|
|
1563
|
-
setCurrentSessionId(void 0);
|
|
1564
|
-
activeStreamSessionRef.current = void 0;
|
|
1565
1609
|
abortControllerRef.current?.abort();
|
|
1566
1610
|
setIsWaitingForResponse(false);
|
|
1567
1611
|
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1568
1612
|
}, []);
|
|
1569
|
-
const getSessionId = react.useCallback(() =>
|
|
1570
|
-
|
|
1571
|
-
const approveUserAction = react.useCallback(async (otp) => {
|
|
1572
|
-
const request = userActionStateRef.current.request;
|
|
1573
|
-
if (!request) return;
|
|
1574
|
-
try {
|
|
1575
|
-
await submitUserAction(configRef.current, request.userActionId, { otp });
|
|
1576
|
-
} catch (error) {
|
|
1577
|
-
setUserActionState((prev) => ({
|
|
1578
|
-
...prev,
|
|
1579
|
-
clearOtpTrigger: prev.clearOtpTrigger + 1
|
|
1580
|
-
}));
|
|
1581
|
-
callbacksRef.current.onError?.(error);
|
|
1582
|
-
throw error;
|
|
1583
|
-
}
|
|
1613
|
+
const getSessionId = react.useCallback(() => {
|
|
1614
|
+
return sessionIdRef.current;
|
|
1584
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
|
+
);
|
|
1585
1636
|
const rejectUserAction = react.useCallback(async () => {
|
|
1586
1637
|
const request = userActionStateRef.current.request;
|
|
1587
1638
|
if (!request) return;
|
|
@@ -1615,91 +1666,43 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1615
1666
|
throw error;
|
|
1616
1667
|
}
|
|
1617
1668
|
}, []);
|
|
1618
|
-
const inFlightLoadRef = react.useRef(null);
|
|
1619
|
-
const loadingSessionIdRef = react.useRef(void 0);
|
|
1620
|
-
loadingSessionIdRef.current = loadingSessionId;
|
|
1621
|
-
const loadSession = react.useCallback(async (sessionId) => {
|
|
1622
|
-
const inFlight = inFlightLoadRef.current;
|
|
1623
|
-
if (inFlight && inFlight.sessionId === sessionId) {
|
|
1624
|
-
return inFlight.promise;
|
|
1625
|
-
}
|
|
1626
|
-
if (sessionIdRef.current === sessionId && loadingSessionIdRef.current !== sessionId) {
|
|
1627
|
-
return;
|
|
1628
|
-
}
|
|
1629
|
-
const run = async () => {
|
|
1630
|
-
const scope = scopeKeyRef.current;
|
|
1631
|
-
setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
|
|
1632
|
-
sessionIdRef.current = sessionId;
|
|
1633
|
-
setCurrentSessionId(sessionId);
|
|
1634
|
-
callbacksRef.current.onSessionIdChange?.(sessionId);
|
|
1635
|
-
const streamKey = streamKeyFor(scope, sessionId);
|
|
1636
|
-
const active = activeStreamStore.get(streamKey);
|
|
1637
|
-
if (active) {
|
|
1638
|
-
setMessages(active.messages);
|
|
1639
|
-
setIsWaitingForResponse(active.isWaiting);
|
|
1640
|
-
setLoadingSessionId(void 0);
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
setIsWaitingForResponse(false);
|
|
1644
|
-
setMessages([]);
|
|
1645
|
-
chatStore.delete(scope);
|
|
1646
|
-
setLoadingSessionId(sessionId);
|
|
1647
|
-
try {
|
|
1648
|
-
const response = await listConversations(configRef.current, { sessionId });
|
|
1649
|
-
const entries = response.data ?? [];
|
|
1650
|
-
const historical = entries.flatMap((entry) => conversationEntryToMessages(entry, sessionId));
|
|
1651
|
-
if (sessionIdRef.current === sessionId) {
|
|
1652
|
-
setMessages(historical);
|
|
1653
|
-
}
|
|
1654
|
-
if (historical.length > 0) {
|
|
1655
|
-
chatStore.set(scope, historical);
|
|
1656
|
-
}
|
|
1657
|
-
} catch (error) {
|
|
1658
|
-
callbacksRef.current.onError?.(error);
|
|
1659
|
-
throw error;
|
|
1660
|
-
} finally {
|
|
1661
|
-
setLoadingSessionId((current) => current === sessionId ? void 0 : current);
|
|
1662
|
-
}
|
|
1663
|
-
};
|
|
1664
|
-
const promise = run().finally(() => {
|
|
1665
|
-
if (inFlightLoadRef.current?.sessionId === sessionId) {
|
|
1666
|
-
inFlightLoadRef.current = null;
|
|
1667
|
-
}
|
|
1668
|
-
});
|
|
1669
|
-
inFlightLoadRef.current = { sessionId, promise };
|
|
1670
|
-
return promise;
|
|
1671
|
-
}, []);
|
|
1672
1669
|
react.useEffect(() => {
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1670
|
+
const { userId } = config;
|
|
1671
|
+
if (!userId) return;
|
|
1672
|
+
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1675
1673
|
setMessages(msgs);
|
|
1676
1674
|
setIsWaitingForResponse(isWaiting);
|
|
1677
1675
|
});
|
|
1678
|
-
const active = activeStreamStore.get(
|
|
1676
|
+
const active = activeStreamStore.get(userId);
|
|
1679
1677
|
if (active) {
|
|
1680
1678
|
setMessages(active.messages);
|
|
1681
1679
|
setIsWaitingForResponse(active.isWaiting);
|
|
1682
1680
|
}
|
|
1683
1681
|
return unsubscribe;
|
|
1684
|
-
}, [
|
|
1682
|
+
}, []);
|
|
1685
1683
|
react.useEffect(() => {
|
|
1684
|
+
if (!config.userId) return;
|
|
1686
1685
|
const toSave = messages.filter((m) => !m.isStreaming);
|
|
1687
1686
|
if (toSave.length > 0) {
|
|
1688
|
-
chatStore.set(
|
|
1687
|
+
chatStore.set(config.userId, toSave);
|
|
1689
1688
|
}
|
|
1690
|
-
}, [messages,
|
|
1689
|
+
}, [messages, config.userId]);
|
|
1691
1690
|
react.useEffect(() => {
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
if (
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
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]);
|
|
1703
1706
|
return {
|
|
1704
1707
|
messages,
|
|
1705
1708
|
sendMessage,
|
|
@@ -1710,13 +1713,11 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1710
1713
|
getSessionId,
|
|
1711
1714
|
getMessages,
|
|
1712
1715
|
isWaitingForResponse,
|
|
1713
|
-
sessionId:
|
|
1716
|
+
sessionId: sessionIdRef.current,
|
|
1714
1717
|
userActionState,
|
|
1715
1718
|
approveUserAction,
|
|
1716
1719
|
rejectUserAction,
|
|
1717
|
-
resendOtp
|
|
1718
|
-
loadSession,
|
|
1719
|
-
loadingSessionId
|
|
1720
|
+
resendOtp
|
|
1720
1721
|
};
|
|
1721
1722
|
}
|
|
1722
1723
|
function useVoice() {
|
|
@@ -1836,18 +1837,14 @@ function useVoice() {
|
|
|
1836
1837
|
}
|
|
1837
1838
|
|
|
1838
1839
|
exports.buildFormattedThinking = buildFormattedThinking;
|
|
1839
|
-
exports.buildScopeKey = buildScopeKey;
|
|
1840
1840
|
exports.cancelUserAction = cancelUserAction;
|
|
1841
1841
|
exports.createInitialV2State = createInitialV2State;
|
|
1842
1842
|
exports.generateId = generateId;
|
|
1843
|
-
exports.listConversations = listConversations;
|
|
1844
|
-
exports.listSessions = listSessions;
|
|
1845
1843
|
exports.processStreamEventV2 = processStreamEventV2;
|
|
1846
1844
|
exports.resendUserAction = resendUserAction;
|
|
1847
1845
|
exports.streamWorkflowEvents = streamWorkflowEvents;
|
|
1848
1846
|
exports.submitUserAction = submitUserAction;
|
|
1849
1847
|
exports.useChatV2 = useChatV2;
|
|
1850
1848
|
exports.useVoice = useVoice;
|
|
1851
|
-
exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;
|
|
1852
1849
|
//# sourceMappingURL=index.native.js.map
|
|
1853
1850
|
//# sourceMappingURL=index.native.js.map
|