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