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