@kuralle-syrinx/mastra 4.2.0 → 4.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/package.json +3 -3
- package/src/from-mastra.test.ts +0 -283
- package/tsconfig.json +0 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kuralle-syrinx/mastra",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Mastra agent bridge for Syrinx — run a Mastra Agent as the voice pipeline's reasoner",
|
|
6
6
|
"keywords": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"main": "./src/index.ts",
|
|
25
25
|
"types": "./src/index.ts",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@kuralle-syrinx/core": "4.
|
|
27
|
+
"@kuralle-syrinx/core": "4.4.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@mastra/core": ">=1.41.0 <2"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@mastra/core": "^1.41.0",
|
|
34
34
|
"typescript": "^5.7.0",
|
|
35
|
-
"vitest": "^2.
|
|
35
|
+
"vitest": "^3.2.6"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"typecheck": "tsc --noEmit",
|
package/src/from-mastra.test.ts
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
import type { Reasoner, ReasonerTurn, ReasoningPart } from "@kuralle-syrinx/core";
|
|
5
|
-
import { fromMastraAgent, type MastraAgentLike, type MastraChunk } from "./from-mastra.js";
|
|
6
|
-
|
|
7
|
-
function baseTurn(): ReasonerTurn {
|
|
8
|
-
return {
|
|
9
|
-
userText: "Hi",
|
|
10
|
-
messages: [{ role: "system", content: "test" }],
|
|
11
|
-
signal: new AbortController().signal,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function textDelta(text: string): MastraChunk {
|
|
16
|
-
return { type: "text-delta", payload: { text } };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function toolCall(toolCallId: string, toolName: string, args: Record<string, unknown>): MastraChunk {
|
|
20
|
-
return { type: "tool-call", payload: { toolCallId, toolName, args } };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function toolResult(toolCallId: string, toolName: string, result: unknown): MastraChunk {
|
|
24
|
-
return { type: "tool-result", payload: { toolCallId, toolName, result } };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function errorChunk(error: unknown): MastraChunk {
|
|
28
|
-
return { type: "error", payload: { error } };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function finish(reason: string): MastraChunk {
|
|
32
|
-
return { type: "finish", payload: { stepResult: { reason } } };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function chunksToStream(chunks: MastraChunk[]): ReadableStream<MastraChunk> {
|
|
36
|
-
return new ReadableStream({
|
|
37
|
-
start(controller) {
|
|
38
|
-
for (const ch of chunks) controller.enqueue(ch);
|
|
39
|
-
controller.close();
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function delayedStream(first: MastraChunk): { stream: ReadableStream<MastraChunk>; release: () => void } {
|
|
45
|
-
let release!: () => void;
|
|
46
|
-
const gate = new Promise<void>((resolve) => {
|
|
47
|
-
release = resolve;
|
|
48
|
-
});
|
|
49
|
-
const stream = new ReadableStream<MastraChunk>({
|
|
50
|
-
async start(controller) {
|
|
51
|
-
controller.enqueue(first);
|
|
52
|
-
await gate;
|
|
53
|
-
controller.close();
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
return { stream, release };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function fakeAgent(
|
|
60
|
-
chunks: MastraChunk[] | ReadableStream<MastraChunk>,
|
|
61
|
-
opts?: {
|
|
62
|
-
runId?: string;
|
|
63
|
-
resume?: {
|
|
64
|
-
chunks: MastraChunk[] | ReadableStream<MastraChunk>;
|
|
65
|
-
runId?: string;
|
|
66
|
-
};
|
|
67
|
-
spy?: {
|
|
68
|
-
streamCalled?: boolean;
|
|
69
|
-
resumeCalled?: boolean;
|
|
70
|
-
resumeData?: unknown;
|
|
71
|
-
resumeOptions?: { runId: string; toolCallId?: string; abortSignal?: AbortSignal };
|
|
72
|
-
};
|
|
73
|
-
},
|
|
74
|
-
): MastraAgentLike {
|
|
75
|
-
const spy = opts?.spy;
|
|
76
|
-
return {
|
|
77
|
-
async stream() {
|
|
78
|
-
if (spy) spy.streamCalled = true;
|
|
79
|
-
return {
|
|
80
|
-
runId: opts?.runId ?? "r1",
|
|
81
|
-
fullStream: chunks instanceof ReadableStream ? chunks : chunksToStream(chunks),
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
async resumeStream(resumeData, options) {
|
|
85
|
-
if (spy) {
|
|
86
|
-
spy.resumeCalled = true;
|
|
87
|
-
spy.resumeData = resumeData;
|
|
88
|
-
spy.resumeOptions = options;
|
|
89
|
-
}
|
|
90
|
-
const resumeChunks = opts?.resume?.chunks ?? [];
|
|
91
|
-
return {
|
|
92
|
-
runId: opts?.resume?.runId ?? opts?.runId ?? "r1",
|
|
93
|
-
fullStream:
|
|
94
|
-
resumeChunks instanceof ReadableStream ? resumeChunks : chunksToStream(resumeChunks),
|
|
95
|
-
};
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function collectParts(reasoner: Reasoner, turn: ReasonerTurn): Promise<ReasoningPart[]> {
|
|
101
|
-
const parts: ReasoningPart[] = [];
|
|
102
|
-
for await (const part of reasoner.stream(turn)) {
|
|
103
|
-
parts.push(part);
|
|
104
|
-
}
|
|
105
|
-
return parts;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
describe("fromMastraAgent", () => {
|
|
109
|
-
it("maps happy path: deltas, tool-call, tool-result, finish:stop", async () => {
|
|
110
|
-
const reasoner = fromMastraAgent(
|
|
111
|
-
fakeAgent([
|
|
112
|
-
textDelta("Hello "),
|
|
113
|
-
textDelta("world."),
|
|
114
|
-
toolCall("tc-1", "get_weather", { city: "NYC" }),
|
|
115
|
-
toolResult("tc-1", "get_weather", { temp: 72 }),
|
|
116
|
-
finish("stop"),
|
|
117
|
-
]),
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const parts = await collectParts(reasoner, baseTurn());
|
|
121
|
-
|
|
122
|
-
expect(parts).toEqual([
|
|
123
|
-
{ type: "text-delta", text: "Hello " },
|
|
124
|
-
{ type: "text-delta", text: "world." },
|
|
125
|
-
{
|
|
126
|
-
type: "tool-call",
|
|
127
|
-
toolId: "tc-1",
|
|
128
|
-
toolName: "get_weather",
|
|
129
|
-
args: { city: "NYC" },
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
type: "tool-result",
|
|
133
|
-
toolId: "tc-1",
|
|
134
|
-
toolName: "get_weather",
|
|
135
|
-
result: JSON.stringify({ temp: 72 }),
|
|
136
|
-
},
|
|
137
|
-
{ type: "finish", reason: "stop", text: "Hello world." },
|
|
138
|
-
]);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("maps error chunk to terminal error", async () => {
|
|
142
|
-
const reasoner = fromMastraAgent(
|
|
143
|
-
fakeAgent([textDelta("partial"), errorChunk(new Error("provider failed"))]),
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
const parts = await collectParts(reasoner, baseTurn());
|
|
147
|
-
|
|
148
|
-
expect(parts).toHaveLength(2);
|
|
149
|
-
expect(parts[0]).toEqual({ type: "text-delta", text: "partial" });
|
|
150
|
-
expect(parts[1]?.type).toBe("error");
|
|
151
|
-
if (parts[1]?.type === "error") {
|
|
152
|
-
expect(parts[1].cause.message).toBe("provider failed");
|
|
153
|
-
expect(parts[1].recoverable).toBe(false);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("maps abnormal finish (content-filter) to terminal error", async () => {
|
|
158
|
-
const reasoner = fromMastraAgent(fakeAgent([finish("content-filter")]));
|
|
159
|
-
|
|
160
|
-
const parts = await collectParts(reasoner, baseTurn());
|
|
161
|
-
|
|
162
|
-
expect(parts).toHaveLength(1);
|
|
163
|
-
expect(parts[0]?.type).toBe("error");
|
|
164
|
-
if (parts[0]?.type === "error") {
|
|
165
|
-
expect(parts[0].cause.message).toBe(
|
|
166
|
-
"Mastra provider did not complete normally: content-filter",
|
|
167
|
-
);
|
|
168
|
-
expect(parts[0].recoverable).toBe(false);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("maps finish(length) to finish with accumulated text", async () => {
|
|
173
|
-
const reasoner = fromMastraAgent(fakeAgent([textDelta("truncated"), finish("length")]));
|
|
174
|
-
|
|
175
|
-
const parts = await collectParts(reasoner, baseTurn());
|
|
176
|
-
|
|
177
|
-
expect(parts).toEqual([
|
|
178
|
-
{ type: "text-delta", text: "truncated" },
|
|
179
|
-
{ type: "finish", reason: "length", text: "truncated" },
|
|
180
|
-
]);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("drops reasoning-delta and workflow chunks", async () => {
|
|
184
|
-
const reasoner = fromMastraAgent(
|
|
185
|
-
fakeAgent([
|
|
186
|
-
{ type: "reasoning-delta", payload: { text: "thinking..." } },
|
|
187
|
-
{ type: "workflow-step", payload: { step: 1 } },
|
|
188
|
-
textDelta("answer"),
|
|
189
|
-
finish("stop"),
|
|
190
|
-
]),
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const parts = await collectParts(reasoner, baseTurn());
|
|
194
|
-
|
|
195
|
-
expect(parts).toEqual([
|
|
196
|
-
{ type: "text-delta", text: "answer" },
|
|
197
|
-
{ type: "finish", reason: "stop", text: "answer" },
|
|
198
|
-
]);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("yields first text-delta before source stream completes (no buffering)", async () => {
|
|
202
|
-
const { stream, release } = delayedStream(textDelta("immediate"));
|
|
203
|
-
const reasoner = fromMastraAgent(fakeAgent(stream));
|
|
204
|
-
|
|
205
|
-
const iterator = reasoner.stream(baseTurn())[Symbol.asyncIterator]();
|
|
206
|
-
const first = await iterator.next();
|
|
207
|
-
|
|
208
|
-
expect(first.done).toBe(false);
|
|
209
|
-
expect(first.value).toEqual({ type: "text-delta", text: "immediate" });
|
|
210
|
-
release();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("returns without yielding when turn.signal is already aborted (barge-in)", async () => {
|
|
214
|
-
const controller = new AbortController();
|
|
215
|
-
controller.abort();
|
|
216
|
-
const turn: ReasonerTurn = { ...baseTurn(), signal: controller.signal };
|
|
217
|
-
|
|
218
|
-
const reasoner = fromMastraAgent(
|
|
219
|
-
fakeAgent([textDelta("should not appear"), finish("stop")]),
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
const parts = await collectParts(reasoner, turn);
|
|
223
|
-
|
|
224
|
-
expect(parts).toEqual([]);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("maps tool-call-suspended to terminal suspended part", async () => {
|
|
228
|
-
const reasoner = fromMastraAgent(
|
|
229
|
-
fakeAgent(
|
|
230
|
-
[
|
|
231
|
-
{
|
|
232
|
-
type: "tool-call-suspended",
|
|
233
|
-
payload: { suspendPayload: { message: "Approve the refund?" } },
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
{ runId: "run-1" },
|
|
237
|
-
),
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
const parts = await collectParts(reasoner, baseTurn());
|
|
241
|
-
|
|
242
|
-
expect(parts).toEqual([
|
|
243
|
-
{
|
|
244
|
-
type: "suspended",
|
|
245
|
-
runId: "run-1",
|
|
246
|
-
prompt: "Approve the refund?",
|
|
247
|
-
payload: { message: "Approve the refund?" },
|
|
248
|
-
},
|
|
249
|
-
]);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it("resume turn calls resumeStream instead of stream", async () => {
|
|
253
|
-
const spy = {
|
|
254
|
-
streamCalled: false,
|
|
255
|
-
resumeCalled: false,
|
|
256
|
-
resumeData: undefined as unknown,
|
|
257
|
-
resumeOptions: undefined as
|
|
258
|
-
| { runId: string; toolCallId?: string; abortSignal?: AbortSignal }
|
|
259
|
-
| undefined,
|
|
260
|
-
};
|
|
261
|
-
const reasoner = fromMastraAgent(
|
|
262
|
-
fakeAgent([], {
|
|
263
|
-
resume: { chunks: [textDelta("Done."), finish("stop")], runId: "run-1" },
|
|
264
|
-
spy,
|
|
265
|
-
}),
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
const turn: ReasonerTurn = {
|
|
269
|
-
...baseTurn(),
|
|
270
|
-
resume: { runId: "run-1", data: { approved: true } },
|
|
271
|
-
};
|
|
272
|
-
const parts = await collectParts(reasoner, turn);
|
|
273
|
-
|
|
274
|
-
expect(spy.streamCalled).toBe(false);
|
|
275
|
-
expect(spy.resumeCalled).toBe(true);
|
|
276
|
-
expect(spy.resumeData).toEqual({ approved: true });
|
|
277
|
-
expect(spy.resumeOptions).toEqual({ runId: "run-1", abortSignal: turn.signal });
|
|
278
|
-
expect(parts).toEqual([
|
|
279
|
-
{ type: "text-delta", text: "Done." },
|
|
280
|
-
{ type: "finish", reason: "stop", text: "Done." },
|
|
281
|
-
]);
|
|
282
|
-
});
|
|
283
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"types": ["vitest/globals"],
|
|
10
|
-
"noEmit": true
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*.ts"]
|
|
13
|
-
}
|