@dudousxd/nestjs-agent-react 0.3.2 → 0.4.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.cjs +279 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -8
- package/dist/index.d.ts +80 -8
- package/dist/index.js +285 -44
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,7 @@ __export(src_exports, {
|
|
|
42
42
|
formatRelativeTime: () => formatRelativeTime,
|
|
43
43
|
loadStoredSpeechLang: () => loadStoredSpeechLang,
|
|
44
44
|
persistSpeechLang: () => persistSpeechLang,
|
|
45
|
+
storedMessageToUiMessage: () => storedMessageToUiMessage,
|
|
45
46
|
useAgentChat: () => useAgentChat,
|
|
46
47
|
useSpeechRecognition: () => useSpeechRecognition
|
|
47
48
|
});
|
|
@@ -147,18 +148,26 @@ var AgentChatTransport = class {
|
|
|
147
148
|
this.options.onMeta?.(meta);
|
|
148
149
|
}
|
|
149
150
|
/**
|
|
150
|
-
* Parse the backend's SSE byte stream and
|
|
151
|
-
*
|
|
152
|
-
* - `event: meta` `data: {"runId","threadId"}`
|
|
153
|
-
* - `data:
|
|
154
|
-
* - `event: done` `data: {}`
|
|
151
|
+
* Parse the backend's SSE byte stream and re-emit it as a valid v7 UI-message chunk stream.
|
|
152
|
+
* Recognized frames:
|
|
153
|
+
* - `event: meta` `data: {"runId","threadId"}` → records identity
|
|
154
|
+
* - `data: <AgentStreamEvent JSON>` → mapped to UI chunks (text / reasoning / tool)
|
|
155
|
+
* - `event: done` `data: {}` → terminates
|
|
156
|
+
* - `event: error` `data: {code,message}` → error chunk
|
|
157
|
+
*
|
|
158
|
+
* A run is ONE UI message with N steps. Each `step-start`/`step-finish` pair brackets a model
|
|
159
|
+
* call plus its tool execution; text and reasoning open lazily and close at the step boundary, so
|
|
160
|
+
* tool-call cards (input streaming → output) render live between the prose.
|
|
155
161
|
*/
|
|
156
162
|
toChunkStream(source) {
|
|
157
163
|
const reader = source.getReader();
|
|
158
164
|
const decoder = new TextDecoder();
|
|
159
|
-
const textId = `txt-${Math.random().toString(36).slice(2)}`;
|
|
160
165
|
let buffer = "";
|
|
161
166
|
let started = false;
|
|
167
|
+
let stepOpen = false;
|
|
168
|
+
let stepIndex = 0;
|
|
169
|
+
let textId = null;
|
|
170
|
+
let reasoningId = null;
|
|
162
171
|
const record = /* @__PURE__ */ __name((meta) => this.recordMeta(meta), "record");
|
|
163
172
|
return new ReadableStream({
|
|
164
173
|
async pull(controller) {
|
|
@@ -168,24 +177,154 @@ var AgentChatTransport = class {
|
|
|
168
177
|
controller.enqueue({
|
|
169
178
|
type: "start"
|
|
170
179
|
});
|
|
171
|
-
controller.enqueue({
|
|
172
|
-
type: "start-step"
|
|
173
|
-
});
|
|
174
|
-
controller.enqueue({
|
|
175
|
-
type: "text-start",
|
|
176
|
-
id: textId
|
|
177
|
-
});
|
|
178
180
|
}
|
|
179
181
|
__name(ensureStarted, "ensureStarted");
|
|
180
|
-
function
|
|
182
|
+
function openStep() {
|
|
181
183
|
ensureStarted();
|
|
184
|
+
if (stepOpen) closeStep();
|
|
185
|
+
stepOpen = true;
|
|
186
|
+
stepIndex += 1;
|
|
187
|
+
textId = null;
|
|
188
|
+
reasoningId = null;
|
|
182
189
|
controller.enqueue({
|
|
183
|
-
type: "
|
|
184
|
-
id: textId
|
|
190
|
+
type: "start-step"
|
|
185
191
|
});
|
|
192
|
+
}
|
|
193
|
+
__name(openStep, "openStep");
|
|
194
|
+
function ensureStep() {
|
|
195
|
+
if (!stepOpen) openStep();
|
|
196
|
+
}
|
|
197
|
+
__name(ensureStep, "ensureStep");
|
|
198
|
+
function closeStep() {
|
|
199
|
+
if (!stepOpen) return;
|
|
200
|
+
if (textId !== null) {
|
|
201
|
+
controller.enqueue({
|
|
202
|
+
type: "text-end",
|
|
203
|
+
id: textId
|
|
204
|
+
});
|
|
205
|
+
textId = null;
|
|
206
|
+
}
|
|
207
|
+
if (reasoningId !== null) {
|
|
208
|
+
controller.enqueue({
|
|
209
|
+
type: "reasoning-end",
|
|
210
|
+
id: reasoningId
|
|
211
|
+
});
|
|
212
|
+
reasoningId = null;
|
|
213
|
+
}
|
|
186
214
|
controller.enqueue({
|
|
187
215
|
type: "finish-step"
|
|
188
216
|
});
|
|
217
|
+
stepOpen = false;
|
|
218
|
+
}
|
|
219
|
+
__name(closeStep, "closeStep");
|
|
220
|
+
function emit(event) {
|
|
221
|
+
switch (event.kind) {
|
|
222
|
+
case "step-start":
|
|
223
|
+
openStep();
|
|
224
|
+
break;
|
|
225
|
+
case "step-finish":
|
|
226
|
+
closeStep();
|
|
227
|
+
if (event.costUsd !== void 0) {
|
|
228
|
+
controller.enqueue({
|
|
229
|
+
type: "message-metadata",
|
|
230
|
+
messageMetadata: {
|
|
231
|
+
costUsd: event.costUsd
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
case "text":
|
|
237
|
+
ensureStep();
|
|
238
|
+
if (textId === null) {
|
|
239
|
+
textId = `txt-${stepIndex}`;
|
|
240
|
+
controller.enqueue({
|
|
241
|
+
type: "text-start",
|
|
242
|
+
id: textId
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
controller.enqueue({
|
|
246
|
+
type: "text-delta",
|
|
247
|
+
id: textId,
|
|
248
|
+
delta: event.text
|
|
249
|
+
});
|
|
250
|
+
break;
|
|
251
|
+
case "reasoning":
|
|
252
|
+
ensureStep();
|
|
253
|
+
if (reasoningId === null) {
|
|
254
|
+
reasoningId = `rsn-${stepIndex}`;
|
|
255
|
+
controller.enqueue({
|
|
256
|
+
type: "reasoning-start",
|
|
257
|
+
id: reasoningId
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
controller.enqueue({
|
|
261
|
+
type: "reasoning-delta",
|
|
262
|
+
id: reasoningId,
|
|
263
|
+
delta: event.text
|
|
264
|
+
});
|
|
265
|
+
break;
|
|
266
|
+
case "tool-input-start":
|
|
267
|
+
ensureStep();
|
|
268
|
+
controller.enqueue({
|
|
269
|
+
type: "tool-input-start",
|
|
270
|
+
toolCallId: event.id,
|
|
271
|
+
toolName: event.name,
|
|
272
|
+
// `toolKind` is absent on older backends — omitted (not `undefined`-valued) so
|
|
273
|
+
// `toolMetadata` itself is only present when there's something to say, letting a
|
|
274
|
+
// UI gate approval affordances on `kind === 'action'` without hardcoding tool names.
|
|
275
|
+
...event.toolKind !== void 0 ? {
|
|
276
|
+
toolMetadata: {
|
|
277
|
+
toolKind: event.toolKind
|
|
278
|
+
}
|
|
279
|
+
} : {}
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
case "tool-input-delta":
|
|
283
|
+
ensureStep();
|
|
284
|
+
controller.enqueue({
|
|
285
|
+
type: "tool-input-delta",
|
|
286
|
+
toolCallId: event.id,
|
|
287
|
+
inputTextDelta: event.delta
|
|
288
|
+
});
|
|
289
|
+
break;
|
|
290
|
+
case "tool-input-available":
|
|
291
|
+
ensureStep();
|
|
292
|
+
controller.enqueue({
|
|
293
|
+
type: "tool-input-available",
|
|
294
|
+
toolCallId: event.id,
|
|
295
|
+
toolName: event.name,
|
|
296
|
+
input: event.input,
|
|
297
|
+
...event.toolKind !== void 0 ? {
|
|
298
|
+
toolMetadata: {
|
|
299
|
+
toolKind: event.toolKind
|
|
300
|
+
}
|
|
301
|
+
} : {}
|
|
302
|
+
});
|
|
303
|
+
break;
|
|
304
|
+
case "tool-output":
|
|
305
|
+
ensureStep();
|
|
306
|
+
controller.enqueue({
|
|
307
|
+
type: "tool-output-available",
|
|
308
|
+
toolCallId: event.id,
|
|
309
|
+
output: event.output
|
|
310
|
+
});
|
|
311
|
+
break;
|
|
312
|
+
case "tool-output-error":
|
|
313
|
+
ensureStep();
|
|
314
|
+
controller.enqueue({
|
|
315
|
+
type: "tool-output-error",
|
|
316
|
+
toolCallId: event.id,
|
|
317
|
+
errorText: event.error
|
|
318
|
+
});
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
__name(emit, "emit");
|
|
325
|
+
function finish() {
|
|
326
|
+
ensureStarted();
|
|
327
|
+
closeStep();
|
|
189
328
|
controller.enqueue({
|
|
190
329
|
type: "finish"
|
|
191
330
|
});
|
|
@@ -224,15 +363,8 @@ var AgentChatTransport = class {
|
|
|
224
363
|
const meta = parseMeta(frame.data);
|
|
225
364
|
if (meta) record(meta);
|
|
226
365
|
} else if (frame.data) {
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
229
|
-
ensureStarted();
|
|
230
|
-
controller.enqueue({
|
|
231
|
-
type: "text-delta",
|
|
232
|
-
id: textId,
|
|
233
|
-
delta
|
|
234
|
-
});
|
|
235
|
-
}
|
|
366
|
+
const event = parseEvent(frame.data);
|
|
367
|
+
if (event) emit(event);
|
|
236
368
|
}
|
|
237
369
|
separator = buffer.indexOf("\n\n");
|
|
238
370
|
}
|
|
@@ -294,17 +426,17 @@ function parseErrorText(data) {
|
|
|
294
426
|
return "Agent run failed";
|
|
295
427
|
}
|
|
296
428
|
__name(parseErrorText, "parseErrorText");
|
|
297
|
-
function
|
|
429
|
+
function parseEvent(data) {
|
|
298
430
|
try {
|
|
299
431
|
const parsed = JSON.parse(data);
|
|
300
|
-
if (parsed !== null && typeof parsed === "object" && "
|
|
301
|
-
return parsed
|
|
432
|
+
if (parsed !== null && typeof parsed === "object" && "kind" in parsed) {
|
|
433
|
+
return parsed;
|
|
302
434
|
}
|
|
303
435
|
} catch {
|
|
304
436
|
}
|
|
305
437
|
return null;
|
|
306
438
|
}
|
|
307
|
-
__name(
|
|
439
|
+
__name(parseEvent, "parseEvent");
|
|
308
440
|
function extractText(message) {
|
|
309
441
|
let text = "";
|
|
310
442
|
for (const part of message.parts ?? []) {
|
|
@@ -348,10 +480,39 @@ var AgentClient = class {
|
|
|
348
480
|
return this.request("POST", `/agent/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
|
|
349
481
|
}
|
|
350
482
|
renameThread(id, title) {
|
|
351
|
-
return this.
|
|
483
|
+
return this.updateThread(id, {
|
|
352
484
|
title
|
|
353
485
|
});
|
|
354
486
|
}
|
|
487
|
+
/** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
|
|
488
|
+
updateThread(id, patch) {
|
|
489
|
+
return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, patch);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
|
|
493
|
+
* mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
|
|
494
|
+
* what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
|
|
495
|
+
*/
|
|
496
|
+
async uploadAttachment(file) {
|
|
497
|
+
const fetchImpl = this.options.fetch ?? globalThis.fetch;
|
|
498
|
+
const baseUrl = (this.options.baseUrl ?? "").replace(/\/$/, "");
|
|
499
|
+
const dynamic = await this.options.getHeaders?.() ?? {};
|
|
500
|
+
const formData = new FormData();
|
|
501
|
+
formData.append("file", file);
|
|
502
|
+
const response = await fetchImpl(`${baseUrl}/agent/attachments`, {
|
|
503
|
+
method: "POST",
|
|
504
|
+
headers: {
|
|
505
|
+
accept: "application/json",
|
|
506
|
+
...this.options.headers,
|
|
507
|
+
...dynamic
|
|
508
|
+
},
|
|
509
|
+
body: formData,
|
|
510
|
+
...this.options.credentials !== void 0 ? {
|
|
511
|
+
credentials: this.options.credentials
|
|
512
|
+
} : {}
|
|
513
|
+
});
|
|
514
|
+
return this.handleResponse(response, "POST", "/agent/attachments");
|
|
515
|
+
}
|
|
355
516
|
promoteThread(id) {
|
|
356
517
|
return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
|
|
357
518
|
}
|
|
@@ -391,6 +552,9 @@ var AgentClient = class {
|
|
|
391
552
|
credentials: this.options.credentials
|
|
392
553
|
} : {}
|
|
393
554
|
});
|
|
555
|
+
return this.handleResponse(response, method, path);
|
|
556
|
+
}
|
|
557
|
+
async handleResponse(response, method, path) {
|
|
394
558
|
if (!response.ok) {
|
|
395
559
|
throw new AgentHttpError(response.status, method, path, response.statusText);
|
|
396
560
|
}
|
|
@@ -401,6 +565,52 @@ var AgentClient = class {
|
|
|
401
565
|
}
|
|
402
566
|
};
|
|
403
567
|
|
|
568
|
+
// src/stored-message-to-ui-message.ts
|
|
569
|
+
function storedMessageToUiMessage(message) {
|
|
570
|
+
const parts = [];
|
|
571
|
+
if (message.content) {
|
|
572
|
+
parts.push({
|
|
573
|
+
type: "text",
|
|
574
|
+
text: message.content
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
for (const attachment of message.attachments ?? []) {
|
|
578
|
+
parts.push({
|
|
579
|
+
type: "file",
|
|
580
|
+
mediaType: attachment.contentType,
|
|
581
|
+
filename: attachment.name,
|
|
582
|
+
url: attachment.url
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
for (const call of message.toolCalls ?? []) {
|
|
586
|
+
const result = message.toolResults?.find((candidate) => candidate.id === call.id);
|
|
587
|
+
const toolKind = call.kind;
|
|
588
|
+
parts.push({
|
|
589
|
+
type: `tool-${call.name}`,
|
|
590
|
+
toolCallId: call.id,
|
|
591
|
+
...toolKind !== void 0 ? {
|
|
592
|
+
toolMetadata: {
|
|
593
|
+
toolKind
|
|
594
|
+
}
|
|
595
|
+
} : {},
|
|
596
|
+
...result !== void 0 ? {
|
|
597
|
+
state: "output-available",
|
|
598
|
+
input: call.input,
|
|
599
|
+
output: result.output
|
|
600
|
+
} : {
|
|
601
|
+
state: "input-available",
|
|
602
|
+
input: call.input
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
id: message.id,
|
|
608
|
+
role: message.role,
|
|
609
|
+
parts
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
__name(storedMessageToUiMessage, "storedMessageToUiMessage");
|
|
613
|
+
|
|
404
614
|
// src/use-agent-chat.ts
|
|
405
615
|
var import_react = require("@ai-sdk/react");
|
|
406
616
|
var import_react2 = require("react");
|
|
@@ -410,7 +620,11 @@ function useAgentChat(options) {
|
|
|
410
620
|
const [runId, setRunId] = (0, import_react2.useState)(options.resumeRunId);
|
|
411
621
|
const runIdRef = (0, import_react2.useRef)(runId);
|
|
412
622
|
runIdRef.current = runId;
|
|
623
|
+
const autoResumeRunIdRef = (0, import_react2.useRef)(void 0);
|
|
624
|
+
const [autoResume, setAutoResume] = (0, import_react2.useState)(false);
|
|
625
|
+
const [activeRunId, setActiveRunId] = (0, import_react2.useState)(null);
|
|
413
626
|
const regenerateNext = (0, import_react2.useRef)(false);
|
|
627
|
+
const createdThreadId = (0, import_react2.useRef)(void 0);
|
|
414
628
|
const client = (0, import_react2.useMemo)(() => {
|
|
415
629
|
if (options.client) return options.client;
|
|
416
630
|
return new AgentClient({
|
|
@@ -426,9 +640,33 @@ function useAgentChat(options) {
|
|
|
426
640
|
getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
|
|
427
641
|
});
|
|
428
642
|
}, []);
|
|
643
|
+
(0, import_react2.useEffect)(() => {
|
|
644
|
+
if (!options.resume || options.threadId === void 0) return;
|
|
645
|
+
let cancelled = false;
|
|
646
|
+
const threadId = options.threadId;
|
|
647
|
+
void client.getThread(threadId).then((thread) => {
|
|
648
|
+
if (cancelled) return;
|
|
649
|
+
setActiveRunId(thread.activeRunId ?? null);
|
|
650
|
+
if (thread.activeRunId != null) {
|
|
651
|
+
autoResumeRunIdRef.current = thread.activeRunId;
|
|
652
|
+
setAutoResume(true);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
return () => {
|
|
656
|
+
cancelled = true;
|
|
657
|
+
};
|
|
658
|
+
}, [
|
|
659
|
+
client,
|
|
660
|
+
options.resume,
|
|
661
|
+
options.threadId
|
|
662
|
+
]);
|
|
429
663
|
const transport = (0, import_react2.useMemo)(() => {
|
|
430
664
|
function onMeta(meta) {
|
|
431
665
|
setRunId(meta.runId);
|
|
666
|
+
if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
|
|
667
|
+
createdThreadId.current = meta.threadId;
|
|
668
|
+
latest.current.onThreadCreated?.(meta.threadId);
|
|
669
|
+
}
|
|
432
670
|
}
|
|
433
671
|
__name(onMeta, "onMeta");
|
|
434
672
|
return new AgentChatTransport({
|
|
@@ -450,9 +688,10 @@ function useAgentChat(options) {
|
|
|
450
688
|
const pageContext = current.getPageContext?.() ?? null;
|
|
451
689
|
const regenerate2 = regenerateNext.current;
|
|
452
690
|
regenerateNext.current = false;
|
|
691
|
+
const threadId = current.threadId ?? createdThreadId.current;
|
|
453
692
|
return {
|
|
454
|
-
...
|
|
455
|
-
threadId
|
|
693
|
+
...threadId !== void 0 ? {
|
|
694
|
+
threadId
|
|
456
695
|
} : {},
|
|
457
696
|
...pageContext ? {
|
|
458
697
|
pageContext
|
|
@@ -462,13 +701,13 @@ function useAgentChat(options) {
|
|
|
462
701
|
} : {}
|
|
463
702
|
};
|
|
464
703
|
}, "getBody"),
|
|
465
|
-
getResumeRunId: /* @__PURE__ */ __name(() => latest.current.resumeRunId, "getResumeRunId"),
|
|
704
|
+
getResumeRunId: /* @__PURE__ */ __name(() => latest.current.resumeRunId ?? autoResumeRunIdRef.current, "getResumeRunId"),
|
|
466
705
|
onMeta
|
|
467
706
|
});
|
|
468
707
|
}, []);
|
|
469
708
|
const chat = (0, import_react.useChat)({
|
|
470
709
|
transport,
|
|
471
|
-
resume: options.resumeRunId !== void 0,
|
|
710
|
+
resume: options.resumeRunId !== void 0 || autoResume,
|
|
472
711
|
...options.threadId !== void 0 ? {
|
|
473
712
|
id: options.threadId
|
|
474
713
|
} : {},
|
|
@@ -535,10 +774,10 @@ function useAgentChat(options) {
|
|
|
535
774
|
]);
|
|
536
775
|
const cancel = (0, import_react2.useCallback)(async () => {
|
|
537
776
|
chatRef.current.stop();
|
|
538
|
-
const
|
|
539
|
-
if (
|
|
777
|
+
const activeRunId2 = runIdRef.current;
|
|
778
|
+
if (activeRunId2) {
|
|
540
779
|
try {
|
|
541
|
-
await client.cancelStream(
|
|
780
|
+
await client.cancelStream(activeRunId2);
|
|
542
781
|
} catch {
|
|
543
782
|
}
|
|
544
783
|
}
|
|
@@ -570,6 +809,8 @@ function useAgentChat(options) {
|
|
|
570
809
|
...chat,
|
|
571
810
|
addToolResult,
|
|
572
811
|
runId,
|
|
812
|
+
/** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
|
|
813
|
+
activeRunId,
|
|
573
814
|
client,
|
|
574
815
|
threads,
|
|
575
816
|
loadThreads,
|
|
@@ -1179,6 +1420,7 @@ __name(persistSpeechLang, "persistSpeechLang");
|
|
|
1179
1420
|
formatRelativeTime,
|
|
1180
1421
|
loadStoredSpeechLang,
|
|
1181
1422
|
persistSpeechLang,
|
|
1423
|
+
storedMessageToUiMessage,
|
|
1182
1424
|
useAgentChat,
|
|
1183
1425
|
useSpeechRecognition
|
|
1184
1426
|
});
|