veryfront 0.0.67 → 0.0.69

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/ai/react.js CHANGED
@@ -47,12 +47,32 @@ function useChat(options) {
47
47
  const [error, setError] = useState(null);
48
48
  const [data, setData] = useState(null);
49
49
  const abortControllerRef = useRef(null);
50
- const append = useCallback(
50
+ const pendingToolOutputsRef = useRef(/* @__PURE__ */ new Map());
51
+ const addToolOutput = useCallback((output) => {
52
+ pendingToolOutputsRef.current.set(output.toolCallId, output);
53
+ setMessages(
54
+ (prev) => prev.map((msg) => ({
55
+ ...msg,
56
+ parts: msg.parts.map((part) => {
57
+ if (part.type === "tool-call" && part.toolCallId === output.toolCallId) {
58
+ return {
59
+ ...part,
60
+ state: output.state || "output-available",
61
+ output: output.output,
62
+ errorText: output.errorText
63
+ };
64
+ }
65
+ return part;
66
+ })
67
+ }))
68
+ );
69
+ }, []);
70
+ const sendMessage = useCallback(
51
71
  async (message) => {
52
72
  const userMessage = {
53
- ...message,
54
- id: `msg_${Date.now()}`,
55
- timestamp: Date.now()
73
+ id: generateClientId("msg"),
74
+ role: "user",
75
+ parts: [{ type: "text", text: message.text }]
56
76
  };
57
77
  setMessages((prev) => [...prev, userMessage]);
58
78
  setIsLoading(true);
@@ -79,62 +99,43 @@ function useChat(options) {
79
99
  message: `API error: ${response.status}`
80
100
  }));
81
101
  }
82
- if (options.onResponse) {
83
- options.onResponse(response);
84
- }
102
+ options.onResponse?.(response);
85
103
  if (response.body) {
86
- const streamingMessageId = `msg_${Date.now()}`;
104
+ const streamingMessageId = generateClientId("msg");
87
105
  let hasAddedStreamingMessage = false;
88
- await handleStreamingResponse(
89
- response.body,
90
- // onMessage - when streaming is complete
91
- (assistantMessage) => {
106
+ await handleStreamingResponse(response.body, {
107
+ onMessage: (assistantMessage) => {
92
108
  setMessages((prev) => {
93
109
  if (hasAddedStreamingMessage) {
94
110
  return prev.map((m) => m.id === streamingMessageId ? assistantMessage : m);
95
111
  }
96
112
  return [...prev, assistantMessage];
97
113
  });
98
- if (options.onFinish) {
99
- options.onFinish(assistantMessage);
100
- }
101
- },
102
- // onData - for data events
103
- (partialData) => {
104
- setData(partialData);
114
+ options.onFinish?.(assistantMessage);
105
115
  },
106
- // onUpdate - for real-time streaming updates
107
- (partialContent, messageId) => {
116
+ onData: setData,
117
+ onUpdate: (parts, messageId) => {
118
+ const id = messageId || streamingMessageId;
108
119
  if (!hasAddedStreamingMessage) {
109
120
  hasAddedStreamingMessage = true;
110
- setMessages((prev) => [
111
- ...prev,
112
- {
113
- id: messageId || streamingMessageId,
114
- role: "assistant",
115
- content: partialContent,
116
- timestamp: Date.now()
117
- }
118
- ]);
121
+ setMessages((prev) => [...prev, {
122
+ id,
123
+ role: "assistant",
124
+ parts
125
+ }]);
119
126
  } else {
120
- setMessages(
121
- (prev) => prev.map(
122
- (m) => m.id === (messageId || streamingMessageId) ? { ...m, content: partialContent } : m
123
- )
124
- );
127
+ setMessages((prev) => prev.map((m) => m.id === id ? { ...m, parts } : m));
125
128
  }
126
- }
127
- );
129
+ },
130
+ onToolCall: options.onToolCall
131
+ });
128
132
  }
129
133
  } catch (err) {
130
- if (err instanceof Error && err.name === "AbortError") {
134
+ if (err instanceof Error && err.name === "AbortError")
131
135
  return;
132
- }
133
136
  const error2 = err instanceof Error ? err : new Error(String(err));
134
137
  setError(error2);
135
- if (options.onError) {
136
- options.onError(error2);
137
- }
138
+ options.onError?.(error2);
138
139
  } finally {
139
140
  setIsLoading(false);
140
141
  abortControllerRef.current = null;
@@ -145,19 +146,18 @@ function useChat(options) {
145
146
  const reload = useCallback(async () => {
146
147
  if (messages.length === 0)
147
148
  return;
148
- const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
149
- if (lastUserMessageIndex === -1)
149
+ const lastUserIndex = messages.findLastIndex((m) => m.role === "user");
150
+ if (lastUserIndex === -1)
150
151
  return;
151
- const messagesToKeep = messages.slice(0, lastUserMessageIndex);
152
- const lastUserMessage = messages[lastUserMessageIndex];
152
+ const lastUserMessage = messages[lastUserIndex];
153
153
  if (!lastUserMessage)
154
154
  return;
155
- setMessages(messagesToKeep);
156
- await append({
157
- role: lastUserMessage.role,
158
- content: lastUserMessage.content
159
- });
160
- }, [messages, append]);
155
+ const textPart = lastUserMessage.parts.find((p) => p.type === "text");
156
+ if (!textPart)
157
+ return;
158
+ setMessages(messages.slice(0, lastUserIndex));
159
+ await sendMessage({ text: textPart.text });
160
+ }, [messages, sendMessage]);
161
161
  const stop = useCallback(() => {
162
162
  if (abortControllerRef.current) {
163
163
  abortControllerRef.current.abort();
@@ -176,14 +176,11 @@ function useChat(options) {
176
176
  e.preventDefault();
177
177
  if (!input.trim() || isLoading)
178
178
  return;
179
- const messageContent = input;
179
+ const text = input;
180
180
  setInput("");
181
- await append({
182
- role: "user",
183
- content: messageContent
184
- });
181
+ await sendMessage({ text });
185
182
  },
186
- [input, isLoading, append]
183
+ [input, isLoading, sendMessage]
187
184
  );
188
185
  return {
189
186
  messages,
@@ -191,20 +188,52 @@ function useChat(options) {
191
188
  isLoading,
192
189
  error,
193
190
  setInput,
194
- append,
191
+ sendMessage,
195
192
  reload,
196
193
  stop,
197
194
  setMessages,
195
+ addToolOutput,
198
196
  data,
199
197
  handleInputChange,
200
198
  handleSubmit
201
199
  };
202
200
  }
203
- async function handleStreamingResponse(body, onMessage, onData, onUpdate) {
201
+ async function handleStreamingResponse(body, callbacks) {
202
+ const { onMessage, onData, onUpdate, onToolCall } = callbacks;
204
203
  const reader = body.getReader();
205
204
  const decoder = new TextDecoder();
206
- let accumulatedText = "";
207
- let messageId = `msg_${Date.now()}`;
205
+ const textBlocks = /* @__PURE__ */ new Map();
206
+ let currentTextId = "";
207
+ let messageId = "";
208
+ const toolCalls = /* @__PURE__ */ new Map();
209
+ const reasoningBlocks = /* @__PURE__ */ new Map();
210
+ const messageParts = [];
211
+ const buildCurrentParts = () => {
212
+ const parts = [];
213
+ for (const [, block] of textBlocks) {
214
+ if (block.text) {
215
+ parts.push({ type: "text", text: block.text, state: block.state });
216
+ }
217
+ }
218
+ for (const [, reasoning] of reasoningBlocks) {
219
+ parts.push({
220
+ type: "reasoning",
221
+ text: reasoning.text,
222
+ state: reasoning.isComplete ? "done" : "streaming"
223
+ });
224
+ }
225
+ for (const [, tool] of toolCalls) {
226
+ parts.push({
227
+ type: "tool-call",
228
+ toolCallId: tool.toolCallId,
229
+ toolName: tool.toolName,
230
+ state: tool.state,
231
+ input: tool.input,
232
+ output: tool.output
233
+ });
234
+ }
235
+ return parts;
236
+ };
208
237
  while (true) {
209
238
  const { done, value } = await reader.read();
210
239
  if (done) {
@@ -215,40 +244,152 @@ async function handleStreamingResponse(body, onMessage, onData, onUpdate) {
215
244
  for (const line of lines) {
216
245
  if (line.startsWith("data: ")) {
217
246
  const data = line.slice(6);
218
- if (data === "[DONE]") {
219
- if (accumulatedText) {
220
- const assistantMessage = {
221
- id: messageId,
222
- role: "assistant",
223
- content: accumulatedText,
224
- timestamp: Date.now()
225
- };
226
- onMessage(assistantMessage);
227
- }
228
- continue;
229
- }
230
247
  try {
231
248
  const parsed = JSON.parse(data);
232
- if (parsed.type === "data") {
233
- onData(parsed.data);
234
- } else if (parsed.type === "start") {
235
- messageId = parsed.messageId || `msg_${Date.now()}`;
236
- accumulatedText = "";
237
- } else if (parsed.type === "text-delta") {
238
- accumulatedText += parsed.textDelta || "";
239
- if (onUpdate) {
240
- onUpdate(accumulatedText, messageId);
249
+ switch (parsed.type) {
250
+ case "start":
251
+ messageId = parsed.messageId || generateClientId("msg");
252
+ textBlocks.clear();
253
+ toolCalls.clear();
254
+ reasoningBlocks.clear();
255
+ messageParts.length = 0;
256
+ break;
257
+ case "start-step":
258
+ break;
259
+ case "finish-step":
260
+ break;
261
+ case "text-start":
262
+ currentTextId = parsed.id || generateClientId("text");
263
+ textBlocks.set(currentTextId, { text: "", state: "streaming" });
264
+ break;
265
+ case "text-delta": {
266
+ const textId = parsed.id || currentTextId || "default";
267
+ const delta = parsed.delta || "";
268
+ if (!textBlocks.has(textId)) {
269
+ textBlocks.set(textId, { text: "", state: "streaming" });
270
+ currentTextId = textId;
271
+ }
272
+ const block = textBlocks.get(textId);
273
+ block.text += delta;
274
+ onUpdate?.(buildCurrentParts(), messageId);
275
+ break;
241
276
  }
242
- } else if (parsed.type === "finish") {
243
- if (accumulatedText) {
244
- const assistantMessage = {
245
- id: messageId,
246
- role: "assistant",
247
- content: accumulatedText,
248
- timestamp: Date.now()
277
+ case "text-end": {
278
+ const textId = parsed.id || currentTextId;
279
+ const block = textBlocks.get(textId);
280
+ if (block) {
281
+ block.state = "done";
282
+ if (block.text) {
283
+ messageParts.push({ type: "text", text: block.text, state: "done" });
284
+ }
285
+ }
286
+ break;
287
+ }
288
+ case "tool-input-start": {
289
+ const toolCallId = parsed.toolCallId || generateClientId("tool");
290
+ const toolCall = {
291
+ toolCallId,
292
+ toolName: parsed.toolName || "unknown",
293
+ inputText: "",
294
+ state: "input-streaming"
249
295
  };
250
- onMessage(assistantMessage);
296
+ toolCalls.set(toolCallId, toolCall);
297
+ onUpdate?.(buildCurrentParts(), messageId);
298
+ break;
299
+ }
300
+ case "tool-input-delta": {
301
+ const toolCallId = parsed.toolCallId;
302
+ const toolCall = toolCalls.get(toolCallId);
303
+ if (toolCall) {
304
+ toolCall.inputText += parsed.inputTextDelta || parsed.delta || "";
305
+ onUpdate?.(buildCurrentParts(), messageId);
306
+ }
307
+ break;
308
+ }
309
+ case "tool-input-available": {
310
+ const toolCallId = parsed.toolCallId;
311
+ const toolCall = toolCalls.get(toolCallId);
312
+ if (toolCall) {
313
+ toolCall.input = parsed.input;
314
+ toolCall.toolName = parsed.toolName || toolCall.toolName;
315
+ toolCall.state = "input-available";
316
+ onToolCall?.({
317
+ toolCall: {
318
+ toolCallId,
319
+ toolName: toolCall.toolName,
320
+ input: toolCall.input
321
+ }
322
+ });
323
+ messageParts.push({
324
+ type: "tool-call",
325
+ toolCallId,
326
+ toolName: toolCall.toolName,
327
+ state: "input-available",
328
+ input: toolCall.input
329
+ });
330
+ onUpdate?.(buildCurrentParts(), messageId);
331
+ }
332
+ break;
333
+ }
334
+ case "tool-output-available": {
335
+ const toolCallId = parsed.toolCallId;
336
+ const toolCall = toolCalls.get(toolCallId);
337
+ if (toolCall) {
338
+ toolCall.output = parsed.output;
339
+ toolCall.state = "output-available";
340
+ messageParts.push({
341
+ type: "tool-result",
342
+ toolCallId,
343
+ toolName: toolCall.toolName,
344
+ result: toolCall.output
345
+ });
346
+ onUpdate?.(buildCurrentParts(), messageId);
347
+ }
348
+ break;
251
349
  }
350
+ case "reasoning-start": {
351
+ const reasoningId = parsed.id || generateClientId("reasoning");
352
+ const reasoning = {
353
+ id: reasoningId,
354
+ text: "",
355
+ isComplete: false
356
+ };
357
+ reasoningBlocks.set(reasoningId, reasoning);
358
+ onUpdate?.(buildCurrentParts(), messageId);
359
+ break;
360
+ }
361
+ case "reasoning-delta": {
362
+ const reasoningId = parsed.id;
363
+ const reasoning = reasoningBlocks.get(reasoningId);
364
+ if (reasoning) {
365
+ reasoning.text += parsed.delta || "";
366
+ onUpdate?.(buildCurrentParts(), messageId);
367
+ }
368
+ break;
369
+ }
370
+ case "reasoning-end": {
371
+ const reasoningId = parsed.id;
372
+ const reasoning = reasoningBlocks.get(reasoningId);
373
+ if (reasoning) {
374
+ reasoning.isComplete = true;
375
+ messageParts.push({
376
+ type: "reasoning",
377
+ text: reasoning.text,
378
+ state: "done"
379
+ });
380
+ onUpdate?.(buildCurrentParts(), messageId);
381
+ }
382
+ break;
383
+ }
384
+ case "finish": {
385
+ if (messageParts.length > 0 || textBlocks.size > 0) {
386
+ onMessage(createAssistantMessage(messageId, messageParts));
387
+ }
388
+ break;
389
+ }
390
+ case "data":
391
+ onData(parsed.data || parsed.value);
392
+ break;
252
393
  }
253
394
  } catch {
254
395
  }
@@ -256,6 +397,16 @@ async function handleStreamingResponse(body, onMessage, onData, onUpdate) {
256
397
  }
257
398
  }
258
399
  }
400
+ function createAssistantMessage(messageId, parts) {
401
+ return {
402
+ id: messageId || generateClientId("msg"),
403
+ role: "assistant",
404
+ parts
405
+ };
406
+ }
407
+ function generateClientId(prefix) {
408
+ return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
409
+ }
259
410
 
260
411
  // src/ai/react/hooks/use-agent.ts
261
412
  import { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";