@runtypelabs/persona 3.36.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.
@@ -0,0 +1,669 @@
1
+ /**
2
+ * TEST ORACLE — vendored copy of the runtype-core `createUnifiedEventWrite`
3
+ * translator (the api-side legacy → unified 33-event encoder).
4
+ *
5
+ * NOT shipped. Used only by `unified-event-bridge.test.ts` as the inverse
6
+ * reference: `legacy frames → THIS oracle → unified frames → UnifiedToLegacyBridge
7
+ * → legacy frames'` must be semantically equal to the original. Keeping a real
8
+ * copy here means the round-trip test fails loudly if the api mapping and the
9
+ * widget bridge ever drift.
10
+ *
11
+ * The only edit vs the api source is the removed `getLogger` import (the
12
+ * per-frame error log is a no-op here).
13
+ */
14
+
15
+ type Json = Record<string, unknown>;
16
+
17
+ type ExecutionKind = "agent" | "flow";
18
+
19
+ interface UnifiedEventWriteOptions {
20
+ executionId?: string;
21
+ }
22
+
23
+ const ENVELOPE_KEYS = new Set(["type", "executionId", "seq"]);
24
+
25
+ function omitEnvelope(data: Json): Json {
26
+ const out: Json = {};
27
+ for (const [k, v] of Object.entries(data)) {
28
+ if (!ENVELOPE_KEYS.has(k)) out[k] = v;
29
+ }
30
+ return out;
31
+ }
32
+
33
+ class UnifiedEventTranslator {
34
+ private executionId: string;
35
+ private seq = 0;
36
+ private kind: ExecutionKind = "flow";
37
+ private blockCounter = 0;
38
+ private openText: string | null = null;
39
+ private openReasoning: string | null = null;
40
+
41
+ constructor(
42
+ private readonly sink: (chunk: string) => void,
43
+ options?: UnifiedEventWriteOptions
44
+ ) {
45
+ this.executionId = options?.executionId ?? "";
46
+ }
47
+
48
+ private mint(prefix: string): string {
49
+ this.blockCounter += 1;
50
+ return `${prefix}_${this.blockCounter}`;
51
+ }
52
+
53
+ private out(type: string, payload: Json): void {
54
+ const frame: Json = { type, executionId: this.executionId, seq: this.seq, ...payload };
55
+ this.seq += 1;
56
+ this.sink(`event: ${type}\ndata: ${JSON.stringify(frame)}\n\n`);
57
+ }
58
+
59
+ private emitTextDelta(delta: unknown): void {
60
+ if (delta == null || delta === "") return;
61
+ if (!this.openText) {
62
+ this.openText = this.mint("text");
63
+ this.out("text_start", {
64
+ id: this.openText,
65
+ ...(this.kind === "agent" ? { role: "assistant" } : {}),
66
+ });
67
+ }
68
+ this.out("text_delta", { id: this.openText, delta: String(delta) });
69
+ }
70
+
71
+ private closeText(): void {
72
+ if (!this.openText) return;
73
+ this.out("text_complete", { id: this.openText });
74
+ this.openText = null;
75
+ }
76
+
77
+ private ensureReasoningOpen(scope?: "turn" | "loop"): void {
78
+ if (this.openReasoning) return;
79
+ this.openReasoning = this.mint("reason");
80
+ this.out("reasoning_start", { id: this.openReasoning, ...(scope ? { scope } : {}) });
81
+ }
82
+
83
+ private emitReasoningDelta(delta: unknown): void {
84
+ if (delta == null || delta === "") return;
85
+ this.ensureReasoningOpen();
86
+ this.out("reasoning_delta", { id: this.openReasoning, delta: String(delta) });
87
+ }
88
+
89
+ private closeReasoning(): void {
90
+ if (!this.openReasoning) return;
91
+ this.out("reasoning_complete", { id: this.openReasoning });
92
+ this.openReasoning = null;
93
+ }
94
+
95
+ private closeChannels(): void {
96
+ this.closeText();
97
+ this.closeReasoning();
98
+ }
99
+
100
+ private fallbackInfo(data: Json): Json | undefined {
101
+ const used = data.usedFallback === true;
102
+ const executions = Array.isArray(data.fallbackExecutions) ? data.fallbackExecutions : [];
103
+ if (!used && executions.length === 0) return undefined;
104
+ const attempts = executions.map((entry: unknown, index: number) => {
105
+ const e = (entry ?? {}) as Json;
106
+ return {
107
+ attempt: typeof e.attempt === "number" ? e.attempt : index + 1,
108
+ type: typeof e.fallbackType === "string" ? e.fallbackType : "model",
109
+ ...(e.fallbackModel != null ? { model: e.fallbackModel } : {}),
110
+ success: true,
111
+ };
112
+ });
113
+ return {
114
+ fallback: {
115
+ used,
116
+ ...(data.modelUsed != null
117
+ ? { model: data.modelUsed }
118
+ : data.fallbackModel != null
119
+ ? { model: data.fallbackModel }
120
+ : {}),
121
+ attempts,
122
+ exhausted: false,
123
+ },
124
+ };
125
+ }
126
+
127
+ private handle(type: string, data: Json): void {
128
+ switch (type) {
129
+ case "agent_start":
130
+ this.kind = "agent";
131
+ if (typeof data.executionId === "string") this.executionId = data.executionId;
132
+ this.out("execution_start", {
133
+ kind: "agent",
134
+ startedAt: data.startedAt ?? new Date().toISOString(),
135
+ agentId: data.agentId,
136
+ agentName: data.agentName,
137
+ maxTurns: data.maxTurns,
138
+ config: data.config,
139
+ });
140
+ break;
141
+ case "agent_iteration_start":
142
+ case "agent_iteration_complete":
143
+ break;
144
+ case "agent_turn_start":
145
+ this.closeChannels();
146
+ this.out("turn_start", {
147
+ id: data.turnId,
148
+ iteration: data.iteration,
149
+ turnIndex: data.turnIndex,
150
+ role: data.role ?? "assistant",
151
+ });
152
+ break;
153
+ case "agent_turn_delta": {
154
+ const contentType = data.contentType;
155
+ if (contentType === "thinking") this.emitReasoningDelta(data.delta);
156
+ else if (contentType === "tool_input")
157
+ this.out("tool_input_delta", {
158
+ toolCallId: data.turnId ?? this.mint("toolinput"),
159
+ delta: String(data.delta ?? ""),
160
+ });
161
+ else this.emitTextDelta(data.delta);
162
+ break;
163
+ }
164
+ case "agent_turn_complete":
165
+ this.closeChannels();
166
+ this.out("turn_complete", {
167
+ id: data.turnId,
168
+ iteration: data.iteration,
169
+ role: data.role ?? "assistant",
170
+ content: data.content,
171
+ tokens: data.tokens,
172
+ cost: data.cost,
173
+ stopReason: data.stopReason,
174
+ completedAt: data.completedAt,
175
+ });
176
+ break;
177
+ case "agent_tool_start":
178
+ this.out("tool_start", {
179
+ toolCallId: data.toolCallId,
180
+ toolName: data.toolName,
181
+ toolType: data.toolType ?? "builtin",
182
+ iteration: data.iteration,
183
+ parameters: data.parameters,
184
+ hiddenParameterNames: data.hiddenParameterNames,
185
+ origin: data.origin,
186
+ pageOrigin: data.pageOrigin,
187
+ });
188
+ break;
189
+ case "agent_tool_delta":
190
+ this.out("tool_output_delta", {
191
+ toolCallId: data.toolCallId,
192
+ delta: String(data.delta ?? ""),
193
+ });
194
+ break;
195
+ case "agent_tool_input_delta":
196
+ this.out("tool_input_delta", {
197
+ toolCallId: data.toolCallId,
198
+ delta: String(data.delta ?? ""),
199
+ });
200
+ break;
201
+ case "agent_tool_input_complete":
202
+ this.out("tool_input_complete", {
203
+ toolCallId: data.toolCallId,
204
+ toolName: data.toolName,
205
+ parameters: (data.parameters as Json) ?? {},
206
+ hiddenParameterNames: data.hiddenParameterNames,
207
+ });
208
+ break;
209
+ case "agent_tool_complete":
210
+ this.out("tool_complete", {
211
+ toolCallId: data.toolCallId,
212
+ toolName: data.toolName,
213
+ success: data.success ?? true,
214
+ result: data.result,
215
+ executionTime: data.executionTime,
216
+ iteration: data.iteration,
217
+ });
218
+ break;
219
+ case "agent_media":
220
+ this.emitMedia(data);
221
+ break;
222
+ case "agent_approval_start":
223
+ this.out("approval_start", {
224
+ approvalId: data.approvalId,
225
+ toolCallId: data.toolCallId,
226
+ toolName: data.toolName,
227
+ toolType: data.toolType,
228
+ description: data.description,
229
+ reason: data.reason,
230
+ parameters: data.parameters,
231
+ timeout: data.timeout,
232
+ startedAt: data.startedAt,
233
+ iteration: data.iteration,
234
+ });
235
+ break;
236
+ case "agent_approval_complete":
237
+ this.out("approval_complete", {
238
+ approvalId: data.approvalId,
239
+ decision: data.decision,
240
+ completedAt: data.completedAt,
241
+ resolvedBy: data.resolvedBy,
242
+ });
243
+ break;
244
+ case "agent_await":
245
+ this.out("await", {
246
+ toolId: data.toolId,
247
+ toolCallId: data.toolCallId,
248
+ toolName: data.toolName,
249
+ parameters: data.parameters,
250
+ awaitedAt: data.awaitedAt,
251
+ origin: data.origin,
252
+ pageOrigin: data.pageOrigin,
253
+ });
254
+ break;
255
+ case "agent_reflection": {
256
+ const id = this.mint("reason");
257
+ this.out("reasoning_start", { id, scope: "loop" });
258
+ this.out("reasoning_complete", { id, text: data.reflection, scope: "loop" });
259
+ break;
260
+ }
261
+ case "agent_skill_loaded": {
262
+ const toolCallId = (data.toolCallId as string) ?? this.mint("skill");
263
+ const toolName = `skill:${String(data.skill ?? "unknown")}`;
264
+ this.out("tool_start", {
265
+ toolCallId,
266
+ toolName,
267
+ toolType: "builtin",
268
+ iteration: data.iteration,
269
+ });
270
+ this.out("tool_complete", {
271
+ toolCallId,
272
+ toolName,
273
+ success: true,
274
+ result: {
275
+ kind: "skill_loaded",
276
+ skill: data.skill,
277
+ activatedCapabilities: data.activatedCapabilities ?? [],
278
+ },
279
+ iteration: data.iteration,
280
+ });
281
+ break;
282
+ }
283
+ case "agent_skill_proposed": {
284
+ const toolCallId = (data.toolCallId as string) ?? this.mint("propose");
285
+ this.out("tool_start", {
286
+ toolCallId,
287
+ toolName: "propose_skill",
288
+ toolType: "builtin",
289
+ iteration: data.iteration,
290
+ });
291
+ this.out("tool_complete", {
292
+ toolCallId,
293
+ toolName: "propose_skill",
294
+ success: true,
295
+ result: {
296
+ kind: "skill_proposed",
297
+ skill: data.skill,
298
+ outcome: data.outcome,
299
+ proposalId: data.proposalId,
300
+ },
301
+ iteration: data.iteration,
302
+ });
303
+ break;
304
+ }
305
+ case "agent_complete":
306
+ this.closeChannels();
307
+ if (data.success === false || data.stopReason === "error") {
308
+ this.out("execution_error", {
309
+ kind: "agent",
310
+ error: this.toErrorPayload(data.error ?? "Agent execution failed"),
311
+ completedAt: data.completedAt,
312
+ });
313
+ } else {
314
+ this.out("execution_complete", {
315
+ kind: "agent",
316
+ success: data.success ?? true,
317
+ iterations: data.iterations,
318
+ stopReason: data.stopReason,
319
+ completedAt: data.completedAt,
320
+ durationMs: data.duration,
321
+ finalOutput: data.finalOutput,
322
+ totalCost: data.totalCost,
323
+ totalTokens: data.totalTokens,
324
+ });
325
+ }
326
+ break;
327
+ case "agent_error":
328
+ if (data.recoverable)
329
+ this.out("error", { error: this.toErrorPayload(data.error), recoverable: true });
330
+ else this.out("execution_error", { kind: this.kind, error: this.toErrorPayload(data.error) });
331
+ break;
332
+ case "agent_ping":
333
+ this.out("ping", { timestamp: data.timestamp ?? new Date().toISOString() });
334
+ break;
335
+
336
+ case "flow_start":
337
+ this.kind = "flow";
338
+ if (typeof data.executionId === "string") this.executionId = data.executionId;
339
+ this.out("execution_start", {
340
+ kind: "flow",
341
+ startedAt: data.startedAt ?? new Date().toISOString(),
342
+ flowId: data.flowId,
343
+ flowName: data.flowName,
344
+ totalSteps: data.totalSteps,
345
+ source: data.source,
346
+ });
347
+ break;
348
+ case "flow_complete":
349
+ this.closeChannels();
350
+ this.out("execution_complete", {
351
+ kind: "flow",
352
+ success: data.success ?? true,
353
+ completedAt: data.completedAt,
354
+ durationMs: data.duration ?? data.executionTime,
355
+ finalOutput: data.finalOutput,
356
+ totalSteps: data.totalSteps,
357
+ successfulSteps: data.successfulSteps,
358
+ failedSteps: data.failedSteps,
359
+ });
360
+ break;
361
+ case "flow_error":
362
+ this.closeChannels();
363
+ this.out("execution_error", {
364
+ kind: "flow",
365
+ error: this.toErrorPayload(data.error),
366
+ code: data.code,
367
+ upgradeUrl: data.upgradeUrl,
368
+ });
369
+ break;
370
+ case "flow_await":
371
+ this.out("await", {
372
+ toolId: data.toolId,
373
+ toolCallId: data.toolCallId,
374
+ toolName: data.toolName,
375
+ parameters: data.parameters,
376
+ awaitedAt: data.awaitedAt,
377
+ origin: data.origin,
378
+ pageOrigin: data.pageOrigin,
379
+ });
380
+ break;
381
+ case "step_start":
382
+ this.closeChannels();
383
+ this.out("step_start", {
384
+ id: data.id ?? data.stepId,
385
+ name: data.name ?? data.stepName,
386
+ stepType: data.stepType,
387
+ index: data.index,
388
+ totalSteps: data.totalSteps,
389
+ startedAt: data.startedAt,
390
+ outputVariable: data.outputVariable,
391
+ });
392
+ break;
393
+ case "step_delta":
394
+ this.emitTextDelta(data.text ?? data.delta);
395
+ break;
396
+ case "step_complete":
397
+ this.closeChannels();
398
+ this.out("step_complete", {
399
+ id: data.id ?? data.stepId,
400
+ name: data.name ?? data.stepName,
401
+ stepType: data.stepType,
402
+ success: data.success ?? true,
403
+ durationMs: data.duration ?? data.durationMs ?? data.executionTime,
404
+ result: data.result ?? data.output,
405
+ stopReason: data.stopReason,
406
+ completedAt: data.completedAt,
407
+ unresolvedVariables: data.unresolvedVariables,
408
+ ...(this.fallbackInfo(data) ?? {}),
409
+ });
410
+ break;
411
+ case "step_error":
412
+ this.closeChannels();
413
+ this.out("step_complete", {
414
+ id: data.id ?? data.stepId,
415
+ name: data.name,
416
+ stepType: data.stepType,
417
+ success: false,
418
+ error: data.error,
419
+ durationMs: data.executionTime,
420
+ });
421
+ break;
422
+ case "step_skip":
423
+ this.out("step_skip", {
424
+ id: data.id,
425
+ name: data.name,
426
+ stepType: data.stepType,
427
+ index: data.index,
428
+ totalSteps: data.totalSteps,
429
+ when: data.when,
430
+ skippedAt: data.skippedAt,
431
+ });
432
+ break;
433
+ case "step_await":
434
+ this.out("await", {
435
+ toolId: data.toolId,
436
+ toolCallId: data.toolCallId,
437
+ toolName: data.toolName,
438
+ parameters: data.parameters,
439
+ awaitedAt: data.awaitedAt,
440
+ });
441
+ break;
442
+
443
+ case "tool_start":
444
+ this.out("tool_start", {
445
+ toolCallId: data.toolCallId ?? data.toolId,
446
+ toolName: data.toolName ?? data.name,
447
+ toolType: data.toolType ?? "builtin",
448
+ stepId: data.stepId,
449
+ parameters: data.parameters,
450
+ hiddenParameterNames: data.hiddenParameterNames,
451
+ startedAt: data.startedAt,
452
+ });
453
+ break;
454
+ case "tool_delta":
455
+ this.out("tool_output_delta", {
456
+ toolCallId: data.toolId ?? data.toolCallId,
457
+ delta: String(data.delta ?? ""),
458
+ });
459
+ break;
460
+ case "tool_input_delta":
461
+ this.out("tool_input_delta", {
462
+ toolCallId: data.toolCallId ?? data.toolId,
463
+ delta: String(data.delta ?? ""),
464
+ });
465
+ break;
466
+ case "tool_input_complete":
467
+ this.out("tool_input_complete", {
468
+ toolCallId: data.toolCallId ?? data.toolId,
469
+ toolName: data.toolName,
470
+ parameters: (data.parameters as Json) ?? {},
471
+ hiddenParameterNames: data.hiddenParameterNames,
472
+ });
473
+ break;
474
+ case "tool_complete":
475
+ this.out("tool_complete", {
476
+ toolCallId: data.toolCallId ?? data.toolId,
477
+ toolName: data.toolName ?? data.name,
478
+ success: data.success ?? true,
479
+ result: data.result,
480
+ error: data.error,
481
+ executionTime: data.executionTime,
482
+ stepId: data.stepId,
483
+ });
484
+ break;
485
+ case "tool_error":
486
+ this.out("tool_complete", {
487
+ toolCallId: data.toolId ?? data.toolCallId,
488
+ toolName: data.name,
489
+ success: false,
490
+ error: data.error,
491
+ executionTime: data.executionTime,
492
+ });
493
+ break;
494
+ case "chunk":
495
+ this.emitTextDelta(data.text);
496
+ break;
497
+
498
+ case "text_start":
499
+ if (!this.openText) {
500
+ this.openText = (data.id as string) ?? this.mint("text");
501
+ this.out("text_start", { id: this.openText });
502
+ }
503
+ break;
504
+ case "text_end":
505
+ this.closeText();
506
+ break;
507
+ case "reason_start":
508
+ this.ensureReasoningOpen();
509
+ break;
510
+ case "reason_delta":
511
+ this.emitReasoningDelta(data.reasoningText ?? data.delta ?? data.text);
512
+ break;
513
+ case "reason_complete":
514
+ this.closeReasoning();
515
+ break;
516
+ case "source":
517
+ this.out("source", omitEnvelope(data));
518
+ break;
519
+
520
+ case "fallback_start":
521
+ this.out("custom", {
522
+ name: "runtype.fallback",
523
+ value: { phase: "start", ...omitEnvelope(data) },
524
+ });
525
+ break;
526
+ case "fallback_complete":
527
+ this.out("custom", {
528
+ name: "runtype.fallback",
529
+ value: { phase: "complete", ...omitEnvelope(data) },
530
+ });
531
+ break;
532
+ case "fallback_exhausted":
533
+ this.out("custom", {
534
+ name: "runtype.fallback",
535
+ value: { phase: "exhausted", ...omitEnvelope(data) },
536
+ });
537
+ break;
538
+
539
+ case "artifact_start":
540
+ this.out("artifact_start", {
541
+ id: data.id,
542
+ artifactType: data.artifactType,
543
+ title: data.title,
544
+ component: data.component,
545
+ });
546
+ break;
547
+ case "artifact_delta":
548
+ this.out("artifact_delta", { id: data.id, delta: data.delta });
549
+ break;
550
+ case "artifact_update":
551
+ this.out("artifact_update", { id: data.id, component: data.component, props: data.props });
552
+ break;
553
+ case "artifact_complete":
554
+ this.out("artifact_complete", { id: data.id });
555
+ break;
556
+ case "artifact":
557
+ this.out("artifact_start", {
558
+ id: data.id,
559
+ artifactType: data.artifactType ?? "markdown",
560
+ title: data.title,
561
+ component: data.component,
562
+ });
563
+ this.out("artifact_complete", { id: data.id });
564
+ break;
565
+
566
+ case "dispatch_error":
567
+ this.closeChannels();
568
+ this.out("execution_error", {
569
+ kind: this.kind,
570
+ error: this.toErrorPayload(data),
571
+ code: data.code,
572
+ upgradeUrl: data.upgradeUrl,
573
+ });
574
+ break;
575
+
576
+ default:
577
+ break;
578
+ }
579
+ }
580
+
581
+ private emitMedia(data: Json): void {
582
+ const media = Array.isArray(data.media) ? data.media : [];
583
+ for (const raw of media) {
584
+ const item = (raw ?? {}) as Json;
585
+ const id = this.mint("media");
586
+ const mediaType =
587
+ typeof item.mediaType === "string"
588
+ ? item.mediaType
589
+ : item.type === "image-url"
590
+ ? "image"
591
+ : "application/octet-stream";
592
+ this.out("media_start", {
593
+ id,
594
+ mediaType,
595
+ role: "assistant",
596
+ toolCallId: data.toolCallId,
597
+ });
598
+ const fragment =
599
+ typeof item.url === "string" ? item.url : typeof item.data === "string" ? item.data : "";
600
+ if (fragment) this.out("media_delta", { id, delta: fragment });
601
+ this.out("media_complete", {
602
+ id,
603
+ mediaType,
604
+ url: item.url,
605
+ data: item.data,
606
+ toolCallId: data.toolCallId,
607
+ });
608
+ }
609
+ }
610
+
611
+ private toErrorPayload(error: unknown): Json | string {
612
+ if (typeof error === "string") return error;
613
+ if (error && typeof error === "object") {
614
+ const e = error as Json;
615
+ if (typeof e.code === "string" && typeof e.message === "string") {
616
+ return {
617
+ code: e.code,
618
+ message: e.message,
619
+ ...(e.details ? { details: e.details } : {}),
620
+ };
621
+ }
622
+ if (typeof e.error === "string") return e.error;
623
+ if (typeof e.message === "string")
624
+ return { code: typeof e.code === "string" ? e.code : "error", message: e.message };
625
+ }
626
+ return { code: "error", message: "Execution failed" };
627
+ }
628
+
629
+ write(chunk: string): void {
630
+ const parts = chunk.split("\n\n");
631
+ for (const part of parts) {
632
+ if (!part.trim()) continue;
633
+ let dataStr: string | null = null;
634
+ for (const line of part.split("\n")) {
635
+ if (line.startsWith("data: ")) dataStr = line.slice(6);
636
+ }
637
+ if (dataStr === null) {
638
+ this.sink(`${part}\n\n`);
639
+ continue;
640
+ }
641
+ if (dataStr.trim() === "[DONE]") {
642
+ this.sink("data: [DONE]\n\n");
643
+ continue;
644
+ }
645
+ let data: Json;
646
+ try {
647
+ data = JSON.parse(dataStr) as Json;
648
+ } catch {
649
+ this.sink(`${part}\n\n`);
650
+ continue;
651
+ }
652
+ const type = data?.type;
653
+ if (typeof type !== "string") continue;
654
+ try {
655
+ this.handle(type, data);
656
+ } catch {
657
+ // oracle fixture: swallow per-frame translation errors (prod logs via getLogger)
658
+ }
659
+ }
660
+ }
661
+ }
662
+
663
+ export function createUnifiedEventWrite(
664
+ sink: (chunk: string) => void,
665
+ options?: UnifiedEventWriteOptions
666
+ ): (chunk: string) => void {
667
+ const translator = new UnifiedEventTranslator(sink, options);
668
+ return (chunk: string) => translator.write(chunk);
669
+ }