@runtypelabs/persona 3.35.0 → 3.37.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/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/index.cjs +46 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.global.js +33 -33
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +47 -47
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/smart-dom-reader.d.cts +10 -0
- package/dist/smart-dom-reader.d.ts +10 -0
- package/dist/theme-editor-preview.cjs +48 -48
- package/dist/theme-editor-preview.d.cts +10 -0
- package/dist/theme-editor-preview.d.ts +10 -0
- package/dist/theme-editor-preview.js +49 -49
- package/dist/theme-editor.d.cts +10 -0
- package/dist/theme-editor.d.ts +10 -0
- package/package.json +1 -1
- package/src/client.test.ts +142 -0
- package/src/client.ts +63 -8
- package/src/generated/runtype-openapi-contract.ts +1 -0
- package/src/install.ts +4 -0
- package/src/types.ts +10 -0
- package/src/utils/__fixtures__/unified-translator.oracle.ts +669 -0
- package/src/utils/unified-event-bridge.test.ts +263 -0
- package/src/utils/unified-event-bridge.ts +431 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { UnifiedToLegacyBridge, isUnifiedLifecycleStart, type LegacyEvent } from "./unified-event-bridge";
|
|
3
|
+
import { createUnifiedEventWrite } from "./__fixtures__/unified-translator.oracle";
|
|
4
|
+
|
|
5
|
+
type Frame = Record<string, unknown> & { type: string };
|
|
6
|
+
|
|
7
|
+
/** legacy frames → api oracle → parsed unified frames */
|
|
8
|
+
function legacyToUnified(legacy: Frame[], executionId?: string): Frame[] {
|
|
9
|
+
const unified: Frame[] = [];
|
|
10
|
+
const sink = (chunk: string) => {
|
|
11
|
+
for (const part of chunk.split("\n\n")) {
|
|
12
|
+
const dataLine = part.split("\n").find((l) => l.startsWith("data: "));
|
|
13
|
+
if (!dataLine) continue;
|
|
14
|
+
const json = dataLine.slice(6);
|
|
15
|
+
if (json.trim() === "[DONE]") continue;
|
|
16
|
+
try {
|
|
17
|
+
unified.push(JSON.parse(json) as Frame);
|
|
18
|
+
} catch {
|
|
19
|
+
/* ignore */
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const write = createUnifiedEventWrite(sink, executionId ? { executionId } : undefined);
|
|
24
|
+
for (const f of legacy) write(`data: ${JSON.stringify(f)}\n\n`);
|
|
25
|
+
return unified;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** unified frames → widget bridge → legacy' events */
|
|
29
|
+
function unifiedToLegacy(unified: Frame[], executionId?: string): LegacyEvent[] {
|
|
30
|
+
const bridge = new UnifiedToLegacyBridge(executionId ? { executionId } : undefined);
|
|
31
|
+
const out: LegacyEvent[] = [];
|
|
32
|
+
for (const f of unified) out.push(...bridge.push(f.type, f));
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** full round-trip: legacy → unified → legacy' */
|
|
37
|
+
function roundTrip(legacy: Frame[], executionId = "exec_test"): { unified: Frame[]; legacy2: LegacyEvent[] } {
|
|
38
|
+
const unified = legacyToUnified(legacy, executionId);
|
|
39
|
+
return { unified, legacy2: unifiedToLegacy(unified, executionId) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const byType = (evs: LegacyEvent[], t: string) => evs.filter((e) => e.payloadType === t);
|
|
43
|
+
const field = (e: LegacyEvent | undefined, k: string): unknown => e?.payload[k];
|
|
44
|
+
const textOf = (evs: LegacyEvent[]) =>
|
|
45
|
+
byType(evs, "agent_turn_delta")
|
|
46
|
+
.filter((e) => e.payload.contentType === "text")
|
|
47
|
+
.map((e) => String(e.payload.delta ?? ""))
|
|
48
|
+
.join("");
|
|
49
|
+
const thinkingOf = (evs: LegacyEvent[]) =>
|
|
50
|
+
byType(evs, "agent_turn_delta")
|
|
51
|
+
.filter((e) => e.payload.contentType === "thinking")
|
|
52
|
+
.map((e) => String(e.payload.delta ?? ""))
|
|
53
|
+
.join("");
|
|
54
|
+
|
|
55
|
+
describe("UnifiedToLegacyBridge — round-trip against the api oracle", () => {
|
|
56
|
+
it("agent text stream preserves text, turnId, and lifecycle", () => {
|
|
57
|
+
const { unified, legacy2 } = roundTrip([
|
|
58
|
+
{ type: "agent_start", executionId: "exec_test", agentId: "virtual", startedAt: "t0" },
|
|
59
|
+
{ type: "agent_turn_start", turnId: "turn_1", iteration: 1 },
|
|
60
|
+
{ type: "agent_turn_delta", executionId: "exec_test", iteration: 1, turnId: "turn_1", contentType: "text", delta: "Hello " },
|
|
61
|
+
{ type: "agent_turn_delta", executionId: "exec_test", iteration: 1, turnId: "turn_1", contentType: "text", delta: "world" },
|
|
62
|
+
{ type: "agent_turn_complete", turnId: "turn_1", iteration: 1, stopReason: "end_turn", completedAt: "t1" },
|
|
63
|
+
{ type: "agent_complete", executionId: "exec_test", success: true, stopReason: "end_turn", completedAt: "t1" },
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
// the oracle really did produce the neutral vocabulary
|
|
67
|
+
expect(unified[0].type).toBe("execution_start");
|
|
68
|
+
expect(unified.some((f) => f.type === "text_delta")).toBe(true);
|
|
69
|
+
|
|
70
|
+
expect(byType(legacy2, "agent_start")).toHaveLength(1);
|
|
71
|
+
expect(textOf(legacy2)).toBe("Hello world");
|
|
72
|
+
// turnId recovered from the open turn (unified decouples block-id from turn)
|
|
73
|
+
expect(field(byType(legacy2, "agent_turn_delta")[0], "turnId")).toBe("turn_1");
|
|
74
|
+
expect(field(byType(legacy2, "agent_turn_complete")[0], "stopReason")).toBe("end_turn");
|
|
75
|
+
const complete = byType(legacy2, "agent_complete")[0];
|
|
76
|
+
expect(field(complete, "success")).toBe(true);
|
|
77
|
+
expect(field(complete, "executionId")).toBe("exec_test");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("agent text WITHOUT an explicit turn still renders (block-id fallback)", () => {
|
|
81
|
+
// the verified minimal adapter omits agent_turn_start
|
|
82
|
+
const { legacy2 } = roundTrip([
|
|
83
|
+
{ type: "agent_start", executionId: "exec_test", agentId: "virtual" },
|
|
84
|
+
{ type: "agent_turn_delta", executionId: "exec_test", iteration: 1, turnId: "turn_x", contentType: "text", delta: "hi" },
|
|
85
|
+
{ type: "agent_complete", executionId: "exec_test", success: true },
|
|
86
|
+
]);
|
|
87
|
+
expect(textOf(legacy2)).toBe("hi");
|
|
88
|
+
// no turn_start → bridge falls back to the unified text block id (stable, non-empty)
|
|
89
|
+
expect(field(byType(legacy2, "agent_turn_delta")[0], "turnId")).toBeTruthy();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("agent thinking round-trips through the reasoning channel", () => {
|
|
93
|
+
const { unified, legacy2 } = roundTrip([
|
|
94
|
+
{ type: "agent_start", executionId: "exec_test", agentId: "virtual" },
|
|
95
|
+
{ type: "agent_turn_start", turnId: "turn_1", iteration: 1 },
|
|
96
|
+
{ type: "agent_turn_delta", executionId: "exec_test", iteration: 1, turnId: "turn_1", contentType: "thinking", delta: "let me think" },
|
|
97
|
+
{ type: "agent_turn_complete", turnId: "turn_1", iteration: 1 },
|
|
98
|
+
{ type: "agent_complete", executionId: "exec_test", success: true },
|
|
99
|
+
]);
|
|
100
|
+
expect(unified.some((f) => f.type === "reasoning_delta")).toBe(true);
|
|
101
|
+
expect(thinkingOf(legacy2)).toBe("let me think");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("agent tool call preserves name, args, result, and toolCallId", () => {
|
|
105
|
+
const { unified, legacy2 } = roundTrip([
|
|
106
|
+
{ type: "agent_start", executionId: "exec_test", agentId: "virtual" },
|
|
107
|
+
{ type: "agent_tool_start", executionId: "exec_test", iteration: 1, toolCallId: "tc1", toolName: "search", parameters: { q: "shoes" } },
|
|
108
|
+
{ type: "agent_tool_delta", toolCallId: "tc1", delta: "partial" },
|
|
109
|
+
{ type: "agent_tool_complete", toolCallId: "tc1", result: { hits: 3 }, executionTime: 42 },
|
|
110
|
+
{ type: "agent_complete", executionId: "exec_test", success: true },
|
|
111
|
+
]);
|
|
112
|
+
expect(unified.some((f) => f.type === "tool_start")).toBe(true);
|
|
113
|
+
|
|
114
|
+
const start = byType(legacy2, "agent_tool_start")[0];
|
|
115
|
+
expect(field(start, "toolCallId")).toBe("tc1");
|
|
116
|
+
expect(field(start, "toolName")).toBe("search");
|
|
117
|
+
expect(field(start, "parameters")).toEqual({ q: "shoes" });
|
|
118
|
+
|
|
119
|
+
expect(field(byType(legacy2, "agent_tool_delta")[0], "delta")).toBe("partial");
|
|
120
|
+
|
|
121
|
+
const done = byType(legacy2, "agent_tool_complete")[0];
|
|
122
|
+
expect(field(done, "result")).toEqual({ hits: 3 });
|
|
123
|
+
expect(field(done, "executionTime")).toBe(42);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("WebMCP agent_await maps onto the 3.35.0 step_await local-tool path with the webmcp: prefix", () => {
|
|
127
|
+
const { unified, legacy2 } = roundTrip([
|
|
128
|
+
{ type: "agent_start", executionId: "exec_test", agentId: "virtual" },
|
|
129
|
+
{
|
|
130
|
+
type: "agent_await",
|
|
131
|
+
executionId: "exec_test",
|
|
132
|
+
toolId: "runtime_add_to_cart_1",
|
|
133
|
+
toolName: "add_to_cart", // BARE on the wire
|
|
134
|
+
origin: "webmcp",
|
|
135
|
+
toolCallId: "toolu_123",
|
|
136
|
+
parameters: { sku: "SHOE-003" },
|
|
137
|
+
awaitedAt: "t2",
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
// the oracle collapses agent_await → the neutral `await`
|
|
141
|
+
expect(unified.some((f) => f.type === "await")).toBe(true);
|
|
142
|
+
expect(unified.some((f) => f.type === "agent_await")).toBe(false);
|
|
143
|
+
|
|
144
|
+
const awaitEv = byType(legacy2, "step_await")[0];
|
|
145
|
+
expect(awaitEv).toBeDefined();
|
|
146
|
+
expect(field(awaitEv, "awaitReason")).toBe("local_tool_required");
|
|
147
|
+
expect(field(awaitEv, "toolName")).toBe("webmcp:add_to_cart"); // prefix synthesized by the bridge
|
|
148
|
+
expect(field(awaitEv, "toolCallId")).toBe("toolu_123");
|
|
149
|
+
expect(field(awaitEv, "parameters")).toEqual({ sku: "SHOE-003" });
|
|
150
|
+
expect(field(awaitEv, "executionId")).toBe("exec_test");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("tool-produced media round-trips to a single agent_media", () => {
|
|
154
|
+
const { unified, legacy2 } = roundTrip([
|
|
155
|
+
{ type: "agent_start", executionId: "exec_test", agentId: "virtual" },
|
|
156
|
+
{
|
|
157
|
+
type: "agent_media",
|
|
158
|
+
executionId: "exec_test",
|
|
159
|
+
toolCallId: "tc1",
|
|
160
|
+
media: [{ type: "image-url", url: "https://x/img.png", mediaType: "image/png" }],
|
|
161
|
+
},
|
|
162
|
+
{ type: "agent_complete", executionId: "exec_test", success: true },
|
|
163
|
+
]);
|
|
164
|
+
// oracle expands to the media triad
|
|
165
|
+
expect(unified.filter((f) => f.type.startsWith("media_"))).toHaveLength(3);
|
|
166
|
+
|
|
167
|
+
const media = byType(legacy2, "agent_media");
|
|
168
|
+
expect(media).toHaveLength(1);
|
|
169
|
+
const parts = field(media[0], "media") as Array<Record<string, unknown>>;
|
|
170
|
+
expect(parts[0].type).toBe("image-url");
|
|
171
|
+
expect(parts[0].url).toBe("https://x/img.png");
|
|
172
|
+
expect(parts[0].mediaType).toBe("image/png");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("approval round-trips", () => {
|
|
176
|
+
const { legacy2 } = roundTrip([
|
|
177
|
+
{ type: "agent_start", executionId: "exec_test", agentId: "virtual" },
|
|
178
|
+
{ type: "agent_approval_start", approvalId: "ap1", toolName: "delete_file", toolType: "builtin", description: "Delete?", parameters: { path: "/x" } },
|
|
179
|
+
{ type: "agent_approval_complete", approvalId: "ap1", decision: "approved" },
|
|
180
|
+
]);
|
|
181
|
+
const start = byType(legacy2, "agent_approval_start")[0];
|
|
182
|
+
expect(field(start, "approvalId")).toBe("ap1");
|
|
183
|
+
expect(field(start, "toolName")).toBe("delete_file");
|
|
184
|
+
expect(field(byType(legacy2, "agent_approval_complete")[0], "decision")).toBe("approved");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("artifacts round-trip 1:1", () => {
|
|
188
|
+
const { legacy2 } = roundTrip([
|
|
189
|
+
{ type: "agent_start", executionId: "exec_test", agentId: "virtual" },
|
|
190
|
+
{ type: "artifact_start", id: "a1", artifactType: "markdown", title: "Doc" },
|
|
191
|
+
{ type: "artifact_delta", id: "a1", delta: "# Hello" },
|
|
192
|
+
{ type: "artifact_complete", id: "a1" },
|
|
193
|
+
]);
|
|
194
|
+
expect(field(byType(legacy2, "artifact_start")[0], "title")).toBe("Doc");
|
|
195
|
+
expect(field(byType(legacy2, "artifact_delta")[0], "delta")).toBe("# Hello");
|
|
196
|
+
expect(byType(legacy2, "artifact_complete")).toHaveLength(1);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("UnifiedToLegacyBridge — unit: kind routing, drops, and error mapping", () => {
|
|
201
|
+
it("flow-kind text routes to step_delta (not agent_turn_delta)", () => {
|
|
202
|
+
const b = new UnifiedToLegacyBridge();
|
|
203
|
+
b.push("execution_start", { kind: "flow", executionId: "exec_test" });
|
|
204
|
+
b.push("step_start", { id: "step_1", name: "Generate" });
|
|
205
|
+
const evs = b.push("text_delta", { id: "text_1", delta: "flow text" });
|
|
206
|
+
expect(evs).toHaveLength(1);
|
|
207
|
+
expect(evs[0].payloadType).toBe("step_delta");
|
|
208
|
+
expect(evs[0].payload.id).toBe("step_1"); // recovered from the open step
|
|
209
|
+
expect(evs[0].payload.text).toBe("flow text");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("drops events with no legacy renderer", () => {
|
|
213
|
+
const b = new UnifiedToLegacyBridge();
|
|
214
|
+
b.push("execution_start", { kind: "agent", executionId: "exec_test" });
|
|
215
|
+
expect(b.push("source", { url: "https://x" })).toHaveLength(0);
|
|
216
|
+
expect(b.push("custom", { name: "runtype.fallback", value: {} })).toHaveLength(0);
|
|
217
|
+
expect(b.push("step_skip", { id: "s2" })).toHaveLength(0);
|
|
218
|
+
expect(b.push("tool_input_complete", { toolCallId: "tc1", parameters: {} })).toHaveLength(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("unified `error` (recoverable) → agent_error{recoverable:true}, never the terminal `error`", () => {
|
|
222
|
+
const b = new UnifiedToLegacyBridge();
|
|
223
|
+
b.push("execution_start", { kind: "agent", executionId: "exec_test" });
|
|
224
|
+
const evs = b.push("error", { recoverable: true, error: { message: "transient" } });
|
|
225
|
+
expect(evs[0].payloadType).toBe("agent_error");
|
|
226
|
+
expect(evs[0].payload.recoverable).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("execution_error{kind:agent} → terminal agent_error{recoverable:false}", () => {
|
|
230
|
+
const b = new UnifiedToLegacyBridge();
|
|
231
|
+
b.push("execution_start", { kind: "agent", executionId: "exec_test" });
|
|
232
|
+
const evs = b.push("execution_error", { kind: "agent", error: { message: "boom" } });
|
|
233
|
+
expect(evs[0].payloadType).toBe("agent_error");
|
|
234
|
+
expect(evs[0].payload.recoverable).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("loop-scoped reasoning_complete folds into agent_reflection", () => {
|
|
238
|
+
const b = new UnifiedToLegacyBridge();
|
|
239
|
+
b.push("execution_start", { kind: "agent", executionId: "exec_test" });
|
|
240
|
+
b.push("reasoning_start", { id: "reason_1", scope: "loop" });
|
|
241
|
+
const evs = b.push("reasoning_complete", { id: "reason_1", text: "I should retry", scope: "loop" });
|
|
242
|
+
expect(evs[0].payloadType).toBe("agent_reflection");
|
|
243
|
+
expect(evs[0].payload.reflection).toBe("I should retry");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("ask_user_question (non-webmcp local tool) await keeps its bare name", () => {
|
|
247
|
+
const b = new UnifiedToLegacyBridge();
|
|
248
|
+
b.push("execution_start", { kind: "agent", executionId: "exec_test" });
|
|
249
|
+
const evs = b.push("await", { toolName: "ask_user_question", toolCallId: "tc9", parameters: {} });
|
|
250
|
+
expect(evs[0].payloadType).toBe("step_await");
|
|
251
|
+
expect(evs[0].payload.awaitReason).toBe("local_tool_required");
|
|
252
|
+
expect(evs[0].payload.toolName).toBe("ask_user_question"); // no webmcp prefix
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("isUnifiedLifecycleStart", () => {
|
|
257
|
+
it("identifies the unified vocabulary from the first lifecycle frame", () => {
|
|
258
|
+
expect(isUnifiedLifecycleStart("execution_start")).toBe(true);
|
|
259
|
+
expect(isUnifiedLifecycleStart("agent_start")).toBe(false);
|
|
260
|
+
expect(isUnifiedLifecycleStart("flow_start")).toBe(false);
|
|
261
|
+
expect(isUnifiedLifecycleStart("step_start")).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
});
|