@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.
- package/dist/index.d.mts +190 -148
- package/dist/index.d.ts +190 -148
- package/dist/index.js +697 -691
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +699 -689
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +692 -697
- 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,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(
|
|
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
|
-
|
|
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
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1364
|
-
|
|
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
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
const
|
|
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
|
|
1384
|
-
|
|
1385
|
-
|
|
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(
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
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
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
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
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
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
|
-
|
|
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
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
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
|
-
|
|
1515
|
-
abortController
|
|
1558
|
+
sessionIdRef.current,
|
|
1559
|
+
abortController,
|
|
1560
|
+
options
|
|
1516
1561
|
);
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1535
|
-
|
|
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(
|
|
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
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
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(() =>
|
|
1572
|
-
|
|
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
|
|
1676
|
-
|
|
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(
|
|
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
|
-
}, [
|
|
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(
|
|
1687
|
+
chatStore.set(config.userId, toSave);
|
|
1691
1688
|
}
|
|
1692
|
-
}, [messages,
|
|
1689
|
+
}, [messages, config.userId]);
|
|
1693
1690
|
react.useEffect(() => {
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1696
|
-
if (
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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:
|
|
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
|