veryfront 0.1.199 → 0.1.201

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.
@@ -299,12 +299,10 @@ export class AgentRuntime {
299
299
  model: effectiveModel,
300
300
  },
301
301
  });
302
- sendSSE(controller, encoder, { type: "text-start", id: textPartId });
303
302
  const response = await this.executeAgentLoopStreaming(systemPrompt, memoryMessages, controller, encoder, callbacks, textPartId, toolContext, context, resolvedModelString, languageModel, transport.headers, transport.providerOptions, maxOutputTokensOverride, streamAbortSignal);
304
303
  throwIfAborted(streamAbortSignal);
305
304
  callbacks?.onFinish?.(response);
306
305
  throwIfAborted(streamAbortSignal);
307
- sendSSE(controller, encoder, { type: "text-end", id: textPartId });
308
306
  sendSSE(controller, encoder, { type: "message-finish" });
309
307
  closeSSEStream(controller);
310
308
  }
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.199";
1
+ export declare const VERSION = "0.1.201";
2
2
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.199";
3
+ export const VERSION = "0.1.201";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.199",
3
+ "version": "0.1.201",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  type SafeParseResult<T> = { success: true; data: T } | { success: false; error: z.ZodError };
4
4
  import { createFileSystem, getEnv } from "../../../src/platform/index.js";
5
5
  import { basename, extname, join, normalize, relative } from "../../../src/platform/compat/path/index.js";
6
+ import { type CommandResult, runCommand } from "../../../src/platform/compat/process.js";
6
7
  import { withSpan } from "../../../src/observability/tracing/otlp-setup.js";
7
8
  import { cliLogger } from "../../utils/index.js";
8
9
  import { type ApiClient, createApiClient, resolveConfigWithAuth } from "../../shared/config.js";
@@ -485,6 +486,76 @@ export async function runKnowledgeParser(input: {
485
486
  return result;
486
487
  }
487
488
 
489
+ function isMissingPythonExecutableError(error: unknown): boolean {
490
+ if (!(error instanceof Error)) {
491
+ return false;
492
+ }
493
+
494
+ const errorWithCode = error as Error & { code?: unknown };
495
+ if (errorWithCode.code === "ENOENT") {
496
+ return true;
497
+ }
498
+
499
+ return error.name === "NotFound" ||
500
+ /\bENOENT\b/i.test(error.message) ||
501
+ /not found/i.test(error.message) ||
502
+ /no such file or directory/i.test(error.message);
503
+ }
504
+
505
+ export async function executeKnowledgeParserCommand(input: {
506
+ scriptPath: string;
507
+ inputJsonPath: string;
508
+ outputJsonPath: string;
509
+ env?: Record<string, string>;
510
+ }, deps: {
511
+ runCommandFn?: (
512
+ cmd: string,
513
+ options: {
514
+ args: string[];
515
+ env?: Record<string, string>;
516
+ capture: true;
517
+ },
518
+ ) => Promise<CommandResult>;
519
+ } = {}): Promise<void> {
520
+ const runCommandFn = deps.runCommandFn ?? runCommand;
521
+ let result: CommandResult;
522
+ try {
523
+ result = await runCommandFn("python3", {
524
+ args: [
525
+ input.scriptPath,
526
+ "--input-json",
527
+ input.inputJsonPath,
528
+ "--output-json",
529
+ input.outputJsonPath,
530
+ ],
531
+ ...(input.env ? { env: input.env } : {}),
532
+ capture: true,
533
+ });
534
+ } catch (error) {
535
+ if (isMissingPythonExecutableError(error)) {
536
+ throw new Error(
537
+ "python3 is required. Install python3 and the supported parser packages, or run the command inside the Veryfront sandbox.",
538
+ );
539
+ }
540
+ throw error;
541
+ }
542
+
543
+ if (result.success) {
544
+ return;
545
+ }
546
+
547
+ const stderr = result.stderr?.trim();
548
+ const stdout = result.stdout?.trim();
549
+
550
+ if (result.code === 1 && !stderr && !stdout) {
551
+ throw new Error(
552
+ "python3 is required. Install python3 and the supported parser packages, or run the command inside the Veryfront sandbox.",
553
+ );
554
+ }
555
+
556
+ throw new Error(stderr || stdout || "parser exited unsuccessfully");
557
+ }
558
+
488
559
  export async function runKnowledgeParsers(input: {
489
560
  files: KnowledgeParserInput[];
490
561
  outputDir: string;
@@ -515,27 +586,12 @@ export async function runKnowledgeParsers(input: {
515
586
  );
516
587
  await dntShim.Deno.writeTextFile(scriptPath, knowledgeIngestPythonSource);
517
588
 
518
- let result: dntShim.Deno.CommandOutput;
519
- try {
520
- result = await new dntShim.Deno.Command("python3", {
521
- args: [scriptPath, "--input-json", inputJsonPath, "--output-json", outputJsonPath],
522
- ...(input.env ? { env: input.env } : {}),
523
- stdout: "piped",
524
- stderr: "piped",
525
- }).output();
526
- } catch (error) {
527
- if (error instanceof dntShim.Deno.errors.NotFound) {
528
- throw new Error(
529
- "python3 is required. Install python3 and the supported parser packages, or run the command inside the Veryfront sandbox.",
530
- );
531
- }
532
- throw error;
533
- }
534
-
535
- if (result.code !== 0) {
536
- const stderr = new TextDecoder().decode(result.stderr).trim();
537
- throw new Error(stderr || "parser exited unsuccessfully");
538
- }
589
+ await executeKnowledgeParserCommand({
590
+ scriptPath,
591
+ inputJsonPath,
592
+ outputJsonPath,
593
+ env: input.env,
594
+ });
539
595
 
540
596
  const raw = await dntShim.Deno.readTextFile(outputJsonPath);
541
597
  const parsed = JSON.parse(raw) as KnowledgeParserResult | KnowledgeParserResult[];
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.199",
3
+ "version": "0.1.201",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -234,6 +234,28 @@ function createTextEvent(
234
234
  };
235
235
  }
236
236
 
237
+ function closeOpenTextEvent(state: AgUiBrowserEncoderState): AgUiBrowserEncodedEvent[] {
238
+ if (!state.textOpen) {
239
+ return [];
240
+ }
241
+
242
+ state.textOpen = false;
243
+ return [createTextEvent(getMessageId(state, { type: "text-end" }), "TextMessageEnd")];
244
+ }
245
+
246
+ function closeOpenReasoningEvent(state: AgUiBrowserEncoderState): AgUiBrowserEncodedEvent[] {
247
+ if (state.reasoningMessageId === null) {
248
+ return [];
249
+ }
250
+
251
+ const messageId = state.reasoningMessageId;
252
+ state.reasoningMessageId = null;
253
+ return [{
254
+ event: "ReasoningMessageEnd",
255
+ payload: { messageId },
256
+ }];
257
+ }
258
+
237
259
  export function mapRuntimeStreamEventToAgUiBrowserEvents(
238
260
  state: AgUiBrowserEncoderState,
239
261
  event: AgUiRuntimeStreamEvent,
@@ -254,33 +276,38 @@ export function mapRuntimeStreamEventToAgUiBrowserEvents(
254
276
  return [];
255
277
 
256
278
  case "text-start": {
279
+ const events = closeOpenReasoningEvent(state);
257
280
  if (state.textOpen) return [];
258
281
  const messageId = getMessageId(state, event);
259
282
  state.textOpen = true;
260
283
  state.sawVisibleOutput = true;
261
- return [createTextEvent(messageId, "TextMessageStart")];
284
+ events.push(createTextEvent(messageId, "TextMessageStart"));
285
+ return events;
262
286
  }
263
287
 
264
288
  case "text-delta": {
289
+ const events = closeOpenReasoningEvent(state);
265
290
  const messageId = getMessageId(state, event);
266
291
  state.sawVisibleOutput = true;
267
292
  if (!state.textOpen) {
268
293
  state.textOpen = true;
269
- return [
294
+ events.push(
270
295
  createTextEvent(messageId, "TextMessageStart"),
271
296
  createTextEvent(
272
297
  messageId,
273
298
  "TextMessageContent",
274
299
  typeof event.delta === "string" ? event.delta : "",
275
300
  ),
276
- ];
301
+ );
302
+ return events;
277
303
  }
278
304
 
279
- return [createTextEvent(
305
+ events.push(createTextEvent(
280
306
  messageId,
281
307
  "TextMessageContent",
282
308
  typeof event.delta === "string" ? event.delta : "",
283
- )];
309
+ ));
310
+ return events;
284
311
  }
285
312
 
286
313
  case "text-end": {
@@ -289,13 +316,23 @@ export function mapRuntimeStreamEventToAgUiBrowserEvents(
289
316
  return [createTextEvent(getMessageId(state, event), "TextMessageEnd")];
290
317
  }
291
318
 
292
- case "reasoning-start":
319
+ case "reasoning-start": {
320
+ const events = closeOpenTextEvent(state);
321
+ events.push(...closeOpenReasoningEvent(state));
293
322
  state.sawVisibleOutput = true;
294
- return [createReasoningEvent(state, event, "ReasoningMessageStart")];
323
+ events.push(createReasoningEvent(state, event, "ReasoningMessageStart"));
324
+ return events;
325
+ }
295
326
 
296
- case "reasoning-delta":
327
+ case "reasoning-delta": {
328
+ const events = closeOpenTextEvent(state);
297
329
  state.sawVisibleOutput = true;
298
- return [createReasoningEvent(state, event, "ReasoningMessageContent")];
330
+ if (state.reasoningMessageId === null) {
331
+ events.push(createReasoningEvent(state, event, "ReasoningMessageStart"));
332
+ }
333
+ events.push(createReasoningEvent(state, event, "ReasoningMessageContent"));
334
+ return events;
335
+ }
299
336
 
300
337
  case "reasoning-end": {
301
338
  const reasoningEvent = createReasoningEvent(state, event, "ReasoningMessageEnd");
@@ -303,15 +340,21 @@ export function mapRuntimeStreamEventToAgUiBrowserEvents(
303
340
  return [reasoningEvent];
304
341
  }
305
342
 
306
- case "tool-input-start":
343
+ case "tool-input-start": {
344
+ const events = [
345
+ ...closeOpenTextEvent(state),
346
+ ...closeOpenReasoningEvent(state),
347
+ ];
307
348
  state.sawVisibleOutput = true;
308
- return [{
349
+ events.push({
309
350
  event: "ToolCallStart",
310
351
  payload: {
311
352
  toolCallId: event.toolCallId,
312
353
  toolCallName: event.toolName,
313
354
  },
314
- }];
355
+ });
356
+ return events;
357
+ }
315
358
 
316
359
  case "tool-input-delta":
317
360
  state.sawVisibleOutput = true;
@@ -328,12 +371,20 @@ export function mapRuntimeStreamEventToAgUiBrowserEvents(
328
371
 
329
372
  case "tool-input-available": {
330
373
  state.sawVisibleOutput = true;
331
- return completeToolInput(state, event);
374
+ return [
375
+ ...closeOpenTextEvent(state),
376
+ ...closeOpenReasoningEvent(state),
377
+ ...completeToolInput(state, event),
378
+ ];
332
379
  }
333
380
 
334
381
  case "tool-input-error": {
335
382
  state.sawVisibleOutput = true;
336
- const events = completeToolInput(state, event);
383
+ const events = [
384
+ ...closeOpenTextEvent(state),
385
+ ...closeOpenReasoningEvent(state),
386
+ ...completeToolInput(state, event),
387
+ ];
337
388
  events.push({
338
389
  event: "ToolCallResult",
339
390
  payload: {
@@ -349,25 +400,45 @@ export function mapRuntimeStreamEventToAgUiBrowserEvents(
349
400
 
350
401
  case "tool-output-available":
351
402
  state.sawVisibleOutput = true;
352
- return [createToolResultEvent(event.toolCallId, event.output)];
403
+ return [
404
+ ...closeOpenTextEvent(state),
405
+ ...closeOpenReasoningEvent(state),
406
+ createToolResultEvent(event.toolCallId, event.output),
407
+ ];
353
408
 
354
409
  case "tool-output-error":
355
410
  state.sawVisibleOutput = true;
356
- return [createToolResultEvent(event.toolCallId, { error: event.errorText }, true)];
411
+ return [
412
+ ...closeOpenTextEvent(state),
413
+ ...closeOpenReasoningEvent(state),
414
+ createToolResultEvent(event.toolCallId, { error: event.errorText }, true),
415
+ ];
357
416
 
358
417
  case "tool-output-denied":
359
418
  state.sawVisibleOutput = true;
360
- return [createToolResultEvent(event.toolCallId, { error: "Tool output denied" }, true)];
419
+ return [
420
+ ...closeOpenTextEvent(state),
421
+ ...closeOpenReasoningEvent(state),
422
+ createToolResultEvent(event.toolCallId, { error: "Tool output denied" }, true),
423
+ ];
361
424
 
362
425
  case "step-start":
363
426
  case "start-step":
364
427
  state.sawVisibleOutput = true;
365
- return [createStepEvent(state, "StepStarted")];
428
+ return [
429
+ ...closeOpenTextEvent(state),
430
+ ...closeOpenReasoningEvent(state),
431
+ createStepEvent(state, "StepStarted"),
432
+ ];
366
433
 
367
434
  case "step-end":
368
435
  case "finish-step":
369
436
  state.sawVisibleOutput = true;
370
- return [createStepEvent(state, "StepFinished")];
437
+ return [
438
+ ...closeOpenTextEvent(state),
439
+ ...closeOpenReasoningEvent(state),
440
+ createStepEvent(state, "StepFinished"),
441
+ ];
371
442
 
372
443
  case "data":
373
444
  applyDataMetadata(state, event);
@@ -375,12 +446,16 @@ export function mapRuntimeStreamEventToAgUiBrowserEvents(
375
446
 
376
447
  case "error":
377
448
  state.sawTerminalError = true;
378
- return [{
379
- event: "RunError",
380
- payload: {
381
- message: typeof event.error === "string" ? event.error : "Agent run failed",
449
+ return [
450
+ ...closeOpenTextEvent(state),
451
+ ...closeOpenReasoningEvent(state),
452
+ {
453
+ event: "RunError",
454
+ payload: {
455
+ message: typeof event.error === "string" ? event.error : "Agent run failed",
456
+ },
382
457
  },
383
- }];
458
+ ];
384
459
 
385
460
  default:
386
461
  return [];
@@ -409,13 +484,8 @@ export function finalizeAgUiBrowserEvents(
409
484
  }
410
485
 
411
486
  const events: AgUiBrowserEncodedEvent[] = [];
412
- if (state.textOpen) {
413
- state.textOpen = false;
414
- events.push({
415
- event: "TextMessageEnd",
416
- payload: { messageId: getMessageId(state, { type: "text-end" }) },
417
- });
418
- }
487
+ events.push(...closeOpenTextEvent(state));
488
+ events.push(...closeOpenReasoningEvent(state));
419
489
 
420
490
  events.push({
421
491
  event: "RunFinished",
@@ -24,6 +24,7 @@ export interface StreamingToolCall {
24
24
  id: string;
25
25
  name: string;
26
26
  arguments: string;
27
+ inputAvailable?: boolean;
27
28
  providerExecuted?: boolean;
28
29
  dynamic?: boolean;
29
30
  }
@@ -157,6 +158,136 @@ export function processStream(
157
158
  ): Promise<void> {
158
159
  return withSpan("agent.runtime.processStream", async () => {
159
160
  let eventCount = 0;
161
+ let textOpen = false;
162
+ let activeReasoningId: string | null = null;
163
+
164
+ const normalizeReasoningId = (part: { id?: string }) =>
165
+ typeof part.id === "string" && part.id.length > 0 ? part.id : "reasoning";
166
+
167
+ const openTextSegment = () => {
168
+ if (textOpen) {
169
+ return;
170
+ }
171
+
172
+ textOpen = true;
173
+ sendSSE(controller, encoder, {
174
+ type: "text-start",
175
+ id: textPartId,
176
+ });
177
+ };
178
+
179
+ const closeTextSegment = () => {
180
+ if (!textOpen) {
181
+ return;
182
+ }
183
+
184
+ textOpen = false;
185
+ sendSSE(controller, encoder, {
186
+ type: "text-end",
187
+ id: textPartId,
188
+ });
189
+ };
190
+
191
+ const openReasoningSegment = (reasoningId: string) => {
192
+ if (activeReasoningId === reasoningId) {
193
+ return;
194
+ }
195
+
196
+ if (activeReasoningId !== null) {
197
+ sendSSE(controller, encoder, {
198
+ type: "reasoning-end",
199
+ id: activeReasoningId,
200
+ });
201
+ }
202
+
203
+ activeReasoningId = reasoningId;
204
+ sendSSE(controller, encoder, {
205
+ type: "reasoning-start",
206
+ id: reasoningId,
207
+ });
208
+ };
209
+
210
+ const closeReasoningSegment = () => {
211
+ if (activeReasoningId === null) {
212
+ return;
213
+ }
214
+
215
+ sendSSE(controller, encoder, {
216
+ type: "reasoning-end",
217
+ id: activeReasoningId,
218
+ });
219
+ activeReasoningId = null;
220
+ };
221
+
222
+ const ensureToolLifecycle = (part: {
223
+ toolCallId: string;
224
+ toolName: string;
225
+ input?: unknown;
226
+ providerExecuted?: boolean;
227
+ dynamic?: boolean;
228
+ }) => {
229
+ const dynamic = part.dynamic ?? isDynamicTool(part.toolName);
230
+ const existing = state.toolCalls.get(part.toolCallId);
231
+
232
+ if (!existing) {
233
+ const normalizedInput = parseToolInputObject(part.input);
234
+ state.toolCalls.set(part.toolCallId, {
235
+ id: part.toolCallId,
236
+ name: part.toolName,
237
+ arguments: normalizeToolInputString(part.input),
238
+ inputAvailable: true,
239
+ ...(part.providerExecuted !== undefined
240
+ ? { providerExecuted: part.providerExecuted }
241
+ : {}),
242
+ ...(dynamic ? { dynamic: true } : {}),
243
+ });
244
+ sendSSE(controller, encoder, {
245
+ type: "tool-input-start",
246
+ toolCallId: part.toolCallId,
247
+ toolName: part.toolName,
248
+ ...(dynamic ? { dynamic: true } : {}),
249
+ });
250
+ sendSSE(controller, encoder, {
251
+ type: "tool-input-available",
252
+ toolCallId: part.toolCallId,
253
+ toolName: part.toolName,
254
+ input: normalizedInput,
255
+ ...(part.providerExecuted !== undefined
256
+ ? { providerExecuted: part.providerExecuted }
257
+ : {}),
258
+ ...(dynamic ? { dynamic: true } : {}),
259
+ });
260
+ return;
261
+ }
262
+
263
+ if (existing.inputAvailable) {
264
+ return;
265
+ }
266
+
267
+ const resolvedArguments = part.input !== undefined
268
+ ? mergeToolCallInput(existing.arguments, normalizeToolInputString(part.input))
269
+ : existing.arguments;
270
+ const resolvedInput = parseToolInputObject(resolvedArguments);
271
+ existing.arguments = resolvedArguments;
272
+ existing.inputAvailable = true;
273
+ if (part.providerExecuted !== undefined) {
274
+ existing.providerExecuted = part.providerExecuted;
275
+ }
276
+ if (dynamic) {
277
+ existing.dynamic = true;
278
+ }
279
+
280
+ sendSSE(controller, encoder, {
281
+ type: "tool-input-available",
282
+ toolCallId: part.toolCallId,
283
+ toolName: existing.name,
284
+ input: resolvedInput,
285
+ ...(existing.providerExecuted !== undefined
286
+ ? { providerExecuted: existing.providerExecuted }
287
+ : {}),
288
+ ...(existing.dynamic ? { dynamic: true } : {}),
289
+ });
290
+ };
160
291
 
161
292
  throwIfAborted(abortSignal);
162
293
 
@@ -172,6 +303,8 @@ export function processStream(
172
303
 
173
304
  switch (typedPart.type) {
174
305
  case "text-delta": {
306
+ closeReasoningSegment();
307
+ openTextSegment();
175
308
  state.accumulatedText += typedPart.text;
176
309
  sendSSE(controller, encoder, {
177
310
  type: "text-delta",
@@ -183,36 +316,40 @@ export function processStream(
183
316
  }
184
317
 
185
318
  case "reasoning-start": {
186
- sendSSE(controller, encoder, {
187
- type: "reasoning-start",
188
- id: typeof typedPart.id === "string" ? typedPart.id : "reasoning",
189
- });
319
+ closeTextSegment();
320
+ openReasoningSegment(normalizeReasoningId(typedPart));
190
321
  break;
191
322
  }
192
323
 
193
324
  case "reasoning-delta": {
325
+ closeTextSegment();
326
+ openReasoningSegment(normalizeReasoningId(typedPart));
194
327
  sendSSE(controller, encoder, {
195
328
  type: "reasoning-delta",
196
- id: typeof typedPart.id === "string" ? typedPart.id : "reasoning",
329
+ id: normalizeReasoningId(typedPart),
197
330
  delta: typeof typedPart.delta === "string" ? typedPart.delta : "",
198
331
  });
199
332
  break;
200
333
  }
201
334
 
202
335
  case "reasoning-end": {
203
- sendSSE(controller, encoder, {
204
- type: "reasoning-end",
205
- id: typeof typedPart.id === "string" ? typedPart.id : "reasoning",
206
- });
336
+ closeTextSegment();
337
+ if (activeReasoningId === null) {
338
+ activeReasoningId = normalizeReasoningId(typedPart);
339
+ }
340
+ closeReasoningSegment();
207
341
  break;
208
342
  }
209
343
 
210
344
  case "tool-input-start": {
345
+ closeTextSegment();
346
+ closeReasoningSegment();
211
347
  const toolId = typedPart.id;
212
348
  state.toolCalls.set(toolId, {
213
349
  id: toolId,
214
350
  name: typedPart.toolName,
215
351
  arguments: "",
352
+ inputAvailable: false,
216
353
  providerExecuted: typedPart.providerExecuted,
217
354
  dynamic: typedPart.dynamic,
218
355
  });
@@ -228,6 +365,7 @@ export function processStream(
228
365
  }
229
366
 
230
367
  case "tool-input-delta": {
368
+ closeReasoningSegment();
231
369
  const toolId = typedPart.id;
232
370
  const tc = state.toolCalls.get(toolId);
233
371
  if (!tc) break;
@@ -242,6 +380,8 @@ export function processStream(
242
380
  }
243
381
 
244
382
  case "tool-call": {
383
+ closeTextSegment();
384
+ closeReasoningSegment();
245
385
  // tool-call fires when the full tool call is available
246
386
  const toolId = typedPart.toolCallId;
247
387
  const inputStr = normalizeToolInputString(typedPart.input);
@@ -251,6 +391,7 @@ export function processStream(
251
391
  id: toolId,
252
392
  name: typedPart.toolName,
253
393
  arguments: resolvedArguments,
394
+ inputAvailable: true,
254
395
  providerExecuted: typedPart.providerExecuted,
255
396
  dynamic: typedPart.dynamic,
256
397
  });
@@ -271,6 +412,15 @@ export function processStream(
271
412
  }
272
413
 
273
414
  case "tool-result": {
415
+ closeTextSegment();
416
+ closeReasoningSegment();
417
+ ensureToolLifecycle({
418
+ toolCallId: typedPart.toolCallId,
419
+ toolName: typedPart.toolName,
420
+ input: typedPart.input,
421
+ providerExecuted: typedPart.providerExecuted,
422
+ dynamic: typedPart.dynamic,
423
+ });
274
424
  const isError = typedPart.isError === true;
275
425
  logProviderToolPart("tool-result", {
276
426
  toolCallId: typedPart.toolCallId,
@@ -328,6 +478,15 @@ export function processStream(
328
478
  }
329
479
 
330
480
  case "tool-error": {
481
+ closeTextSegment();
482
+ closeReasoningSegment();
483
+ ensureToolLifecycle({
484
+ toolCallId: typedPart.toolCallId,
485
+ toolName: typedPart.toolName,
486
+ input: typedPart.input,
487
+ providerExecuted: typedPart.providerExecuted,
488
+ dynamic: typedPart.dynamic,
489
+ });
331
490
  logProviderToolPart("tool-error", {
332
491
  toolCallId: typedPart.toolCallId,
333
492
  toolName: typedPart.toolName,
@@ -358,6 +517,8 @@ export function processStream(
358
517
  }
359
518
 
360
519
  case "finish": {
520
+ closeTextSegment();
521
+ closeReasoningSegment();
361
522
  state.finishReason = typedPart.finishReason ?? null;
362
523
  if (typedPart.totalUsage) {
363
524
  const input = typedPart.totalUsage.inputTokens ?? 0;
@@ -373,6 +534,8 @@ export function processStream(
373
534
  }
374
535
 
375
536
  case "error": {
537
+ closeTextSegment();
538
+ closeReasoningSegment();
376
539
  logger.warn("Runtime stream error:", typedPart.error);
377
540
  sendSSE(controller, encoder, {
378
541
  type: "error",
@@ -488,8 +488,6 @@ export class AgentRuntime {
488
488
  model: effectiveModel,
489
489
  },
490
490
  });
491
- sendSSE(controller, encoder, { type: "text-start", id: textPartId });
492
-
493
491
  const response = await this.executeAgentLoopStreaming(
494
492
  systemPrompt,
495
493
  memoryMessages,
@@ -510,7 +508,6 @@ export class AgentRuntime {
510
508
  callbacks?.onFinish?.(response);
511
509
  throwIfAborted(streamAbortSignal);
512
510
 
513
- sendSSE(controller, encoder, { type: "text-end", id: textPartId });
514
511
  sendSSE(controller, encoder, { type: "message-finish" });
515
512
  closeSSEStream(controller);
516
513
  } catch (error) {
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.199";
3
+ export const VERSION = "0.1.201";