@runtypelabs/persona 4.2.1 → 4.3.1

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.
Files changed (45) hide show
  1. package/README.md +22 -6
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-B_xbfvR0.d.cts → types-C6tFDxKy.d.cts} +1 -1
  5. package/dist/animations/{types-B_xbfvR0.d.ts → types-C6tFDxKy.d.ts} +1 -1
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/codegen.cjs +6 -6
  9. package/dist/codegen.js +9 -9
  10. package/dist/index.cjs +32 -32
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +101 -3
  13. package/dist/index.d.ts +101 -3
  14. package/dist/index.global.js +23 -23
  15. package/dist/index.global.js.map +1 -1
  16. package/dist/index.js +32 -32
  17. package/dist/index.js.map +1 -1
  18. package/dist/install.global.js +1 -1
  19. package/dist/install.global.js.map +1 -1
  20. package/dist/launcher.global.js.map +1 -1
  21. package/dist/smart-dom-reader.d.cts +76 -1
  22. package/dist/smart-dom-reader.d.ts +76 -1
  23. package/dist/theme-editor-preview.cjs +27 -27
  24. package/dist/theme-editor-preview.d.cts +76 -1
  25. package/dist/theme-editor-preview.d.ts +76 -1
  26. package/dist/theme-editor-preview.js +29 -29
  27. package/dist/theme-editor.cjs +1 -1
  28. package/dist/theme-editor.d.cts +76 -1
  29. package/dist/theme-editor.d.ts +76 -1
  30. package/dist/theme-editor.js +1 -1
  31. package/dist/widget.css +3 -0
  32. package/package.json +1 -1
  33. package/src/client.test.ts +221 -29
  34. package/src/client.ts +71 -24
  35. package/src/defaults.ts +2 -0
  36. package/src/index-core.ts +2 -0
  37. package/src/install.ts +9 -1
  38. package/src/session.ts +6 -3
  39. package/src/session.voice.test.ts +65 -0
  40. package/src/styles/widget.css +3 -0
  41. package/src/types.ts +57 -2
  42. package/src/utils/__fixtures__/unified-translator.oracle.ts +3 -3
  43. package/src/utils/code-generators.ts +10 -0
  44. package/src/utils/target.test.ts +51 -0
  45. package/src/utils/target.ts +90 -0
package/src/install.ts CHANGED
@@ -17,6 +17,7 @@ interface SiteAgentInstallConfig {
17
17
  // Client token mode options (can also be set via data attributes)
18
18
  clientToken?: string;
19
19
  flowId?: string;
20
+ agentId?: string;
20
21
  apiUrl?: string;
21
22
  // Optional query param key that gates widget installation in preview mode
22
23
  previewQueryParam?: string;
@@ -69,7 +70,7 @@ declare global {
69
70
 
70
71
  /**
71
72
  * Read configuration from data attributes on the current script tag.
72
- * Supports: data-config (JSON), data-runtype-token, data-flow-id, data-api-url
73
+ * Supports: data-config (JSON), data-runtype-token, data-flow-id, data-agent-id, data-api-url
73
74
  */
74
75
  const getConfigFromScript = (): Partial<SiteAgentInstallConfig> => {
75
76
  // Try to get the current script element
@@ -112,6 +113,12 @@ declare global {
112
113
  scriptConfig.flowId = flowId;
113
114
  }
114
115
 
116
+ // Optional agent ID
117
+ const agentId = script.getAttribute('data-agent-id');
118
+ if (agentId) {
119
+ scriptConfig.agentId = agentId;
120
+ }
121
+
115
122
  // Optional API URL override
116
123
  const apiUrl = script.getAttribute('data-api-url');
117
124
  if (apiUrl) {
@@ -357,6 +364,7 @@ declare global {
357
364
  if (config.apiUrl && !widgetConfig.apiUrl) widgetConfig.apiUrl = config.apiUrl;
358
365
  if (config.clientToken && !widgetConfig.clientToken) widgetConfig.clientToken = config.clientToken;
359
366
  if (config.flowId && !widgetConfig.flowId) widgetConfig.flowId = config.flowId;
367
+ if (config.agentId && !widgetConfig.agentId) widgetConfig.agentId = config.agentId;
360
368
 
361
369
  const hasApiConfig = !!(widgetConfig.apiUrl || widgetConfig.clientToken);
362
370
  return { target, widgetConfig, hasApiConfig };
package/src/session.ts CHANGED
@@ -70,6 +70,9 @@ const CONNECTION_CONFIG_KEYS = [
70
70
  "apiUrl",
71
71
  "clientToken",
72
72
  "flowId",
73
+ "agentId",
74
+ "target",
75
+ "targetProviders",
73
76
  "agent",
74
77
  "agentOptions",
75
78
  "headers",
@@ -286,7 +289,7 @@ export class AgentWidgetSession {
286
289
  // clientToken) falls back to the browser voice, so the fetch would be wasted.
287
290
  const host = tts.host ?? this.config.apiUrl;
288
291
  const agentId =
289
- tts.agentId ?? this.config.voiceRecognition?.provider?.runtype?.agentId;
292
+ tts.agentId ?? this.config.voiceRecognition?.provider?.runtype?.agentId ?? this.config.agentId;
290
293
  if (!host || !agentId || !this.config.clientToken) return;
291
294
  void loadRuntypeTts().catch(() => {});
292
295
  }
@@ -415,7 +418,7 @@ export class AgentWidgetSession {
415
418
  if (tts?.provider === "runtype") {
416
419
  const host = tts.host ?? this.config.apiUrl;
417
420
  const agentId =
418
- tts.agentId ?? this.config.voiceRecognition?.provider?.runtype?.agentId;
421
+ tts.agentId ?? this.config.voiceRecognition?.provider?.runtype?.agentId ?? this.config.agentId;
419
422
  const clientToken = this.config.clientToken;
420
423
  const wantFallback = tts.browserFallback !== false;
421
424
 
@@ -659,7 +662,7 @@ export class AgentWidgetSession {
659
662
  return {
660
663
  type: 'runtype',
661
664
  runtype: {
662
- agentId: providerConfig.runtype?.agentId || '',
665
+ agentId: providerConfig.runtype?.agentId ?? this.config.agentId ?? '',
663
666
  // Default credentials/endpoint from the widget config so the minimum
664
667
  // voice config collapses to just `agentId`.
665
668
  clientToken: providerConfig.runtype?.clientToken ?? this.config.clientToken,
@@ -49,6 +49,7 @@ vi.mock('./voice', async (importOriginal) => {
49
49
  });
50
50
 
51
51
  import { AgentWidgetSession } from './session';
52
+ import { setRuntypeTtsLoader } from './voice/runtype-tts-loader';
52
53
 
53
54
  describe('AgentWidgetSession - realtime voice onTranscript (Option B)', () => {
54
55
  let session: AgentWidgetSession;
@@ -144,3 +145,67 @@ describe('AgentWidgetSession - realtime voice onTranscript (Option B)', () => {
144
145
  expect(metricsSeen).toEqual([{ llmMs: 100, totalMs: 250 }]);
145
146
  });
146
147
  });
148
+
149
+ describe('AgentWidgetSession - Runtype TTS config', () => {
150
+ it('uses top-level agentId as the default Runtype TTS agent', async () => {
151
+ let capturedOptions: { agentId?: string; clientToken?: string; host?: string } | null = null;
152
+
153
+ class FakeRuntypeSpeechEngine {
154
+ readonly id = 'runtype';
155
+ readonly supportsPause = false;
156
+
157
+ constructor(options: { agentId?: string; clientToken?: string; host?: string }) {
158
+ capturedOptions = options;
159
+ }
160
+
161
+ speak() {}
162
+ pause() {}
163
+ resume() {}
164
+ stop() {}
165
+ }
166
+
167
+ class FakeFallbackSpeechEngine {}
168
+
169
+ setRuntypeTtsLoader(async () => ({
170
+ RuntypeSpeechEngine: FakeRuntypeSpeechEngine as any,
171
+ FallbackSpeechEngine: FakeFallbackSpeechEngine as any,
172
+ }));
173
+
174
+ try {
175
+ const session = new AgentWidgetSession(
176
+ {
177
+ apiUrl: 'https://api.runtype.com',
178
+ clientToken: 'ct_live_demo',
179
+ agentId: 'agent_top_level',
180
+ textToSpeech: { enabled: true, provider: 'runtype', browserFallback: false },
181
+ initialMessages: [
182
+ {
183
+ id: 'assistant-1',
184
+ role: 'assistant',
185
+ content: 'Read this',
186
+ createdAt: new Date().toISOString(),
187
+ },
188
+ ],
189
+ },
190
+ {
191
+ onMessagesChanged: () => {},
192
+ onStatusChanged: () => {},
193
+ onStreamingChanged: () => {},
194
+ onError: () => {},
195
+ },
196
+ );
197
+
198
+ session.toggleReadAloud('assistant-1');
199
+ await Promise.resolve();
200
+ await Promise.resolve();
201
+
202
+ expect(capturedOptions).toMatchObject({
203
+ agentId: 'agent_top_level',
204
+ clientToken: 'ct_live_demo',
205
+ host: 'https://api.runtype.com',
206
+ });
207
+ } finally {
208
+ setRuntypeTtsLoader(null);
209
+ }
210
+ });
211
+ });
@@ -1745,6 +1745,9 @@
1745
1745
  /* Make message bubble position relative for overlay positioning */
1746
1746
  .persona-message-bubble {
1747
1747
  position: relative;
1748
+ /* Break long unbreakable tokens (e.g. URLs, package names) so they wrap
1749
+ inside the bubble's max-width instead of overflowing horizontally. */
1750
+ overflow-wrap: break-word;
1748
1751
  }
1749
1752
 
1750
1753
  /* Fade-in animation for action buttons */
package/src/types.ts CHANGED
@@ -5,6 +5,9 @@ import type {
5
5
  RuntypeClientFeedbackRequest,
6
6
  RuntypeStopReasonKind,
7
7
  } from "./generated/runtype-openapi-contract";
8
+ import type { TargetResolver } from "./utils/target";
9
+
10
+ export type { TargetResolver, ResolvedTarget } from "./utils/target";
8
11
 
9
12
  // ============================================================================
10
13
  // Multi-Modal Content Types
@@ -100,6 +103,7 @@ export type AgentWidgetRequestPayloadMessage = {
100
103
  export type AgentWidgetRequestPayload = {
101
104
  messages: AgentWidgetRequestPayloadMessage[];
102
105
  flowId?: string;
106
+ agent?: { agentId: string };
103
107
  context?: Record<string, unknown>;
104
108
  metadata?: Record<string, unknown>;
105
109
  /** Per-turn template variables for /v1/client/chat (merged as root-level {{var}} in Runtype). */
@@ -212,6 +216,11 @@ export type AgentConfig = {
212
216
  loopConfig?: AgentLoopConfig;
213
217
  };
214
218
 
219
+ export type SavedAgentConfig = {
220
+ /** Persisted Runtype agent ID to execute. */
221
+ agentId: string;
222
+ };
223
+
215
224
  /**
216
225
  * Options for agent execution requests.
217
226
  */
@@ -230,7 +239,7 @@ export type AgentRequestOptions = {
230
239
  * Request payload for agent execution mode.
231
240
  */
232
241
  export type AgentWidgetAgentRequestPayload = {
233
- agent: AgentConfig;
242
+ agent: AgentConfig | SavedAgentConfig;
234
243
  messages: AgentWidgetRequestPayloadMessage[];
235
244
  options: AgentRequestOptions;
236
245
  context?: Record<string, unknown>;
@@ -3573,6 +3582,52 @@ export type AgentWidgetLoadingIndicatorConfig = {
3573
3582
  export type AgentWidgetConfig = {
3574
3583
  apiUrl?: string;
3575
3584
  flowId?: string;
3585
+ /**
3586
+ * Persisted Runtype agent ID to execute.
3587
+ *
3588
+ * This is the primary Runtype entry point for simple agent-backed widgets.
3589
+ * It is mutually exclusive with `flowId` and with inline `agent` configs.
3590
+ * Voice and Runtype TTS providers inherit this value unless they specify their
3591
+ * own feature-scoped agent ID.
3592
+ */
3593
+ agentId?: string;
3594
+ /**
3595
+ * Normalized, backend-neutral routing target. A single string that selects
3596
+ * the backend resource to talk to, optimized for portability across
3597
+ * frameworks (it is always serializable: no live objects).
3598
+ *
3599
+ * Shapes:
3600
+ * - Runtype TypeID (no prefix): `"agent_…"` / `"flow_…"` route to the
3601
+ * Runtype agent/flow paths (the prefix is self-describing).
3602
+ * - Provider-prefixed: `"<provider>:<id>"` is resolved by
3603
+ * `targetProviders[provider]` (e.g. `"eve:support"`). `"runtype:…"` is a
3604
+ * built-in that re-detects a TypeID.
3605
+ * - Bare name: `"support"` requires a `targetProviders.default` resolver.
3606
+ *
3607
+ * Mutually exclusive with `agentId`, `flowId`, and inline `agent`. Prefer
3608
+ * `target` for backend-agnostic apps; use `agentId`/`flowId` for the explicit
3609
+ * Runtype path.
3610
+ *
3611
+ * @example
3612
+ * ```typescript
3613
+ * config: { target: "agent_01k..." } // Runtype agent
3614
+ * config: { target: "flow_01k..." } // Runtype flow
3615
+ * config: { // custom backend
3616
+ * apiUrl: "/api/chat",
3617
+ * target: "eve:support",
3618
+ * targetProviders: { eve: (id) => ({ payload: { assistant: id } }) },
3619
+ * }
3620
+ * ```
3621
+ */
3622
+ target?: string;
3623
+ /**
3624
+ * Prefix-keyed resolvers for `target` strings of the form
3625
+ * `"<provider>:<id>"`. Each resolver maps the id to the dispatch-payload
3626
+ * fragment its backend expects. Register a `default` resolver to also accept
3627
+ * bare (unprefixed, non-TypeID) target names. The built-in `runtype` provider
3628
+ * is always available and does not need to be registered.
3629
+ */
3630
+ targetProviders?: Record<string, TargetResolver>;
3576
3631
  /**
3577
3632
  * Override the assistant-bubble copy shown when a dispatch fails before any
3578
3633
  * response streams back (connection refused, CORS, 4xx/5xx, malformed
@@ -4265,7 +4320,7 @@ export type AgentWidgetReasoning = {
4265
4320
  status: "pending" | "streaming" | "complete";
4266
4321
  chunks: string[];
4267
4322
  /**
4268
- * Reasoning channel scope (unified spec). `"turn"` is ordinary per-turn
4323
+ * Reasoning channel scope (wire spec). `"turn"` is ordinary per-turn
4269
4324
  * thinking; `"loop"` is a cross-iteration agent reflection (the fold that
4270
4325
  * replaced the legacy `agent_reflection` event). Absent for legacy streams.
4271
4326
  */
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * TEST ORACLE — vendored copy of the runtype-core `createUnifiedEventWrite`
3
- * translator (the api-side legacy → unified 33-event encoder).
3
+ * translator (the api-side legacy → wire 33-event encoder).
4
4
  *
5
- * NOT shipped. Used only by `unified-event-bridge.test.ts` as the inverse
6
- * reference: `legacy frames → THIS oracle → unified frames → UnifiedToLegacyBridge
5
+ * NOT shipped. Used only by legacy-wire handler tests as the inverse
6
+ * reference: `legacy frames → THIS oracle → wire frames → client handlers
7
7
  * → legacy frames'` must be semantically equal to the original. Keeping a real
8
8
  * copy here means the round-trip test fails loudly if the api mapping and the
9
9
  * widget bridge ever drift.
@@ -605,6 +605,8 @@ function generateESMCode(config: any, options?: CodeGeneratorOptions): string {
605
605
 
606
606
  if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
607
607
  if (config.clientToken) lines.push(` clientToken: "${config.clientToken}",`);
608
+ if (config.agentId) lines.push(` agentId: "${config.agentId}",`);
609
+ if (config.target) lines.push(` target: "${config.target}",`);
608
610
  if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
609
611
  if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
610
612
 
@@ -751,6 +753,8 @@ function generateReactComponentCode(config: any, options?: CodeGeneratorOptions)
751
753
 
752
754
  if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
753
755
  if (config.clientToken) lines.push(` clientToken: "${config.clientToken}",`);
756
+ if (config.agentId) lines.push(` agentId: "${config.agentId}",`);
757
+ if (config.target) lines.push(` target: "${config.target}",`);
754
758
  if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
755
759
  if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
756
760
 
@@ -1015,6 +1019,8 @@ function generateReactAdvancedCode(config: any, options?: CodeGeneratorOptions):
1015
1019
 
1016
1020
  if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
1017
1021
  if (config.clientToken) lines.push(` clientToken: "${config.clientToken}",`);
1022
+ if (config.agentId) lines.push(` agentId: "${config.agentId}",`);
1023
+ if (config.target) lines.push(` target: "${config.target}",`);
1018
1024
  if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
1019
1025
 
1020
1026
  if (config.theme && typeof config.theme === "object" && Object.keys(config.theme).length > 0) {
@@ -1270,6 +1276,8 @@ function buildSerializableConfig(config: any): Record<string, any> {
1270
1276
 
1271
1277
  if (config.apiUrl) serializableConfig.apiUrl = config.apiUrl;
1272
1278
  if (config.clientToken) serializableConfig.clientToken = config.clientToken;
1279
+ if (config.agentId) serializableConfig.agentId = config.agentId;
1280
+ if (config.target) serializableConfig.target = config.target;
1273
1281
  if (config.flowId) serializableConfig.flowId = config.flowId;
1274
1282
  if (shouldEmitParserType) serializableConfig.parserType = parserType;
1275
1283
  if (config.theme) serializableConfig.theme = config.theme;
@@ -1419,6 +1427,8 @@ function generateScriptManualCode(config: any, options?: CodeGeneratorOptions):
1419
1427
 
1420
1428
  if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
1421
1429
  if (config.clientToken) lines.push(` clientToken: "${config.clientToken}",`);
1430
+ if (config.agentId) lines.push(` agentId: "${config.agentId}",`);
1431
+ if (config.target) lines.push(` target: "${config.target}",`);
1422
1432
  if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
1423
1433
  if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
1424
1434
 
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { resolveTarget } from "./target";
3
+
4
+ describe("resolveTarget", () => {
5
+ it("resolves a Runtype agent TypeID to agentId routing", () => {
6
+ expect(resolveTarget("agent_01k")).toEqual({
7
+ kind: "agentId",
8
+ agentId: "agent_01k",
9
+ });
10
+ });
11
+
12
+ it("resolves a Runtype flow TypeID to flowId routing", () => {
13
+ expect(resolveTarget("flow_01k")).toEqual({
14
+ kind: "flowId",
15
+ flowId: "flow_01k",
16
+ });
17
+ });
18
+
19
+ it("accepts an explicit runtype: prefix on a TypeID", () => {
20
+ expect(resolveTarget("runtype:agent_01k")).toEqual({
21
+ kind: "agentId",
22
+ agentId: "agent_01k",
23
+ });
24
+ });
25
+
26
+ it("routes a provider-prefixed target through its registered resolver", () => {
27
+ const resolved = resolveTarget("eve:support", {
28
+ eve: (id) => ({ payload: { assistant: id } }),
29
+ });
30
+ expect(resolved).toEqual({ kind: "payload", payload: { assistant: "support" } });
31
+ });
32
+
33
+ it("uses a default resolver for a bare name when registered", () => {
34
+ const resolved = resolveTarget("support", {
35
+ default: (id) => ({ payload: { target: id } }),
36
+ });
37
+ expect(resolved).toEqual({ kind: "payload", payload: { target: "support" } });
38
+ });
39
+
40
+ it("throws for an unknown provider prefix", () => {
41
+ expect(() => resolveTarget("eve:support")).toThrow(/no target provider/i);
42
+ });
43
+
44
+ it("throws for a bare name with no TypeID and no default resolver", () => {
45
+ expect(() => resolveTarget("support")).toThrow(/support/);
46
+ });
47
+
48
+ it("throws for an empty target", () => {
49
+ expect(() => resolveTarget(" ")).toThrow(/empty/i);
50
+ });
51
+ });
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Target resolution for the normalized, backend-neutral `target` field.
3
+ *
4
+ * `target` is a single string that selects which backend resource a widget
5
+ * talks to, optimized for a browser widget (always serializable, no live
6
+ * objects). Three shapes are supported:
7
+ *
8
+ * - Runtype TypeID (no prefix): `"agent_…"` / `"flow_…"` route to the
9
+ * Runtype agent/flow paths. The TypeID prefix is self-describing, so no
10
+ * wrapper is needed for the common case.
11
+ * - Provider-prefixed: `"<provider>:<id>"` is handed to the matching
12
+ * `targetProviders[provider]` resolver, which returns the dispatch payload
13
+ * fragment for that backend (e.g. `eve`, `langgraph`). `"runtype:…"` is a
14
+ * built-in that re-detects a TypeID.
15
+ * - Bare name: `"support"` requires a `targetProviders.default` resolver,
16
+ * otherwise it throws (a bare name is ambiguous without one).
17
+ *
18
+ * Resolvers are registered, not passed as the value, which keeps `target`
19
+ * itself a plain string that survives script-tag installs, `data-config`,
20
+ * persisted state, and codegen.
21
+ */
22
+
23
+ /** Resolver for a provider-prefixed (or default) target id. */
24
+ export type TargetResolver = (id: string) => { payload: Record<string, unknown> };
25
+
26
+ /** Normalized routing produced from a `target` string. */
27
+ export type ResolvedTarget =
28
+ | { kind: "agentId"; agentId: string }
29
+ | { kind: "flowId"; flowId: string }
30
+ | { kind: "payload"; payload: Record<string, unknown> };
31
+
32
+ const RUNTYPE_AGENT_PREFIX = "agent_";
33
+ const RUNTYPE_FLOW_PREFIX = "flow_";
34
+
35
+ function detectRuntypeTypeId(id: string): ResolvedTarget | null {
36
+ if (id.startsWith(RUNTYPE_AGENT_PREFIX)) return { kind: "agentId", agentId: id };
37
+ if (id.startsWith(RUNTYPE_FLOW_PREFIX)) return { kind: "flowId", flowId: id };
38
+ return null;
39
+ }
40
+
41
+ /**
42
+ * Resolve a `target` string into normalized routing. Pure and synchronous so
43
+ * it can run on the dispatch hot path and be unit-tested in isolation.
44
+ */
45
+ export function resolveTarget(
46
+ target: string,
47
+ targetProviders?: Record<string, TargetResolver>,
48
+ ): ResolvedTarget {
49
+ const trimmed = target.trim();
50
+ if (!trimmed) {
51
+ throw new Error("[Persona] `target` is empty.");
52
+ }
53
+
54
+ const colon = trimmed.indexOf(":");
55
+ if (colon > 0) {
56
+ const prefix = trimmed.slice(0, colon);
57
+ const rest = trimmed.slice(colon + 1);
58
+
59
+ // Built-in: an explicit `runtype:` prefix wrapping a TypeID.
60
+ if (prefix === "runtype") {
61
+ const detected = detectRuntypeTypeId(rest);
62
+ if (detected) return detected;
63
+ throw new Error(
64
+ `[Persona] target "runtype:${rest}" is not a valid Runtype agent_/flow_ id.`,
65
+ );
66
+ }
67
+
68
+ const resolver = targetProviders?.[prefix];
69
+ if (!resolver) {
70
+ throw new Error(
71
+ `[Persona] No target provider registered for "${prefix}". ` +
72
+ `Add a \`targetProviders.${prefix}\` resolver, or use a Runtype agent_/flow_ id.`,
73
+ );
74
+ }
75
+ return { kind: "payload", payload: resolver(rest).payload };
76
+ }
77
+
78
+ // No prefix: a bare Runtype TypeID is self-describing.
79
+ const detected = detectRuntypeTypeId(trimmed);
80
+ if (detected) return detected;
81
+
82
+ // Bare, non-TypeID name: only resolvable via an explicit default resolver.
83
+ const fallback = targetProviders?.default;
84
+ if (fallback) return { kind: "payload", payload: fallback(trimmed).payload };
85
+
86
+ throw new Error(
87
+ `[Persona] target "${trimmed}" has no provider prefix and is not a Runtype agent_/flow_ id. ` +
88
+ `Use "<provider>:${trimmed}", a Runtype TypeID, or register a \`targetProviders.default\` resolver.`,
89
+ );
90
+ }