Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions integrations/mastra/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@
},
"dependencies": {
"@ag-ui/a2ui-toolkit": "workspace:*",
"@ai-sdk/ui-utils": "^1.1.19",
"fast-json-patch": "^3.1.1",
"rxjs": "7.8.1",
"zod": "^3.25.76"
"zod": "^3.25.76 || ^4.0.0"
},
"peerDependencies": {
"@ag-ui/core": ">=1.0.0",
Expand Down
6 changes: 5 additions & 1 deletion integrations/mastra/typescript/src/mastra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ import type { Agent as LocalMastraAgent } from "@mastra/core/agent";
import { RequestContext } from "@mastra/core/request-context";
import { randomUUID } from "@ag-ui/client";
import jsonpatch from "fast-json-patch";
import { parsePartialJson } from "@ai-sdk/ui-utils";
// Vendored synchronous copy of @ai-sdk/ui-utils' parsePartialJson, as Mastra did in
// https://github.com/mastra-ai/mastra/pull/27297. Fixes
// https://github.com/ag-ui-protocol/ag-ui/issues/3096 and
// https://github.com/ag-ui-protocol/ag-ui/issues/2874.
import { parsePartialJson } from "./utils/ai-sdk-v4";
import { Observable, type Subscriber } from "rxjs";
import { MastraClient } from "@mastra/client-js";
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { parsePartialJson } from ".";
import { fixJson } from "./fix-json";

// The vendored parser replaces @ai-sdk/ui-utils' parsePartialJson (#3096, #2874),
// following Mastra's copy in https://github.com/mastra-ai/mastra/pull/27297.
// The first case is Mastra's own test; the rest pin the shapes the working-memory
// streaming bridge in mastra.ts feeds it.

describe("parsePartialJson (vendored)", () => {
it("reports each parse state (Mastra's test case)", () => {
expect(parsePartialJson(undefined)).toEqual({
value: undefined,
state: "undefined-input",
});
expect(parsePartialJson('{"a":1}')).toEqual({
value: { a: 1 },
state: "successful-parse",
});
expect(parsePartialJson('{"a":[1,"tw')).toEqual({
value: { a: [1, "tw"] },
state: "repaired-parse",
});
expect(parsePartialJson("}{")).toEqual({
value: undefined,
state: "failed-parse",
});
});

it("is synchronous", () => {
expect(parsePartialJson('{"a":1}')).not.toBeInstanceOf(Promise);
});

it("repairs the streamed working-memory envelope and its nested JSON string", () => {
const outer = parsePartialJson(
'{"memory":"{\\"recipe\\":{\\"title\\":\\"Sp',
);
expect(outer.state).toBe("repaired-parse");
const memory = (outer.value as { memory: string }).memory;
expect(parsePartialJson(memory).value).toEqual({
recipe: { title: "Sp" },
});
});

it("repairs dangling keys, literals and numbers", () => {
expect(parsePartialJson('{"memory":').value).toEqual({});
expect(parsePartialJson('{"done":tr').value).toEqual({ done: true });
expect(parsePartialJson('{"a":1,"b":').value).toEqual({ a: 1 });
});
});

describe("fixJson (vendored)", () => {
it("closes open strings, arrays and objects", () => {
expect(JSON.parse(fixJson('{"a":"he'))).toEqual({ a: "he" });
expect(JSON.parse(fixJson('{"a":['))).toEqual({ a: [] });
expect(JSON.parse(fixJson('{"a":"x\\"y'))).toEqual({ a: 'x"y' });
});
});
Loading
Loading