veryfront 0.1.259 → 0.1.261

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/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.259",
3
+ "version": "0.1.261",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -0,0 +1,58 @@
1
+ import type {
2
+ AgUiBrowserEncodedEvent,
3
+ AgUiBrowserEncoderState,
4
+ AgUiBrowserRunFinishedMetadata,
5
+ AgUiRuntimeStreamEvent,
6
+ } from "./ag-ui-browser-encoder.js";
7
+ import {
8
+ type AgUiRuntimeEventEncoder,
9
+ createAgUiRuntimeEventEncoder,
10
+ } from "./ag-ui-runtime-event-encoder.js";
11
+ import type { AgentResponse } from "./types.js";
12
+
13
+ export interface AgUiBrowserChunkEncoder<TChunk> {
14
+ state: AgUiBrowserEncoderState;
15
+ encode: (chunk: TChunk) => AgUiBrowserEncodedEvent[];
16
+ finalize: (response: AgentResponse | null) => AgUiBrowserEncodedEvent[];
17
+ }
18
+
19
+ export interface CreateAgUiBrowserChunkEncoderOptions<TChunk> {
20
+ getRuntimeEvents: (chunk: TChunk) => readonly AgUiRuntimeStreamEvent[];
21
+ getMetadataFromChunk?: (
22
+ chunk: TChunk,
23
+ ) => Partial<AgUiBrowserRunFinishedMetadata> | null | undefined;
24
+ initialMetadata?: Partial<AgUiBrowserRunFinishedMetadata>;
25
+ }
26
+
27
+ function mergeMetadata(
28
+ target: AgUiBrowserEncoderState["metadata"],
29
+ metadata: Partial<AgUiBrowserRunFinishedMetadata> | null | undefined,
30
+ ): void {
31
+ if (!metadata) {
32
+ return;
33
+ }
34
+
35
+ if (typeof metadata.provider === "string") target.provider = metadata.provider;
36
+ if (typeof metadata.model === "string") target.model = metadata.model;
37
+ if (typeof metadata.inputTokens === "number") target.inputTokens = metadata.inputTokens;
38
+ if (typeof metadata.outputTokens === "number") target.outputTokens = metadata.outputTokens;
39
+ if (typeof metadata.totalTokens === "number") target.totalTokens = metadata.totalTokens;
40
+ if (typeof metadata.finishReason === "string") target.finishReason = metadata.finishReason;
41
+ }
42
+
43
+ export function createAgUiBrowserChunkEncoder<TChunk>(
44
+ options: CreateAgUiBrowserChunkEncoderOptions<TChunk>,
45
+ ): AgUiBrowserChunkEncoder<TChunk> {
46
+ const runtimeEventEncoder: AgUiRuntimeEventEncoder = createAgUiRuntimeEventEncoder({
47
+ initialMetadata: options.initialMetadata,
48
+ });
49
+
50
+ return {
51
+ state: runtimeEventEncoder.state,
52
+ encode: (chunk) => {
53
+ mergeMetadata(runtimeEventEncoder.state.metadata, options.getMetadataFromChunk?.(chunk));
54
+ return options.getRuntimeEvents(chunk).flatMap((event) => runtimeEventEncoder.encode(event));
55
+ },
56
+ finalize: (response) => runtimeEventEncoder.finalize(response),
57
+ };
58
+ }
@@ -0,0 +1,359 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import {
3
+ mergeToolInputDelta,
4
+ parseToolInputObject,
5
+ stripLeadingEmptyObjectPlaceholder,
6
+ } from "./data-stream.js";
7
+ import type { AgUiRuntimeStreamEvent } from "./ag-ui-browser-encoder.js";
8
+ import type { ChatFinishReason, ChatStreamEvent } from "../chat/protocol.js";
9
+
10
+ export interface AgUiRuntimeChatStreamEncoderState {
11
+ isStepOpen: boolean;
12
+ finishReason: ChatFinishReason;
13
+ }
14
+
15
+ export interface AgUiRuntimeChatStreamEncoder {
16
+ state: AgUiRuntimeChatStreamEncoderState;
17
+ encode: (event: AgUiRuntimeStreamEvent) => ChatStreamEvent[];
18
+ }
19
+
20
+ export interface CreateAgUiRuntimeChatStreamEncoderOptions {
21
+ responseMessageId: string;
22
+ sendReasoning?: boolean;
23
+ onError?: (error: unknown) => string;
24
+ }
25
+
26
+ type ToolPart = {
27
+ toolName: string;
28
+ inputText: string;
29
+ input: Record<string, unknown>;
30
+ };
31
+
32
+ type PendingToolDelta = {
33
+ inputText: string;
34
+ chunks: string[];
35
+ };
36
+
37
+ function getStringField(event: AgUiRuntimeStreamEvent, key: string): string | undefined {
38
+ const value = event[key];
39
+ return typeof value === "string" ? value : undefined;
40
+ }
41
+
42
+ function isRecord(value: unknown): value is Record<string, unknown> {
43
+ return typeof value === "object" && value !== null && !Array.isArray(value);
44
+ }
45
+
46
+ function getDataRecord(event: AgUiRuntimeStreamEvent): Record<string, unknown> | undefined {
47
+ return isRecord(event.data) ? event.data : undefined;
48
+ }
49
+
50
+ function getToolInput(event: AgUiRuntimeStreamEvent): Record<string, unknown> {
51
+ return parseToolInputObject(event.input);
52
+ }
53
+
54
+ function parseStreamedToolInput(inputText: string): Record<string, unknown> | null {
55
+ const normalizedInputText = stripLeadingEmptyObjectPlaceholder(inputText).trim();
56
+ if (normalizedInputText.length === 0) {
57
+ return {};
58
+ }
59
+
60
+ try {
61
+ const parsed = JSON.parse(normalizedInputText);
62
+ return isRecord(parsed) ? Object.fromEntries(Object.entries(parsed)) : {};
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ function isEmptyRecord(value: Record<string, unknown>): boolean {
69
+ return Object.keys(value).length === 0;
70
+ }
71
+
72
+ function formatErrorText(error: unknown, onError?: (error: unknown) => string): string {
73
+ return onError ? onError(error) : error instanceof Error ? error.message : String(error);
74
+ }
75
+
76
+ export function createAgUiRuntimeChatStreamEncoder(
77
+ options: CreateAgUiRuntimeChatStreamEncoderOptions,
78
+ ): AgUiRuntimeChatStreamEncoder {
79
+ const state: AgUiRuntimeChatStreamEncoderState = {
80
+ isStepOpen: false,
81
+ finishReason: "stop",
82
+ };
83
+ const startedTextBlockIds = new Set<string>();
84
+ const seenTextBlockIds = new Set<string>();
85
+ const emittedToolInputStartIds = new Set<string>();
86
+ const toolParts = new Map<string, ToolPart>();
87
+ const pendingToolDeltas = new Map<string, PendingToolDelta>();
88
+
89
+ const ensureStepStarted = (events: ChatStreamEvent[]) => {
90
+ if (state.isStepOpen) {
91
+ return;
92
+ }
93
+ state.isStepOpen = true;
94
+ events.push({ type: "start-step" });
95
+ };
96
+
97
+ const appendPendingToolDelta = (toolCallId: string, inputTextDelta: string) => {
98
+ const existing = pendingToolDeltas.get(toolCallId);
99
+ if (existing) {
100
+ existing.inputText = mergeToolInputDelta(existing.inputText, inputTextDelta);
101
+ existing.chunks.push(inputTextDelta);
102
+ return;
103
+ }
104
+
105
+ pendingToolDeltas.set(toolCallId, {
106
+ inputText: inputTextDelta,
107
+ chunks: [inputTextDelta],
108
+ });
109
+ };
110
+
111
+ const flushPendingToolDeltas = (toolCallId: string): ChatStreamEvent[] => {
112
+ const pending = pendingToolDeltas.get(toolCallId);
113
+ if (!pending) {
114
+ return [];
115
+ }
116
+ pendingToolDeltas.delete(toolCallId);
117
+ return pending.chunks.map((inputTextDelta) => ({
118
+ type: "tool-input-delta",
119
+ toolCallId,
120
+ inputTextDelta,
121
+ }));
122
+ };
123
+
124
+ return {
125
+ state,
126
+ encode: (event) => {
127
+ const events: ChatStreamEvent[] = [];
128
+
129
+ switch (event.type) {
130
+ case "message-start":
131
+ case "message-finish":
132
+ return events;
133
+ case "step-start":
134
+ ensureStepStarted(events);
135
+ return events;
136
+ case "step-end":
137
+ if (state.isStepOpen) {
138
+ state.isStepOpen = false;
139
+ events.push({ type: "finish-step" });
140
+ }
141
+ return events;
142
+ case "text-start": {
143
+ ensureStepStarted(events);
144
+ const id = getStringField(event, "id") ?? options.responseMessageId;
145
+ if (!seenTextBlockIds.has(id)) {
146
+ seenTextBlockIds.add(id);
147
+ } else if (startedTextBlockIds.has(id)) {
148
+ events.push({ type: "text-start", id: options.responseMessageId });
149
+ }
150
+ return events;
151
+ }
152
+ case "text-delta": {
153
+ ensureStepStarted(events);
154
+ const id = getStringField(event, "id") ?? options.responseMessageId;
155
+ const delta = getStringField(event, "delta") ?? "";
156
+ if (delta.length === 0) {
157
+ return events;
158
+ }
159
+ if (!startedTextBlockIds.has(id)) {
160
+ startedTextBlockIds.add(id);
161
+ seenTextBlockIds.add(id);
162
+ events.push({ type: "text-start", id: options.responseMessageId });
163
+ }
164
+ events.push({ type: "text-delta", id: options.responseMessageId, delta });
165
+ return events;
166
+ }
167
+ case "text-end": {
168
+ const id = getStringField(event, "id") ?? options.responseMessageId;
169
+ if (startedTextBlockIds.has(id)) {
170
+ startedTextBlockIds.delete(id);
171
+ events.push({ type: "text-end", id: options.responseMessageId });
172
+ }
173
+ return events;
174
+ }
175
+ case "reasoning-start": {
176
+ ensureStepStarted(events);
177
+ const id = getStringField(event, "id") ?? dntShim.crypto.randomUUID();
178
+ events.push({ type: "reasoning-start", id });
179
+ return events;
180
+ }
181
+ case "reasoning-delta": {
182
+ ensureStepStarted(events);
183
+ if (options.sendReasoning === false) {
184
+ return events;
185
+ }
186
+ const id = getStringField(event, "id") ?? dntShim.crypto.randomUUID();
187
+ const delta = getStringField(event, "delta") ?? "";
188
+ if (delta.length > 0) {
189
+ events.push({ type: "reasoning-delta", id, delta });
190
+ }
191
+ return events;
192
+ }
193
+ case "reasoning-end": {
194
+ const id = getStringField(event, "id");
195
+ if (id) {
196
+ events.push({ type: "reasoning-end", id });
197
+ }
198
+ return events;
199
+ }
200
+ case "tool-input-start": {
201
+ ensureStepStarted(events);
202
+ const toolCallId = getStringField(event, "toolCallId");
203
+ const toolName = getStringField(event, "toolName");
204
+ if (!toolCallId || !toolName) {
205
+ return events;
206
+ }
207
+ const toolPart = toolParts.get(toolCallId);
208
+ if (!toolPart) {
209
+ toolParts.set(toolCallId, {
210
+ toolName,
211
+ inputText: "",
212
+ input: {},
213
+ });
214
+ }
215
+ if (!emittedToolInputStartIds.has(toolCallId)) {
216
+ emittedToolInputStartIds.add(toolCallId);
217
+ events.push({ type: "tool-input-start", toolCallId, toolName });
218
+ }
219
+ const pendingEvents = flushPendingToolDeltas(toolCallId);
220
+ const existingToolPart = toolParts.get(toolCallId);
221
+ if (existingToolPart) {
222
+ for (const pendingEvent of pendingEvents) {
223
+ if (pendingEvent.type === "tool-input-delta") {
224
+ existingToolPart.inputText = mergeToolInputDelta(
225
+ existingToolPart.inputText,
226
+ pendingEvent.inputTextDelta,
227
+ );
228
+ const parsedInput = parseStreamedToolInput(existingToolPart.inputText);
229
+ if (parsedInput) {
230
+ existingToolPart.input = parsedInput;
231
+ }
232
+ }
233
+ }
234
+ }
235
+ events.push(...pendingEvents);
236
+ return events;
237
+ }
238
+ case "tool-input-delta": {
239
+ ensureStepStarted(events);
240
+ const toolCallId = getStringField(event, "toolCallId");
241
+ const inputTextDelta = getStringField(event, "inputTextDelta") ??
242
+ getStringField(event, "delta") ?? "";
243
+ if (!toolCallId || inputTextDelta.length === 0) {
244
+ return events;
245
+ }
246
+ const toolPart = toolParts.get(toolCallId);
247
+ if (!toolPart) {
248
+ appendPendingToolDelta(toolCallId, inputTextDelta);
249
+ return events;
250
+ }
251
+ toolPart.inputText = mergeToolInputDelta(toolPart.inputText, inputTextDelta);
252
+ const parsedInput = parseStreamedToolInput(toolPart.inputText);
253
+ if (parsedInput) {
254
+ toolPart.input = parsedInput;
255
+ }
256
+ events.push({ type: "tool-input-delta", toolCallId, inputTextDelta });
257
+ return events;
258
+ }
259
+ case "tool-input-available": {
260
+ ensureStepStarted(events);
261
+ const toolCallId = getStringField(event, "toolCallId");
262
+ const toolName = getStringField(event, "toolName");
263
+ if (!toolCallId || !toolName) {
264
+ return events;
265
+ }
266
+ const inputRecord = getToolInput(event);
267
+ const existingToolPart = toolParts.get(toolCallId);
268
+ const pendingToolDelta = pendingToolDeltas.get(toolCallId);
269
+ const pendingInputText = pendingToolDelta?.inputText ?? "";
270
+ const parsedPendingInput = pendingInputText.length > 0
271
+ ? parseStreamedToolInput(pendingInputText)
272
+ : null;
273
+ const resolvedInputRecord = isEmptyRecord(inputRecord)
274
+ ? existingToolPart && !isEmptyRecord(existingToolPart.input)
275
+ ? existingToolPart.input
276
+ : parsedPendingInput && !isEmptyRecord(parsedPendingInput)
277
+ ? parsedPendingInput
278
+ : inputRecord
279
+ : inputRecord;
280
+
281
+ if (existingToolPart) {
282
+ existingToolPart.toolName = toolName;
283
+ existingToolPart.inputText = pendingInputText;
284
+ existingToolPart.input = resolvedInputRecord;
285
+ } else {
286
+ toolParts.set(toolCallId, {
287
+ toolName,
288
+ inputText: pendingInputText,
289
+ input: resolvedInputRecord,
290
+ });
291
+ }
292
+
293
+ if (!emittedToolInputStartIds.has(toolCallId)) {
294
+ emittedToolInputStartIds.add(toolCallId);
295
+ events.push({ type: "tool-input-start", toolCallId, toolName });
296
+ }
297
+ events.push(...flushPendingToolDeltas(toolCallId));
298
+ events.push({
299
+ type: "tool-input-available",
300
+ toolCallId,
301
+ toolName,
302
+ input: resolvedInputRecord,
303
+ });
304
+ return events;
305
+ }
306
+ case "tool-output-available": {
307
+ ensureStepStarted(events);
308
+ const toolCallId = getStringField(event, "toolCallId");
309
+ if (!toolCallId) {
310
+ return events;
311
+ }
312
+ events.push({ type: "tool-output-available", toolCallId, output: event.output });
313
+ return events;
314
+ }
315
+ case "tool-output-error": {
316
+ ensureStepStarted(events);
317
+ const toolCallId = getStringField(event, "toolCallId");
318
+ if (!toolCallId) {
319
+ return events;
320
+ }
321
+ const errorText = getStringField(event, "errorText") ?? "Tool execution failed";
322
+ events.push({ type: "tool-output-error", toolCallId, errorText });
323
+ return events;
324
+ }
325
+ case "data": {
326
+ const data = getDataRecord(event);
327
+ const name = typeof data?.name === "string" && data.name.length > 0
328
+ ? data.name
329
+ : undefined;
330
+ if (name) {
331
+ const dataValue = data && Object.hasOwn(data, "value") ? data.value : undefined;
332
+ events.push({
333
+ type: `data-${name}`,
334
+ data: dataValue,
335
+ });
336
+ return events;
337
+ }
338
+ if (data && typeof data.model === "string") {
339
+ events.push({ type: "message-metadata", messageMetadata: { modelId: data.model } });
340
+ }
341
+ return events;
342
+ }
343
+ case "error": {
344
+ state.finishReason = "error";
345
+ events.push({
346
+ type: "error",
347
+ errorText: formatErrorText(
348
+ getStringField(event, "error") ?? "Framework stream failed",
349
+ options.onError,
350
+ ),
351
+ });
352
+ return events;
353
+ }
354
+ default:
355
+ return events;
356
+ }
357
+ },
358
+ };
359
+ }
@@ -174,11 +174,22 @@ export {
174
174
  finalizeAgUiBrowserEvents,
175
175
  mapRuntimeStreamEventToAgUiBrowserEvents,
176
176
  } from "./ag-ui-browser-encoder.js";
177
+ export {
178
+ type AgUiBrowserChunkEncoder,
179
+ createAgUiBrowserChunkEncoder,
180
+ type CreateAgUiBrowserChunkEncoderOptions,
181
+ } from "./ag-ui-browser-chunk-encoder.js";
177
182
  export {
178
183
  type AgUiRuntimeEventEncoder,
179
184
  createAgUiRuntimeEventEncoder,
180
185
  type CreateAgUiRuntimeEventEncoderOptions,
181
186
  } from "./ag-ui-runtime-event-encoder.js";
187
+ export {
188
+ type AgUiRuntimeChatStreamEncoder,
189
+ type AgUiRuntimeChatStreamEncoderState,
190
+ createAgUiRuntimeChatStreamEncoder,
191
+ type CreateAgUiRuntimeChatStreamEncoderOptions,
192
+ } from "./ag-ui-runtime-chat-stream-encoder.js";
182
193
  export {
183
194
  type AgUiBrowserFinalizeTracker,
184
195
  createAgUiBrowserFinalizeTracker,