@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.
- 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 +34 -34
- 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 +98 -0
- package/src/client.ts +58 -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
package/dist/theme-editor.d.cts
CHANGED
|
@@ -4891,6 +4891,16 @@ type AgentWidgetConfig = {
|
|
|
4891
4891
|
* ```
|
|
4892
4892
|
*/
|
|
4893
4893
|
parseSSEEvent?: AgentWidgetSSEEventParser;
|
|
4894
|
+
/**
|
|
4895
|
+
* Wire vocabulary requested from the dispatch endpoint.
|
|
4896
|
+
* - `'legacy'` (default): the current `agent_*` / `flow_*` SSE events.
|
|
4897
|
+
* - `'unified'`: opt into the API's neutral unified event vocabulary by sending
|
|
4898
|
+
* `?events=unified`; incoming frames are transparently bridged back to the same
|
|
4899
|
+
* handlers, so rendering is unchanged. Requires an upstream that honors the
|
|
4900
|
+
* param — the widget detects the actual wire mode from the first stream frame
|
|
4901
|
+
* and falls back to legacy automatically if the param was ignored.
|
|
4902
|
+
*/
|
|
4903
|
+
events?: "legacy" | "unified";
|
|
4894
4904
|
/**
|
|
4895
4905
|
* Called for every parsed SSE frame (after JSON parse), before native handling.
|
|
4896
4906
|
* Use for lightweight side effects (e.g. telemetry). Does not replace native
|
package/dist/theme-editor.d.ts
CHANGED
|
@@ -4891,6 +4891,16 @@ type AgentWidgetConfig = {
|
|
|
4891
4891
|
* ```
|
|
4892
4892
|
*/
|
|
4893
4893
|
parseSSEEvent?: AgentWidgetSSEEventParser;
|
|
4894
|
+
/**
|
|
4895
|
+
* Wire vocabulary requested from the dispatch endpoint.
|
|
4896
|
+
* - `'legacy'` (default): the current `agent_*` / `flow_*` SSE events.
|
|
4897
|
+
* - `'unified'`: opt into the API's neutral unified event vocabulary by sending
|
|
4898
|
+
* `?events=unified`; incoming frames are transparently bridged back to the same
|
|
4899
|
+
* handlers, so rendering is unchanged. Requires an upstream that honors the
|
|
4900
|
+
* param — the widget detects the actual wire mode from the first stream frame
|
|
4901
|
+
* and falls back to legacy automatically if the param was ignored.
|
|
4902
|
+
*/
|
|
4903
|
+
events?: "legacy" | "unified";
|
|
4894
4904
|
/**
|
|
4895
4905
|
* Called for every parsed SSE frame (after JSON parse), before native handling.
|
|
4896
4906
|
* Use for lightweight side effects (e.g. telemetry). Does not replace native
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.37.0",
|
|
4
4
|
"description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
package/src/client.test.ts
CHANGED
|
@@ -4220,3 +4220,101 @@ describe('AgentWidgetClient - version header', () => {
|
|
|
4220
4220
|
expect(headers[0]['X-Persona-Version']).toBe('override');
|
|
4221
4221
|
});
|
|
4222
4222
|
});
|
|
4223
|
+
|
|
4224
|
+
describe('AgentWidgetClient - Unified event vocabulary (events: "unified")', () => {
|
|
4225
|
+
const unifiedTextStream = (execId: string) => [
|
|
4226
|
+
sseEvent('execution_start', { kind: 'agent', executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
|
|
4227
|
+
sseEvent('turn_start', { executionId: execId, id: 'turn_1', iteration: 1, role: 'assistant', seq: 2 }),
|
|
4228
|
+
sseEvent('text_start', { executionId: execId, id: 'text_1', role: 'assistant', seq: 3 }),
|
|
4229
|
+
sseEvent('text_delta', { executionId: execId, id: 'text_1', delta: 'Hello', seq: 4 }),
|
|
4230
|
+
sseEvent('text_delta', { executionId: execId, id: 'text_1', delta: ' World', seq: 5 }),
|
|
4231
|
+
sseEvent('text_complete', { executionId: execId, id: 'text_1', seq: 6 }),
|
|
4232
|
+
sseEvent('turn_complete', { executionId: execId, id: 'turn_1', iteration: 1, role: 'assistant', completedAt: new Date().toISOString(), seq: 7 }),
|
|
4233
|
+
sseEvent('execution_complete', { kind: 'agent', executionId: execId, success: true, completedAt: new Date().toISOString(), seq: 8 }),
|
|
4234
|
+
];
|
|
4235
|
+
|
|
4236
|
+
it('appends ?events=unified to the dispatch URL when opted in', async () => {
|
|
4237
|
+
let capturedUrl = '';
|
|
4238
|
+
global.fetch = vi.fn().mockImplementation(async (url: string) => {
|
|
4239
|
+
capturedUrl = url;
|
|
4240
|
+
const encoder = new TextEncoder();
|
|
4241
|
+
const stream = new ReadableStream({
|
|
4242
|
+
start(c) {
|
|
4243
|
+
for (const e of unifiedTextStream('exec_u')) c.enqueue(encoder.encode(e));
|
|
4244
|
+
c.close();
|
|
4245
|
+
},
|
|
4246
|
+
});
|
|
4247
|
+
return { ok: true, body: stream };
|
|
4248
|
+
});
|
|
4249
|
+
|
|
4250
|
+
const client = new AgentWidgetClient({
|
|
4251
|
+
apiUrl: 'http://localhost:8000',
|
|
4252
|
+
events: 'unified',
|
|
4253
|
+
agent: { name: 'Test', model: 'openai:gpt-4o-mini', systemPrompt: 'test' },
|
|
4254
|
+
});
|
|
4255
|
+
await client.dispatch(
|
|
4256
|
+
{ messages: [{ id: 'u1', role: 'user', content: 'Hi', createdAt: new Date().toISOString() }] },
|
|
4257
|
+
() => {}
|
|
4258
|
+
);
|
|
4259
|
+
expect(capturedUrl).toContain('events=unified');
|
|
4260
|
+
});
|
|
4261
|
+
|
|
4262
|
+
it('renders a unified stream by bridging it back to the legacy handlers', async () => {
|
|
4263
|
+
const events: AgentWidgetEvent[] = [];
|
|
4264
|
+
const execId = 'exec_u1';
|
|
4265
|
+
global.fetch = createAgentStreamFetch(unifiedTextStream(execId));
|
|
4266
|
+
|
|
4267
|
+
const client = new AgentWidgetClient({
|
|
4268
|
+
apiUrl: 'http://localhost:8000',
|
|
4269
|
+
events: 'unified',
|
|
4270
|
+
agent: { name: 'Test', model: 'openai:gpt-4o-mini', systemPrompt: 'test' },
|
|
4271
|
+
});
|
|
4272
|
+
await client.dispatch(
|
|
4273
|
+
{ messages: [{ id: 'u1', role: 'user', content: 'Hi', createdAt: new Date().toISOString() }] },
|
|
4274
|
+
(e) => events.push(e)
|
|
4275
|
+
);
|
|
4276
|
+
|
|
4277
|
+
const messageEvents = events.filter((e) => e.type === 'message');
|
|
4278
|
+
expect(messageEvents.length).toBeGreaterThan(0);
|
|
4279
|
+
const last = messageEvents[messageEvents.length - 1];
|
|
4280
|
+
expect(last.type).toBe('message');
|
|
4281
|
+
if (last.type === 'message') {
|
|
4282
|
+
expect(last.message.content).toBe('Hello World');
|
|
4283
|
+
expect(last.message.streaming).toBe(false);
|
|
4284
|
+
expect(last.message.role).toBe('assistant');
|
|
4285
|
+
expect(last.message.agentMetadata?.executionId).toBe(execId);
|
|
4286
|
+
}
|
|
4287
|
+
});
|
|
4288
|
+
|
|
4289
|
+
it('auto-falls-back to legacy when "unified" was requested but the API streamed legacy frames', async () => {
|
|
4290
|
+
const events: AgentWidgetEvent[] = [];
|
|
4291
|
+
const execId = 'exec_legacy_fallback';
|
|
4292
|
+
// Same content, LEGACY vocabulary — the API ignored ?events=unified.
|
|
4293
|
+
global.fetch = createAgentStreamFetch([
|
|
4294
|
+
sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
|
|
4295
|
+
sseEvent('agent_turn_start', { executionId: execId, iteration: 1, turnId: 'turn_1', role: 'assistant', seq: 2 }),
|
|
4296
|
+
sseEvent('agent_turn_delta', { executionId: execId, iteration: 1, delta: 'Hello', contentType: 'text', turnId: 'turn_1', seq: 3 }),
|
|
4297
|
+
sseEvent('agent_turn_delta', { executionId: execId, iteration: 1, delta: ' World', contentType: 'text', turnId: 'turn_1', seq: 4 }),
|
|
4298
|
+
sseEvent('agent_turn_complete', { executionId: execId, iteration: 1, turnId: 'turn_1', completedAt: new Date().toISOString(), seq: 5 }),
|
|
4299
|
+
sseEvent('agent_complete', { executionId: execId, success: true, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 6 }),
|
|
4300
|
+
]);
|
|
4301
|
+
|
|
4302
|
+
const client = new AgentWidgetClient({
|
|
4303
|
+
apiUrl: 'http://localhost:8000',
|
|
4304
|
+
events: 'unified',
|
|
4305
|
+
agent: { name: 'Test', model: 'openai:gpt-4o-mini', systemPrompt: 'test' },
|
|
4306
|
+
});
|
|
4307
|
+
await client.dispatch(
|
|
4308
|
+
{ messages: [{ id: 'u1', role: 'user', content: 'Hi', createdAt: new Date().toISOString() }] },
|
|
4309
|
+
(e) => events.push(e)
|
|
4310
|
+
);
|
|
4311
|
+
|
|
4312
|
+
const messageEvents = events.filter((e) => e.type === 'message');
|
|
4313
|
+
const last = messageEvents[messageEvents.length - 1];
|
|
4314
|
+
expect(last.type).toBe('message');
|
|
4315
|
+
if (last.type === 'message') {
|
|
4316
|
+
expect(last.message.content).toBe('Hello World');
|
|
4317
|
+
expect(last.message.streaming).toBe(false);
|
|
4318
|
+
}
|
|
4319
|
+
});
|
|
4320
|
+
});
|
package/src/client.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
createXmlParser
|
|
34
34
|
} from "./utils/formatting";
|
|
35
35
|
import { SequenceReorderBuffer } from "./utils/sequence-buffer";
|
|
36
|
+
import { UnifiedToLegacyBridge, isUnifiedLifecycleStart } from "./utils/unified-event-bridge";
|
|
36
37
|
import { VERSION } from "./version";
|
|
37
38
|
// artifactsSidebarEnabled is used in ui.ts to gate the sidebar pane rendering;
|
|
38
39
|
// artifact events are always processed here regardless of config.
|
|
@@ -292,6 +293,17 @@ export class AgentWidgetClient {
|
|
|
292
293
|
return !!this.config.agent;
|
|
293
294
|
}
|
|
294
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Append `?events=unified` when the widget opted into the unified vocabulary,
|
|
298
|
+
* preserving any existing query string. No-op for the default legacy mode.
|
|
299
|
+
* The server only switches vocabularies when it sees the param; the widget
|
|
300
|
+
* still auto-detects the actual wire mode from the first stream frame.
|
|
301
|
+
*/
|
|
302
|
+
private withEventsParam(url: string): string {
|
|
303
|
+
if (this.config.events !== "unified") return url;
|
|
304
|
+
return url + (url.includes("?") ? "&" : "?") + "events=unified";
|
|
305
|
+
}
|
|
306
|
+
|
|
295
307
|
/**
|
|
296
308
|
* Get the appropriate API URL based on mode
|
|
297
309
|
*/
|
|
@@ -682,7 +694,7 @@ export class AgentWidgetClient {
|
|
|
682
694
|
console.debug("[AgentWidgetClient] client token dispatch", chatRequest);
|
|
683
695
|
}
|
|
684
696
|
|
|
685
|
-
response = await fetch(this.getClientApiUrl('chat'), {
|
|
697
|
+
response = await fetch(this.withEventsParam(this.getClientApiUrl('chat')), {
|
|
686
698
|
method: 'POST',
|
|
687
699
|
headers: {
|
|
688
700
|
'Content-Type': 'application/json',
|
|
@@ -802,7 +814,7 @@ export class AgentWidgetClient {
|
|
|
802
814
|
if (this.customFetch) {
|
|
803
815
|
try {
|
|
804
816
|
response = await this.customFetch(
|
|
805
|
-
this.apiUrl,
|
|
817
|
+
this.withEventsParam(this.apiUrl),
|
|
806
818
|
{
|
|
807
819
|
method: "POST",
|
|
808
820
|
headers,
|
|
@@ -817,7 +829,7 @@ export class AgentWidgetClient {
|
|
|
817
829
|
throw err;
|
|
818
830
|
}
|
|
819
831
|
} else {
|
|
820
|
-
response = await fetch(this.apiUrl, {
|
|
832
|
+
response = await fetch(this.withEventsParam(this.apiUrl), {
|
|
821
833
|
method: "POST",
|
|
822
834
|
headers,
|
|
823
835
|
body: JSON.stringify(payload),
|
|
@@ -878,7 +890,7 @@ export class AgentWidgetClient {
|
|
|
878
890
|
if (this.customFetch) {
|
|
879
891
|
try {
|
|
880
892
|
response = await this.customFetch(
|
|
881
|
-
this.apiUrl,
|
|
893
|
+
this.withEventsParam(this.apiUrl),
|
|
882
894
|
{
|
|
883
895
|
method: "POST",
|
|
884
896
|
headers,
|
|
@@ -893,7 +905,7 @@ export class AgentWidgetClient {
|
|
|
893
905
|
throw err;
|
|
894
906
|
}
|
|
895
907
|
} else {
|
|
896
|
-
response = await fetch(this.apiUrl, {
|
|
908
|
+
response = await fetch(this.withEventsParam(this.apiUrl), {
|
|
897
909
|
method: "POST",
|
|
898
910
|
headers,
|
|
899
911
|
body: JSON.stringify(payload),
|
|
@@ -996,9 +1008,11 @@ export class AgentWidgetClient {
|
|
|
996
1008
|
options?: { streamResponse?: boolean; signal?: AbortSignal }
|
|
997
1009
|
): Promise<Response> {
|
|
998
1010
|
const isClientToken = this.isClientTokenMode();
|
|
999
|
-
const url =
|
|
1000
|
-
|
|
1001
|
-
|
|
1011
|
+
const url = this.withEventsParam(
|
|
1012
|
+
isClientToken
|
|
1013
|
+
? this.getClientApiUrl('resume')
|
|
1014
|
+
: `${this.config.apiUrl?.replace(/\/+$/, '') || DEFAULT_CLIENT_API_BASE}/resume`
|
|
1015
|
+
);
|
|
1002
1016
|
|
|
1003
1017
|
// The client-token resume route authenticates the session, not a Bearer
|
|
1004
1018
|
// key. A WebMCP approval can sit awaiting user input for a long time, so by
|
|
@@ -1833,6 +1847,17 @@ export class AgentWidgetClient {
|
|
|
1833
1847
|
seqReadyQueue.push({ payloadType, payload });
|
|
1834
1848
|
scheduleReadyQueueDrain();
|
|
1835
1849
|
});
|
|
1850
|
+
// Unified-event consumer (opt-in; see utils/unified-event-bridge.ts). When the
|
|
1851
|
+
// API streams the neutral unified vocabulary, each frame is transduced back to
|
|
1852
|
+
// the legacy events the dispatch chain below already renders. Seed the mode from
|
|
1853
|
+
// the requested vocabulary so /resume (whose stream continues mid-run with no
|
|
1854
|
+
// `execution_start`) bridges correctly; a leading lifecycle frame is then
|
|
1855
|
+
// authoritative and can flip it (e.g. an upstream that ignored `?events=unified`
|
|
1856
|
+
// streams `agent_start`). The unified stream is single-connection and in order,
|
|
1857
|
+
// so bridged events bypass the reorder buffer.
|
|
1858
|
+
const unifiedBridge = new UnifiedToLegacyBridge();
|
|
1859
|
+
let unifiedMode = this.config.events === "unified";
|
|
1860
|
+
let wireModeResolved = false;
|
|
1836
1861
|
// Agent execution state tracking
|
|
1837
1862
|
let agentExecution: AgentExecutionState | null = null;
|
|
1838
1863
|
// Track assistant messages per agent iteration for 'separate' mode
|
|
@@ -3373,6 +3398,31 @@ export class AgentWidgetClient {
|
|
|
3373
3398
|
if (handled) continue; // Skip default handling if custom handler processed it
|
|
3374
3399
|
}
|
|
3375
3400
|
|
|
3401
|
+
// Unified vocabulary (opt-in): resolve the wire mode from the first
|
|
3402
|
+
// lifecycle frame (the flag only requests the param; the frame is
|
|
3403
|
+
// authoritative), then transduce unified → legacy, bypassing the reorder
|
|
3404
|
+
// buffer (the unified stream is single-connection and already in order).
|
|
3405
|
+
if (!wireModeResolved) {
|
|
3406
|
+
if (isUnifiedLifecycleStart(payloadType)) {
|
|
3407
|
+
unifiedMode = true;
|
|
3408
|
+
wireModeResolved = true;
|
|
3409
|
+
} else if (
|
|
3410
|
+
payloadType === "agent_start" ||
|
|
3411
|
+
payloadType === "flow_start" ||
|
|
3412
|
+
payloadType === "step_start"
|
|
3413
|
+
) {
|
|
3414
|
+
unifiedMode = false;
|
|
3415
|
+
wireModeResolved = true;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
if (unifiedMode) {
|
|
3419
|
+
for (const legacyEvent of unifiedBridge.push(payloadType, payload)) {
|
|
3420
|
+
seqReadyQueue.push(legacyEvent);
|
|
3421
|
+
}
|
|
3422
|
+
drainReadyQueue();
|
|
3423
|
+
continue;
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3376
3426
|
// Push through the sequence reorder buffer
|
|
3377
3427
|
seqBuffer.push(payloadType, payload);
|
|
3378
3428
|
drainReadyQueue();
|
package/src/install.ts
CHANGED
|
@@ -18,6 +18,9 @@ interface SiteAgentInstallConfig {
|
|
|
18
18
|
clientToken?: string;
|
|
19
19
|
flowId?: string;
|
|
20
20
|
apiUrl?: string;
|
|
21
|
+
/** Wire vocabulary requested from the dispatch endpoint; 'unified' opts into the
|
|
22
|
+
* API's neutral event schema (transparently bridged back to the same rendering). */
|
|
23
|
+
events?: "legacy" | "unified";
|
|
21
24
|
// Optional query param key that gates widget installation in preview mode
|
|
22
25
|
previewQueryParam?: string;
|
|
23
26
|
// Shadow DOM option (defaults to false for better CSS compatibility)
|
|
@@ -377,6 +380,7 @@ declare global {
|
|
|
377
380
|
if (config.apiUrl && !widgetConfig.apiUrl) widgetConfig.apiUrl = config.apiUrl;
|
|
378
381
|
if (config.clientToken && !widgetConfig.clientToken) widgetConfig.clientToken = config.clientToken;
|
|
379
382
|
if (config.flowId && !widgetConfig.flowId) widgetConfig.flowId = config.flowId;
|
|
383
|
+
if (config.events && !widgetConfig.events) widgetConfig.events = config.events;
|
|
380
384
|
|
|
381
385
|
const hasApiConfig = !!(widgetConfig.apiUrl || widgetConfig.clientToken);
|
|
382
386
|
return { target, widgetConfig, hasApiConfig };
|
package/src/types.ts
CHANGED
|
@@ -4056,6 +4056,16 @@ export type AgentWidgetConfig = {
|
|
|
4056
4056
|
* ```
|
|
4057
4057
|
*/
|
|
4058
4058
|
parseSSEEvent?: AgentWidgetSSEEventParser;
|
|
4059
|
+
/**
|
|
4060
|
+
* Wire vocabulary requested from the dispatch endpoint.
|
|
4061
|
+
* - `'legacy'` (default): the current `agent_*` / `flow_*` SSE events.
|
|
4062
|
+
* - `'unified'`: opt into the API's neutral unified event vocabulary by sending
|
|
4063
|
+
* `?events=unified`; incoming frames are transparently bridged back to the same
|
|
4064
|
+
* handlers, so rendering is unchanged. Requires an upstream that honors the
|
|
4065
|
+
* param — the widget detects the actual wire mode from the first stream frame
|
|
4066
|
+
* and falls back to legacy automatically if the param was ignored.
|
|
4067
|
+
*/
|
|
4068
|
+
events?: "legacy" | "unified";
|
|
4059
4069
|
/**
|
|
4060
4070
|
* Called for every parsed SSE frame (after JSON parse), before native handling.
|
|
4061
4071
|
* Use for lightweight side effects (e.g. telemetry). Does not replace native
|