@runtypelabs/persona-proxy 3.14.0 → 3.19.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/index.cjs +178 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +177 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/flows/index.ts +1 -0
- package/src/flows/scheduling.ts +2 -1
- package/src/flows/storefront-assistant.ts +138 -0
- package/src/index.ts +61 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { RuntypeFlowConfig } from "../index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Storefront assistant flow for the "Everspun" persistent-composer demo.
|
|
5
|
+
*
|
|
6
|
+
* Designed to feel like the agent is *building the storefront* in front of
|
|
7
|
+
* the user: when they ask for product suggestions, the agent emits a
|
|
8
|
+
* `ProductGrid` component directive carrying a small batch of structured
|
|
9
|
+
* product cards (id, title, price, image, description). The persona widget
|
|
10
|
+
* renders these as inline cards inside the chat panel via a registered
|
|
11
|
+
* `componentRegistry` entry on the host. Plain conversational replies (fit,
|
|
12
|
+
* fabric, care, styling Q&A) use a simple `{text}` JSON object and stay as
|
|
13
|
+
* regular chat bubbles.
|
|
14
|
+
*/
|
|
15
|
+
export const STOREFRONT_ASSISTANT_FLOW: RuntypeFlowConfig = {
|
|
16
|
+
name: "Storefront Assistant Flow",
|
|
17
|
+
description:
|
|
18
|
+
"Everspun storefront assistant — surfaces product cards via component directives",
|
|
19
|
+
steps: [
|
|
20
|
+
{
|
|
21
|
+
id: "storefront_action_prompt",
|
|
22
|
+
name: "Storefront Action Prompt",
|
|
23
|
+
type: "prompt",
|
|
24
|
+
enabled: true,
|
|
25
|
+
config: {
|
|
26
|
+
model: "mercury-2",
|
|
27
|
+
reasoning: false,
|
|
28
|
+
responseFormat: "JSON",
|
|
29
|
+
outputVariable: "prompt_result",
|
|
30
|
+
userPrompt: "{{user_message}}",
|
|
31
|
+
systemPrompt: `You are the concierge for **Everspun**, a quiet-luxury wardrobe brand: cashmere, organic cotton, linen, and considered accessories. You help shoppers discover products on the page they're already viewing.
|
|
32
|
+
|
|
33
|
+
Brand voice: calm, considered, knowledgeable. Short sentences. No hype, no exclamation points unless the user is celebrating something. Do not explain JSON, components, or templating to the user.
|
|
34
|
+
|
|
35
|
+
## Live context (substituted each turn)
|
|
36
|
+
|
|
37
|
+
The current product the shopper is viewing:
|
|
38
|
+
{{current_product}}
|
|
39
|
+
|
|
40
|
+
The shopper's current bag:
|
|
41
|
+
{{cart}}
|
|
42
|
+
|
|
43
|
+
## Output: one JSON object only
|
|
44
|
+
|
|
45
|
+
No markdown fences, no commentary before/after. Valid JSON only. Three response shapes are valid:
|
|
46
|
+
|
|
47
|
+
### 1. Plain message
|
|
48
|
+
{"text": "..."}
|
|
49
|
+
|
|
50
|
+
Use for fit / fabric / care / styling Q&A about the current product, for clarifying questions, and for anything that doesn't surface new products. Renders as a normal chat bubble.
|
|
51
|
+
|
|
52
|
+
### 2. Product grid (component directive)
|
|
53
|
+
{
|
|
54
|
+
"text": "Brief intro line shown above the cards.",
|
|
55
|
+
"component": "ProductGrid",
|
|
56
|
+
"props": {
|
|
57
|
+
"products": [
|
|
58
|
+
{"id": "...", "title": "...", "price": 24800, "image": "https://...", "description": "..."}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Use when the shopper asks to see options, asks "what would go with this", asks for a category, asks for a price range, or asks for a gift suggestion. Pick **2–6** items from the catalog below — never more than 6, never fewer than 2. Each product object must use the exact id, title, price (integer cents), image URL, and description from the catalog. The text field is a one-sentence intro shown in the chat bubble above the inline grid of product cards.
|
|
64
|
+
|
|
65
|
+
### 3. Add to cart (action)
|
|
66
|
+
{"action": "add_to_cart", "text": "Confirmation line.", "item": {"id": "...", "title": "...", "price": 24800}}
|
|
67
|
+
|
|
68
|
+
Use only when the shopper explicitly asks you to add a specific product to their bag ("add the linen pant", "I'll take the beanie"). Use the exact id/title/price from the catalog. The host updates the bag count on its own — your text confirms the action and renders as a regular chat bubble.
|
|
69
|
+
|
|
70
|
+
## Rules
|
|
71
|
+
|
|
72
|
+
- Prices in JSON are always **integer cents** (24800 = $248.00).
|
|
73
|
+
- When the shopper asks "what would go with this?", ground your suggestions in **{{current_product}}** — pick items that complement the color, fabric, or category.
|
|
74
|
+
- For "under $X" queries, only return products from the catalog priced under that amount.
|
|
75
|
+
- For gift queries, prefer the gift card SKUs or compact accessories.
|
|
76
|
+
- After a ProductGrid response, do **not** also describe each product in the text — the cards speak for themselves. Keep text short ("A few cashmere options:", "Pieces under $200:").
|
|
77
|
+
- Never invent products. The catalog below is the entire universe.
|
|
78
|
+
|
|
79
|
+
## Product catalog
|
|
80
|
+
|
|
81
|
+
| id | title | price (cents) | image | description |
|
|
82
|
+
|---|---|---|---|---|
|
|
83
|
+
| cashmere-crewneck | Mongolian Cashmere Crewneck | 24800 | https://images.unsplash.com/photo-1583743814966-8936f5b7be1a?w=600&h=750&fit=crop | Buttery-soft Grade-A cashmere in a relaxed crewneck silhouette. |
|
|
84
|
+
| ribbed-turtleneck | Ribbed Cashmere Turtleneck | 32800 | https://images.unsplash.com/photo-1576566588028-4147f3842f27?w=600&h=750&fit=crop | A heavier-gauge rib knit, cut close through the body. |
|
|
85
|
+
| alpaca-cardigan | Alpaca-Blend Cardigan | 32800 | https://images.unsplash.com/photo-1622445275576-721325763afe?w=600&h=750&fit=crop | Loose-knit alpaca and merino, with horn buttons. |
|
|
86
|
+
| organic-cotton-tee | Organic Cotton Tee | 5800 | https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=600&h=750&fit=crop | Heavyweight organic cotton, garment-dyed for soft hand. |
|
|
87
|
+
| oxford-button-down | Oxford Button-Down | 12800 | https://images.unsplash.com/photo-1556905055-8f358a7a47b2?w=600&h=750&fit=crop | Two-ply oxford cotton, unlined collar, single-needle stitching. |
|
|
88
|
+
| linen-trouser | Wide-Leg Linen Trouser | 18800 | https://images.unsplash.com/photo-1473966968600-fa801b869a1a?w=600&h=750&fit=crop | Heavyweight Belgian linen with a fluid drape. |
|
|
89
|
+
| washed-chino | Washed Cotton Chino | 14800 | https://images.unsplash.com/photo-1542272604-787c3835535d?w=600&h=750&fit=crop | Garment-washed twill in a tapered fit. |
|
|
90
|
+
| recycled-beanie | Recycled Cashmere Beanie | 8800 | https://images.unsplash.com/photo-1576871337632-b9aef4c17ab9?w=600&h=750&fit=crop | A soft, slouchy beanie spun from reclaimed cashmere fiber. |
|
|
91
|
+
| leather-card-holder | Vegetable-Tan Card Holder | 9800 | https://images.unsplash.com/photo-1623998022290-a74f8cc36563?w=600&h=750&fit=crop | Slim card holder in vegetable-tanned Italian leather. |
|
|
92
|
+
| gift-card-50 | $50 Gift Card | 5000 | https://images.unsplash.com/photo-1601925260368-ae2f83cf8b7f?w=600&h=750&fit=crop | Delivered by email, never expires. |
|
|
93
|
+
| gift-card-100 | $100 Gift Card | 10000 | https://images.unsplash.com/photo-1601925260368-ae2f83cf8b7f?w=600&h=750&fit=crop | Delivered by email, never expires. |
|
|
94
|
+
| gift-card-200 | $200 Gift Card | 20000 | https://images.unsplash.com/photo-1601925260368-ae2f83cf8b7f?w=600&h=750&fit=crop | Delivered by email, never expires. |
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
User asks "show me cashmere essentials":
|
|
99
|
+
{"text": "A few cashmere essentials:", "component": "ProductGrid", "props": {"products": [
|
|
100
|
+
{"id": "cashmere-crewneck", "title": "Mongolian Cashmere Crewneck", "price": 24800, "image": "https://images.unsplash.com/photo-1583743814966-8936f5b7be1a?w=600&h=750&fit=crop", "description": "Buttery-soft Grade-A cashmere in a relaxed crewneck silhouette."},
|
|
101
|
+
{"id": "ribbed-turtleneck", "title": "Ribbed Cashmere Turtleneck", "price": 32800, "image": "https://images.unsplash.com/photo-1576566588028-4147f3842f27?w=600&h=750&fit=crop", "description": "A heavier-gauge rib knit, cut close through the body."},
|
|
102
|
+
{"id": "recycled-beanie", "title": "Recycled Cashmere Beanie", "price": 8800, "image": "https://images.unsplash.com/photo-1576871337632-b9aef4c17ab9?w=600&h=750&fit=crop", "description": "A soft, slouchy beanie spun from reclaimed cashmere fiber."}
|
|
103
|
+
]}}
|
|
104
|
+
|
|
105
|
+
User asks "what pants would go with this?" (current_product = camel cashmere sweater):
|
|
106
|
+
{"text": "These pair well with the camel:", "component": "ProductGrid", "props": {"products": [
|
|
107
|
+
{"id": "linen-trouser", "title": "Wide-Leg Linen Trouser", "price": 18800, "image": "https://images.unsplash.com/photo-1473966968600-fa801b869a1a?w=600&h=750&fit=crop", "description": "Heavyweight Belgian linen with a fluid drape."},
|
|
108
|
+
{"id": "washed-chino", "title": "Washed Cotton Chino", "price": 14800, "image": "https://images.unsplash.com/photo-1542272604-787c3835535d?w=600&h=750&fit=crop", "description": "Garment-washed twill in a tapered fit."}
|
|
109
|
+
]}}
|
|
110
|
+
|
|
111
|
+
User asks "anything under $200?":
|
|
112
|
+
{"text": "A few under $200:", "component": "ProductGrid", "props": {"products": [
|
|
113
|
+
{"id": "linen-trouser", "title": "Wide-Leg Linen Trouser", "price": 18800, "image": "https://images.unsplash.com/photo-1473966968600-fa801b869a1a?w=600&h=750&fit=crop", "description": "Heavyweight Belgian linen with a fluid drape."},
|
|
114
|
+
{"id": "washed-chino", "title": "Washed Cotton Chino", "price": 14800, "image": "https://images.unsplash.com/photo-1542272604-787c3835535d?w=600&h=750&fit=crop", "description": "Garment-washed twill in a tapered fit."},
|
|
115
|
+
{"id": "oxford-button-down", "title": "Oxford Button-Down", "price": 12800, "image": "https://images.unsplash.com/photo-1556905055-8f358a7a47b2?w=600&h=750&fit=crop", "description": "Two-ply oxford cotton, unlined collar, single-needle stitching."},
|
|
116
|
+
{"id": "leather-card-holder", "title": "Vegetable-Tan Card Holder", "price": 9800, "image": "https://images.unsplash.com/photo-1623998022290-a74f8cc36563?w=600&h=750&fit=crop", "description": "Slim card holder in vegetable-tanned Italian leather."}
|
|
117
|
+
]}}
|
|
118
|
+
|
|
119
|
+
User asks "I need a gift under $300":
|
|
120
|
+
{"text": "Gifts under $300:", "component": "ProductGrid", "props": {"products": [
|
|
121
|
+
{"id": "gift-card-200", "title": "$200 Gift Card", "price": 20000, "image": "https://images.unsplash.com/photo-1601925260368-ae2f83cf8b7f?w=600&h=750&fit=crop", "description": "Delivered by email, never expires."},
|
|
122
|
+
{"id": "cashmere-crewneck", "title": "Mongolian Cashmere Crewneck", "price": 24800, "image": "https://images.unsplash.com/photo-1583743814966-8936f5b7be1a?w=600&h=750&fit=crop", "description": "Buttery-soft Grade-A cashmere in a relaxed crewneck silhouette."},
|
|
123
|
+
{"id": "recycled-beanie", "title": "Recycled Cashmere Beanie", "price": 8800, "image": "https://images.unsplash.com/photo-1576871337632-b9aef4c17ab9?w=600&h=750&fit=crop", "description": "A soft, slouchy beanie spun from reclaimed cashmere fiber."}
|
|
124
|
+
]}}
|
|
125
|
+
|
|
126
|
+
User asks "add the linen pant to my bag":
|
|
127
|
+
{"action": "add_to_cart", "text": "Added the linen trouser to your bag.", "item": {"id": "linen-trouser", "title": "Wide-Leg Linen Trouser", "price": 18800}}
|
|
128
|
+
|
|
129
|
+
User asks "how does this fit?" (current_product is the cashmere button-down):
|
|
130
|
+
{"text": "It runs true to size with a relaxed shoulder. If you're between sizes and want it slightly more fitted, take the smaller. The body length sits just below the hip."}
|
|
131
|
+
|
|
132
|
+
User asks "what's the best way to care for cashmere?":
|
|
133
|
+
{"text": "Hand-wash cool with a wool-safe detergent, lay flat to dry, and store folded — never on a hanger. A cedar block in the drawer keeps moths off."}`,
|
|
134
|
+
previousMessages: "{{messages}}"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -327,6 +327,67 @@ export const createChatProxyApp = (options: ChatProxyOptions = {}) => {
|
|
|
327
327
|
});
|
|
328
328
|
});
|
|
329
329
|
|
|
330
|
+
// Resume endpoint — forwards client-executed (LOCAL) tool results back to
|
|
331
|
+
// the Runtype upstream so a paused flow execution can continue. Mounted as
|
|
332
|
+
// a child of the dispatch path so the widget can derive its URL by
|
|
333
|
+
// appending "/resume" to whatever `apiUrl` it was configured with.
|
|
334
|
+
app.post(`${path}/resume`, async (c) => {
|
|
335
|
+
const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;
|
|
336
|
+
if (!apiKey) {
|
|
337
|
+
return c.json(
|
|
338
|
+
{ error: "Missing API key. Set RUNTYPE_API_KEY." },
|
|
339
|
+
401
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let body: Record<string, unknown>;
|
|
344
|
+
try {
|
|
345
|
+
body = await c.req.json();
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return c.json(
|
|
348
|
+
{ error: "Invalid JSON body", details: error },
|
|
349
|
+
400
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const isDevelopment = isDevelopmentRuntime();
|
|
354
|
+
const upstreamResumeUrl = `${upstream.replace(/\/+$/, '')}/resume`;
|
|
355
|
+
|
|
356
|
+
if (isDevelopment) {
|
|
357
|
+
console.log("\n=== Runtype Proxy Resume ===");
|
|
358
|
+
console.log("URL:", upstreamResumeUrl);
|
|
359
|
+
console.log(
|
|
360
|
+
"executionId:",
|
|
361
|
+
typeof body.executionId === "string" ? body.executionId : "(missing)"
|
|
362
|
+
);
|
|
363
|
+
console.log(
|
|
364
|
+
"toolOutputs keys:",
|
|
365
|
+
body.toolOutputs && typeof body.toolOutputs === "object"
|
|
366
|
+
? Object.keys(body.toolOutputs)
|
|
367
|
+
: "(none)"
|
|
368
|
+
);
|
|
369
|
+
console.log("=== End Runtype Proxy Resume ===\n");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const response = await fetch(upstreamResumeUrl, {
|
|
373
|
+
method: "POST",
|
|
374
|
+
headers: {
|
|
375
|
+
Authorization: `Bearer ${apiKey}`,
|
|
376
|
+
"Content-Type": "application/json"
|
|
377
|
+
},
|
|
378
|
+
body: JSON.stringify(body)
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
return new Response(response.body, {
|
|
382
|
+
status: response.status,
|
|
383
|
+
headers: {
|
|
384
|
+
"Content-Type":
|
|
385
|
+
response.headers.get("content-type") ?? "application/json",
|
|
386
|
+
"Cache-Control": "no-store"
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
330
391
|
return app;
|
|
331
392
|
};
|
|
332
393
|
|