@runtypelabs/persona-proxy 3.18.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 +129 -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 +128 -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/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
FORM_DIRECTIVE_FLOW: () => FORM_DIRECTIVE_FLOW,
|
|
27
27
|
SHOPPING_ASSISTANT_FLOW: () => SHOPPING_ASSISTANT_FLOW,
|
|
28
28
|
SHOPPING_ASSISTANT_METADATA_FLOW: () => SHOPPING_ASSISTANT_METADATA_FLOW,
|
|
29
|
+
STOREFRONT_ASSISTANT_FLOW: () => STOREFRONT_ASSISTANT_FLOW,
|
|
29
30
|
createChatProxyApp: () => createChatProxyApp,
|
|
30
31
|
createCheckoutSession: () => createCheckoutSession,
|
|
31
32
|
createVercelHandler: () => createVercelHandler,
|
|
@@ -94,11 +95,12 @@ Each field in the "fields" array should have:
|
|
|
94
95
|
- type (optional): "text", "email", "tel", "date", "time", "textarea", "number" (defaults to "text")
|
|
95
96
|
- placeholder (optional): Placeholder text
|
|
96
97
|
- required (optional): true/false
|
|
98
|
+
- width (optional): "full" or "half" \u2014 pair short related fields side-by-side with "half" (e.g. Phone + Company, City + Zip, First + Last name); use "full" or omit for everything else (especially textareas, emails, and standalone fields). Two consecutive "half" fields render in one row.
|
|
97
99
|
|
|
98
100
|
EXAMPLES:
|
|
99
101
|
|
|
100
102
|
User: "Schedule a demo for me"
|
|
101
|
-
Response: {"text": "I'd be happy to help you schedule a demo! Please fill out the form below:", "component": "DynamicForm", "props": {"title": "Schedule a Demo", "description": "Share your details and we'll follow up with a confirmation.", "fields": [{"label": "Full Name", "type": "text", "required": true}, {"label": "Email", "type": "email", "required": true}, {"label": "Company", "type": "text"}, {"label": "Preferred Date", "type": "date", "required": true}, {"label": "Notes", "type": "textarea", "placeholder": "Any specific topics you'd like to cover?"}], "submit_text": "Request Demo"}}
|
|
103
|
+
Response: {"text": "I'd be happy to help you schedule a demo! Please fill out the form below:", "component": "DynamicForm", "props": {"title": "Schedule a Demo", "description": "Share your details and we'll follow up with a confirmation.", "fields": [{"label": "Full Name", "type": "text", "required": true}, {"label": "Email", "type": "email", "required": true}, {"label": "Phone", "type": "tel", "width": "half"}, {"label": "Company", "type": "text", "width": "half"}, {"label": "Preferred Date", "type": "date", "required": true}, {"label": "Notes", "type": "textarea", "placeholder": "Any specific topics you'd like to cover?"}], "submit_text": "Request Demo"}}
|
|
102
104
|
|
|
103
105
|
User: "What is AI?"
|
|
104
106
|
Response: {"text": "AI (Artificial Intelligence) refers to computer systems designed to perform tasks that typically require human intelligence, such as learning, reasoning, problem-solving, and understanding language."}
|
|
@@ -447,6 +449,131 @@ Cart has sourdough (qty 1) and croissant box (qty 1); user says "pay":
|
|
|
447
449
|
]
|
|
448
450
|
};
|
|
449
451
|
|
|
452
|
+
// src/flows/storefront-assistant.ts
|
|
453
|
+
var STOREFRONT_ASSISTANT_FLOW = {
|
|
454
|
+
name: "Storefront Assistant Flow",
|
|
455
|
+
description: "Everspun storefront assistant \u2014 surfaces product cards via component directives",
|
|
456
|
+
steps: [
|
|
457
|
+
{
|
|
458
|
+
id: "storefront_action_prompt",
|
|
459
|
+
name: "Storefront Action Prompt",
|
|
460
|
+
type: "prompt",
|
|
461
|
+
enabled: true,
|
|
462
|
+
config: {
|
|
463
|
+
model: "mercury-2",
|
|
464
|
+
reasoning: false,
|
|
465
|
+
responseFormat: "JSON",
|
|
466
|
+
outputVariable: "prompt_result",
|
|
467
|
+
userPrompt: "{{user_message}}",
|
|
468
|
+
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.
|
|
469
|
+
|
|
470
|
+
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.
|
|
471
|
+
|
|
472
|
+
## Live context (substituted each turn)
|
|
473
|
+
|
|
474
|
+
The current product the shopper is viewing:
|
|
475
|
+
{{current_product}}
|
|
476
|
+
|
|
477
|
+
The shopper's current bag:
|
|
478
|
+
{{cart}}
|
|
479
|
+
|
|
480
|
+
## Output: one JSON object only
|
|
481
|
+
|
|
482
|
+
No markdown fences, no commentary before/after. Valid JSON only. Three response shapes are valid:
|
|
483
|
+
|
|
484
|
+
### 1. Plain message
|
|
485
|
+
{"text": "..."}
|
|
486
|
+
|
|
487
|
+
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.
|
|
488
|
+
|
|
489
|
+
### 2. Product grid (component directive)
|
|
490
|
+
{
|
|
491
|
+
"text": "Brief intro line shown above the cards.",
|
|
492
|
+
"component": "ProductGrid",
|
|
493
|
+
"props": {
|
|
494
|
+
"products": [
|
|
495
|
+
{"id": "...", "title": "...", "price": 24800, "image": "https://...", "description": "..."}
|
|
496
|
+
]
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
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\u20136** items from the catalog below \u2014 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.
|
|
501
|
+
|
|
502
|
+
### 3. Add to cart (action)
|
|
503
|
+
{"action": "add_to_cart", "text": "Confirmation line.", "item": {"id": "...", "title": "...", "price": 24800}}
|
|
504
|
+
|
|
505
|
+
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 \u2014 your text confirms the action and renders as a regular chat bubble.
|
|
506
|
+
|
|
507
|
+
## Rules
|
|
508
|
+
|
|
509
|
+
- Prices in JSON are always **integer cents** (24800 = $248.00).
|
|
510
|
+
- When the shopper asks "what would go with this?", ground your suggestions in **{{current_product}}** \u2014 pick items that complement the color, fabric, or category.
|
|
511
|
+
- For "under $X" queries, only return products from the catalog priced under that amount.
|
|
512
|
+
- For gift queries, prefer the gift card SKUs or compact accessories.
|
|
513
|
+
- After a ProductGrid response, do **not** also describe each product in the text \u2014 the cards speak for themselves. Keep text short ("A few cashmere options:", "Pieces under $200:").
|
|
514
|
+
- Never invent products. The catalog below is the entire universe.
|
|
515
|
+
|
|
516
|
+
## Product catalog
|
|
517
|
+
|
|
518
|
+
| id | title | price (cents) | image | description |
|
|
519
|
+
|---|---|---|---|---|
|
|
520
|
+
| 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. |
|
|
521
|
+
| 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. |
|
|
522
|
+
| 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. |
|
|
523
|
+
| 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. |
|
|
524
|
+
| 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. |
|
|
525
|
+
| 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. |
|
|
526
|
+
| 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. |
|
|
527
|
+
| 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. |
|
|
528
|
+
| 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. |
|
|
529
|
+
| 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. |
|
|
530
|
+
| 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. |
|
|
531
|
+
| 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. |
|
|
532
|
+
|
|
533
|
+
## Examples
|
|
534
|
+
|
|
535
|
+
User asks "show me cashmere essentials":
|
|
536
|
+
{"text": "A few cashmere essentials:", "component": "ProductGrid", "props": {"products": [
|
|
537
|
+
{"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."},
|
|
538
|
+
{"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."},
|
|
539
|
+
{"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."}
|
|
540
|
+
]}}
|
|
541
|
+
|
|
542
|
+
User asks "what pants would go with this?" (current_product = camel cashmere sweater):
|
|
543
|
+
{"text": "These pair well with the camel:", "component": "ProductGrid", "props": {"products": [
|
|
544
|
+
{"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."},
|
|
545
|
+
{"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."}
|
|
546
|
+
]}}
|
|
547
|
+
|
|
548
|
+
User asks "anything under $200?":
|
|
549
|
+
{"text": "A few under $200:", "component": "ProductGrid", "props": {"products": [
|
|
550
|
+
{"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."},
|
|
551
|
+
{"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."},
|
|
552
|
+
{"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."},
|
|
553
|
+
{"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."}
|
|
554
|
+
]}}
|
|
555
|
+
|
|
556
|
+
User asks "I need a gift under $300":
|
|
557
|
+
{"text": "Gifts under $300:", "component": "ProductGrid", "props": {"products": [
|
|
558
|
+
{"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."},
|
|
559
|
+
{"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."},
|
|
560
|
+
{"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."}
|
|
561
|
+
]}}
|
|
562
|
+
|
|
563
|
+
User asks "add the linen pant to my bag":
|
|
564
|
+
{"action": "add_to_cart", "text": "Added the linen trouser to your bag.", "item": {"id": "linen-trouser", "title": "Wide-Leg Linen Trouser", "price": 18800}}
|
|
565
|
+
|
|
566
|
+
User asks "how does this fit?" (current_product is the cashmere button-down):
|
|
567
|
+
{"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."}
|
|
568
|
+
|
|
569
|
+
User asks "what's the best way to care for cashmere?":
|
|
570
|
+
{"text": "Hand-wash cool with a wool-safe detergent, lay flat to dry, and store folded \u2014 never on a hanger. A cedar block in the drawer keeps moths off."}`,
|
|
571
|
+
previousMessages: "{{messages}}"
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
]
|
|
575
|
+
};
|
|
576
|
+
|
|
450
577
|
// src/utils/stripe.ts
|
|
451
578
|
var STRIPE_API_VERSION = "2026-03-25.dahlia";
|
|
452
579
|
function parseStripeApiErrorBody(body) {
|
|
@@ -826,6 +953,7 @@ var index_default = createChatProxyApp;
|
|
|
826
953
|
FORM_DIRECTIVE_FLOW,
|
|
827
954
|
SHOPPING_ASSISTANT_FLOW,
|
|
828
955
|
SHOPPING_ASSISTANT_METADATA_FLOW,
|
|
956
|
+
STOREFRONT_ASSISTANT_FLOW,
|
|
829
957
|
createChatProxyApp,
|
|
830
958
|
createCheckoutSession,
|
|
831
959
|
createVercelHandler
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/flows/conversational.ts","../src/flows/scheduling.ts","../src/flows/shopping-assistant.ts","../src/flows/components.ts","../src/flows/bakery-assistant.ts","../src/utils/stripe.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport { handle } from \"hono/vercel\";\n\nexport type RuntypeFlowStep = {\n id: string;\n name: string;\n type: string;\n enabled: boolean;\n config: Record<string, unknown>;\n};\n\nexport type RuntypeFlowConfig = {\n name: string;\n description: string;\n steps: RuntypeFlowStep[];\n};\n\ntype RuntimeEnv = Record<string, string | undefined>;\n\n/**\n * Payload for message feedback (upvote/downvote)\n */\nexport type FeedbackPayload = {\n type: \"upvote\" | \"downvote\";\n messageId: string;\n content?: string;\n timestamp?: string;\n sessionId?: string;\n metadata?: Record<string, unknown>;\n};\n\n/**\n * Handler function for processing feedback\n */\nexport type FeedbackHandler = (feedback: FeedbackPayload) => Promise<void> | void;\n\nexport type ChatProxyOptions = {\n upstreamUrl?: string;\n apiKey?: string;\n path?: string;\n allowedOrigins?: string[];\n flowId?: string;\n flowConfig?: RuntypeFlowConfig;\n /**\n * Path for the feedback endpoint (default: \"/api/feedback\")\n */\n feedbackPath?: string;\n /**\n * Custom handler for processing feedback.\n * Use this to store feedback in a database or send to analytics.\n * \n * @example\n * ```ts\n * onFeedback: async (feedback) => {\n * await db.feedback.create({ data: feedback });\n * }\n * ```\n */\n onFeedback?: FeedbackHandler;\n};\n\nconst DEFAULT_ENDPOINT = \"https://api.runtype.com/v1/dispatch\";\nconst DEFAULT_PATH = \"/api/chat/dispatch\";\n\nconst getRuntimeEnv = (): RuntimeEnv | undefined => {\n const maybeProcess = (\n globalThis as typeof globalThis & { process?: { env?: RuntimeEnv } }\n ).process;\n return maybeProcess?.env;\n};\n\n/** True only when `NODE_ENV` is exactly `\"development\"` (unset = production). Safe when `process` is missing (e.g. some Workers runtimes). */\nconst isDevelopmentRuntime = (): boolean =>\n getRuntimeEnv()?.NODE_ENV === \"development\";\n\nconst DEFAULT_FLOW: RuntypeFlowConfig = {\n name: \"Streaming Prompt Flow\",\n description: \"Streaming chat generated by the widget\",\n steps: [\n {\n id: \"widget_prompt\",\n name: \"Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n responseFormat: \"markdown\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: \"you are a helpful assistant, chatting with a user\",\n // tools: {\n // toolIds: [\n // \"builtin:dalle\"\n // ]\n // },\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n\nconst withCors =\n (allowedOrigins: string[] | undefined) =>\n async (c: Context, next: () => Promise<void>) => {\n const origin = c.req.header(\"origin\");\n const isDevelopment = isDevelopmentRuntime();\n \n // Determine the CORS origin to allow\n let corsOrigin: string;\n if (!allowedOrigins || allowedOrigins.length === 0) {\n // No restrictions - allow any origin (or use the request origin)\n corsOrigin = origin || \"*\";\n } else if (allowedOrigins.includes(origin || \"\")) {\n // Origin is in the allowed list\n corsOrigin = origin || \"*\";\n } else if (isDevelopment && origin) {\n // In development, allow the actual origin even if not in the list\n // This helps with local development where ports might vary\n corsOrigin = origin;\n } else {\n // Production: origin not allowed - reject by not setting CORS headers\n // Return error for preflight, or continue without CORS headers\n if (c.req.method === \"OPTIONS\") {\n return c.json({ error: \"CORS policy violation: origin not allowed\" }, 403);\n }\n // For non-preflight requests, continue but browser will block due to missing CORS headers\n await next();\n return;\n }\n\n const headers: Record<string, string> = {\n \"Access-Control-Allow-Origin\": corsOrigin,\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n Vary: \"Origin\"\n };\n\n if (c.req.method === \"OPTIONS\") {\n return new Response(null, { status: 204, headers });\n }\n\n await next();\n Object.entries(headers).forEach(([key, value]) =>\n c.header(key, value, { append: false })\n );\n };\n\nexport const createChatProxyApp = (options: ChatProxyOptions = {}) => {\n const app = new Hono();\n const path = options.path ?? DEFAULT_PATH;\n const feedbackPath = options.feedbackPath ?? \"/api/feedback\";\n const upstream = options.upstreamUrl ?? DEFAULT_ENDPOINT;\n\n app.use(\"*\", withCors(options.allowedOrigins));\n\n // Feedback endpoint for collecting upvote/downvote data\n app.post(feedbackPath, async (c) => {\n let payload: FeedbackPayload;\n try {\n payload = await c.req.json();\n } catch (error) {\n return c.json({ error: \"Invalid JSON body\" }, 400);\n }\n\n // Validate payload\n if (!payload.type || ![\"upvote\", \"downvote\"].includes(payload.type)) {\n return c.json(\n { error: \"Invalid feedback type. Must be 'upvote' or 'downvote'\" },\n 400\n );\n }\n if (!payload.messageId) {\n return c.json({ error: \"Missing messageId\" }, 400);\n }\n\n // Add timestamp if not provided\n payload.timestamp = payload.timestamp ?? new Date().toISOString();\n\n const isDevelopment = isDevelopmentRuntime();\n\n if (isDevelopment) {\n console.log(\"\\n=== Feedback Received ===\");\n console.log(\"Type:\", payload.type);\n console.log(\"Message ID:\", payload.messageId);\n console.log(\"Content Length:\", payload.content?.length ?? 0);\n console.log(\"Timestamp:\", payload.timestamp);\n console.log(\"=== End Feedback ===\\n\");\n }\n\n // Call custom handler if provided\n if (options.onFeedback) {\n try {\n await options.onFeedback(payload);\n } catch (error) {\n console.error(\"[Feedback] Handler error:\", error);\n return c.json({ error: \"Feedback handler failed\" }, 500);\n }\n }\n\n return c.json({\n success: true,\n message: \"Feedback recorded\",\n feedback: {\n type: payload.type,\n messageId: payload.messageId,\n timestamp: payload.timestamp\n }\n });\n });\n\n // Chat dispatch endpoint\n app.post(path, async (c) => {\n const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;\n if (!apiKey) {\n return c.json(\n { error: \"Missing API key. Set RUNTYPE_API_KEY.\" },\n 401\n );\n }\n\n let clientPayload: Record<string, unknown>;\n try {\n clientPayload = await c.req.json();\n } catch (error) {\n return c.json(\n { error: \"Invalid JSON body\", details: error },\n 400\n );\n }\n\n const isDevelopment = isDevelopmentRuntime();\n\n // Detect agent mode: if the payload contains an `agent` field, forward it directly\n const isAgentMode = !!clientPayload.agent;\n\n let runtypePayload: Record<string, unknown>;\n\n if (isAgentMode) {\n // Agent dispatch - forward the payload as-is to the upstream API\n runtypePayload = clientPayload;\n } else {\n // Flow dispatch - build the Runtype flow payload\n const messages = (clientPayload.messages ?? []) as Array<{ role: string; content: string; createdAt?: string }>;\n const sortedMessages = [...messages].sort((a, b) => {\n const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return timeA - timeB;\n });\n const formattedMessages = sortedMessages.map((message) => ({\n role: message.role,\n content: message.content\n }));\n\n const flowId = (clientPayload.flowId as string | undefined) ?? options.flowId;\n const flowConfig = options.flowConfig ?? DEFAULT_FLOW;\n\n runtypePayload = {\n record: {\n name: \"Streaming Chat Widget\",\n type: \"standalone\",\n metadata: (clientPayload.metadata as Record<string, unknown>) || {}\n },\n messages: formattedMessages,\n options: {\n streamResponse: true,\n recordMode: \"virtual\",\n flowMode: flowId ? \"existing\" : \"virtual\",\n autoAppendMetadata: false\n }\n };\n\n const clientInputs = clientPayload.inputs;\n if (clientInputs && typeof clientInputs === \"object\" && !Array.isArray(clientInputs)) {\n runtypePayload.inputs = clientInputs;\n }\n\n if (flowId) {\n runtypePayload.flow = { id: flowId };\n } else {\n runtypePayload.flow = flowConfig;\n }\n }\n\n // Development only: do not log key material or full bodies in production.\n if (isDevelopment) {\n console.log(`\\n=== Runtype Proxy Request (${isAgentMode ? \"agent\" : \"flow\"}) ===`);\n console.log(\"URL:\", upstream);\n console.log(\"API Key Used:\", apiKey ? \"Yes\" : \"No\");\n console.log(\"API Key (first 12 chars):\", apiKey ? apiKey.substring(0, 12) : \"N/A\");\n console.log(\"Request Payload:\", JSON.stringify(runtypePayload, null, 2));\n }\n\n const response = await fetch(upstream, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(runtypePayload)\n });\n\n if (isDevelopment) {\n console.log(\"Response Status:\", response.status);\n console.log(\"Response Status Text:\", response.statusText);\n\n // If there's an error, try to read and log the response body\n if (!response.ok) {\n const clonedResponse = response.clone();\n try {\n const errorBody = await clonedResponse.text();\n console.log(\"Error Response Body:\", errorBody);\n } catch (e) {\n console.log(\"Could not read error response body:\", e);\n }\n }\n console.log(\"=== End Runtype Proxy Request ===\\n\");\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"Content-Type\":\n response.headers.get(\"content-type\") ?? \"application/json\",\n \"Cache-Control\": \"no-store\"\n }\n });\n });\n\n // Resume endpoint — forwards client-executed (LOCAL) tool results back to\n // the Runtype upstream so a paused flow execution can continue. Mounted as\n // a child of the dispatch path so the widget can derive its URL by\n // appending \"/resume\" to whatever `apiUrl` it was configured with.\n app.post(`${path}/resume`, async (c) => {\n const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;\n if (!apiKey) {\n return c.json(\n { error: \"Missing API key. Set RUNTYPE_API_KEY.\" },\n 401\n );\n }\n\n let body: Record<string, unknown>;\n try {\n body = await c.req.json();\n } catch (error) {\n return c.json(\n { error: \"Invalid JSON body\", details: error },\n 400\n );\n }\n\n const isDevelopment = isDevelopmentRuntime();\n const upstreamResumeUrl = `${upstream.replace(/\\/+$/, '')}/resume`;\n\n if (isDevelopment) {\n console.log(\"\\n=== Runtype Proxy Resume ===\");\n console.log(\"URL:\", upstreamResumeUrl);\n console.log(\n \"executionId:\",\n typeof body.executionId === \"string\" ? body.executionId : \"(missing)\"\n );\n console.log(\n \"toolOutputs keys:\",\n body.toolOutputs && typeof body.toolOutputs === \"object\"\n ? Object.keys(body.toolOutputs)\n : \"(none)\"\n );\n console.log(\"=== End Runtype Proxy Resume ===\\n\");\n }\n\n const response = await fetch(upstreamResumeUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(body)\n });\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"Content-Type\":\n response.headers.get(\"content-type\") ?? \"application/json\",\n \"Cache-Control\": \"no-store\"\n }\n });\n });\n\n return app;\n};\n\nexport const createVercelHandler = (options?: ChatProxyOptions) =>\n handle(createChatProxyApp(options));\n\n// Export pre-configured flows\nexport * from \"./flows/index.js\";\n\n// Export utility functions\nexport * from \"./utils/index.js\";\n\nexport default createChatProxyApp;\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Basic conversational assistant flow\n * This is the default flow for simple chat interactions\n */\nexport const CONVERSATIONAL_FLOW: RuntypeFlowConfig = {\n name: \"Streaming Prompt Flow\",\n description: \"Streaming chat generated by the widget\",\n steps: [\n {\n id: \"widget_prompt\",\n name: \"Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n responseFormat: \"markdown\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: \"you are a helpful assistant, chatting with a user\",\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Dynamic Form flow configuration\n * This flow returns forms as component directives for the widget to render\n */\nexport const FORM_DIRECTIVE_FLOW: RuntypeFlowConfig = {\n name: \"Dynamic Form Flow\",\n description: \"Returns dynamic forms as component directives\",\n steps: [\n {\n id: \"form_prompt\",\n name: \"Form Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful assistant that can have conversations and collect user information via forms.\n\nRESPONSE FORMAT:\nAlways respond with valid JSON. Choose the appropriate format:\n\n1. For CONVERSATIONAL responses or text answers:\n {\"text\": \"Your response here\"}\n\n2. When the user wants to SCHEDULE, BOOK, SIGN UP, or provide DETAILS (show a form):\n {\"component\": \"DynamicForm\", \"props\": {\"title\": \"Form Title\", \"description\": \"Optional description\", \"fields\": [...], \"submit_text\": \"Submit\"}}\n\n3. For BOTH explanation AND form:\n {\"text\": \"Your explanation\", \"component\": \"DynamicForm\", \"props\": {...}}\n\nFORM FIELD FORMAT:\nEach field in the \"fields\" array should have:\n- label (required): Display name for the field\n- name (optional): Field identifier (defaults to lowercase label with underscores)\n- type (optional): \"text\", \"email\", \"tel\", \"date\", \"time\", \"textarea\", \"number\" (defaults to \"text\")\n- placeholder (optional): Placeholder text\n- required (optional): true/false\n\nEXAMPLES:\n\nUser: \"Schedule a demo for me\"\nResponse: {\"text\": \"I'd be happy to help you schedule a demo! Please fill out the form below:\", \"component\": \"DynamicForm\", \"props\": {\"title\": \"Schedule a Demo\", \"description\": \"Share your details and we'll follow up with a confirmation.\", \"fields\": [{\"label\": \"Full Name\", \"type\": \"text\", \"required\": true}, {\"label\": \"Email\", \"type\": \"email\", \"required\": true}, {\"label\": \"Company\", \"type\": \"text\"}, {\"label\": \"Preferred Date\", \"type\": \"date\", \"required\": true}, {\"label\": \"Notes\", \"type\": \"textarea\", \"placeholder\": \"Any specific topics you'd like to cover?\"}], \"submit_text\": \"Request Demo\"}}\n\nUser: \"What is AI?\"\nResponse: {\"text\": \"AI (Artificial Intelligence) refers to computer systems designed to perform tasks that typically require human intelligence, such as learning, reasoning, problem-solving, and understanding language.\"}\n\nUser: \"Collect my contact details\"\nResponse: {\"component\": \"DynamicForm\", \"props\": {\"title\": \"Contact Details\", \"fields\": [{\"label\": \"Name\", \"type\": \"text\", \"required\": true}, {\"label\": \"Email\", \"type\": \"email\", \"required\": true}, {\"label\": \"Phone\", \"type\": \"tel\"}], \"submit_text\": \"Save Details\"}}\n\nIMPORTANT:\n- Use {\"text\": \"...\"} for questions, explanations, and general conversation\n- Show a DynamicForm when user wants to provide information, schedule, book, or sign up\n- Create contextually appropriate form fields based on what the user is trying to do\n- Keep forms focused with only the relevant fields needed`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Shopping assistant flow configuration\n * This flow returns JSON actions for page interaction including:\n * - Simple messages\n * - Navigation with messages\n * - Element clicks with messages\n * - Stripe checkout\n */\nexport const SHOPPING_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Shopping Assistant Flow\",\n description: \"Returns JSON actions for page interaction\",\n steps: [\n {\n id: \"action_prompt\",\n name: \"Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant that can interact with web pages.\nYou will receive information about the current page's elements (class names and text content)\nand user messages. You must respond with JSON in one of these formats:\n\n1. Simple message:\n{\n \"action\": \"message\",\n \"text\": \"Your response text here\"\n}\n\n2. Navigate then show message (for navigation to another page):\n{\n \"action\": \"nav_then_click\",\n \"page\": \"http://site.com/page-url\",\n \"on_load_text\": \"Message to show after navigation\"\n}\n\n3. Show message and click an element:\n{\n \"action\": \"message_and_click\",\n \"element\": \".className-of-element\",\n \"text\": \"Your message text\"\n}\n\n4. Create Stripe checkout:\n{\n \"action\": \"checkout\",\n \"text\": \"Your message text\",\n \"items\": [\n {\"name\": \"Product Name\", \"price\": 2999, \"quantity\": 1}\n ]\n}\n\nGuidelines:\n- Use \"message\" for simple conversational responses\n- Use \"nav_then_click\" when you need to navigate to a different page (like a product detail page)\n- Use \"message_and_click\" when you want to click a button or element on the current page\n- Use \"checkout\" when the user wants to proceed to checkout/payment. Include items array with name (string), price (number in cents), and quantity (number)\n- When selecting elements, use the class names provided in the page context\n- Always respond with valid JSON only, no additional text\n- For product searches, format results as markdown links: [Product Name](url)\n- Be helpful and conversational in your messages\n- Product prices: Black Shirt - Medium: $29.99 (2999 cents), Blue Shirt - Large: $34.99 (3499 cents), Red T-Shirt - Small: $19.99 (1999 cents), Jeans - Medium: $49.99 (4999 cents)\n\nExample conversation flow:\n- User: \"I am looking for a black shirt in medium\"\n- You: {\"action\": \"message\", \"text\": \"Here are the products I found:\\\\n1. [Black Shirt - Medium](/products.html?product=black-shirt-medium) - $29.99\\\\n2. [Blue Shirt - Large](/products.html?product=blue-shirt-large) - $34.99\\\\n3. [Red T-Shirt - Small](/products.html?product=red-tshirt-small) - $19.99\\\\n4. [Jeans - Medium](/products.html?product=jeans-medium) - $49.99\\\\n\\\\nWould you like me to navigate to the first result and add it to your cart?\"}\n\n- User: \"No, I would like to add another shirt to the cart\"\n- You: {\"action\": \"message_and_click\", \"element\": \".AddToCartButton-blue-shirt-large\", \"text\": \"I've added the Blue Shirt - Large to your cart. Ready to checkout?\"}\n\n- User: \"yes\"\n- You: {\"action\": \"checkout\", \"text\": \"Perfect! I'll set up the checkout for you.\", \"items\": [{\"name\": \"Black Shirt - Medium\", \"price\": 2999, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n\n/**\n * Metadata-based shopping assistant flow configuration\n * This flow uses DOM context from record metadata instead of user message.\n * The metadata should include dom_elements, dom_body, page_url, and page_title.\n */\nexport const SHOPPING_ASSISTANT_METADATA_FLOW: RuntypeFlowConfig = {\n name: \"Metadata-Based Shopping Assistant\",\n description: \"Uses DOM context from record metadata for page interaction\",\n steps: [\n {\n id: \"metadata_action_prompt\",\n name: \"Metadata Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant that can interact with web pages.\n\nIMPORTANT: You have access to the current page's DOM elements through the record metadata, which includes:\n- dom_elements: Array of page elements with className, innerText, and tagName\n- dom_body: Complete HTML body of the page (if provided)\n- page_url: Current page URL\n- page_title: Page title\n\nThe dom_elements array provides information about clickable elements and their text content.\nUse this metadata to understand what's available on the page and help users interact with it.\n\nYou must respond with JSON in one of these formats:\n\n1. Simple message:\n{\n \"action\": \"message\",\n \"text\": \"Your response text here\"\n}\n\n2. Navigate then show message (for navigation to another page):\n{\n \"action\": \"nav_then_click\",\n \"page\": \"http://site.com/page-url\",\n \"on_load_text\": \"Message to show after navigation\"\n}\n\n3. Show message and click an element:\n{\n \"action\": \"message_and_click\",\n \"element\": \".className-of-element\",\n \"text\": \"Your message text\"\n}\n\n4. Create Stripe checkout:\n{\n \"action\": \"checkout\",\n \"text\": \"Your message text\",\n \"items\": [\n {\"name\": \"Product Name\", \"price\": 2999, \"quantity\": 1}\n ]\n}\n\nGuidelines:\n- Use \"message\" for simple conversational responses\n- Use \"nav_then_click\" when you need to navigate to a different page (like a product detail page)\n- Use \"message_and_click\" when you want to click a button or element on the current page\n- Use \"checkout\" when the user wants to proceed to checkout/payment. Include items array with name (string), price (number in cents), and quantity (number)\n- When selecting elements, use the class names from the dom_elements in the metadata\n- Always respond with valid JSON only, no additional text\n- For product searches, format results as markdown links: [Product Name](url)\n- Be helpful and conversational in your messages\n- Product prices: Black Shirt - Medium: $29.99 (2999 cents), Blue Shirt - Large: $34.99 (3499 cents), Red T-Shirt - Small: $19.99 (1999 cents), Jeans - Medium: $49.99 (4999 cents)\n\nExample conversation flow:\n- User: \"I am looking for a black shirt in medium\"\n- You: {\"action\": \"message\", \"text\": \"Here are the products I found:\\\\n1. [Black Shirt - Medium](/products.html?product=black-shirt-medium) - $29.99\\\\n2. [Blue Shirt - Large](/products.html?product=blue-shirt-large) - $34.99\\\\n3. [Red T-Shirt - Small](/products.html?product=red-tshirt-small) - $19.99\\\\n4. [Jeans - Medium](/products.html?product=jeans-medium) - $49.99\\\\n\\\\nWould you like me to navigate to the first result and add it to your cart?\"}\n\n- User: \"No, I would like to add another shirt to the cart\"\n- You: {\"action\": \"message_and_click\", \"element\": \".AddToCartButton-blue-shirt-large\", \"text\": \"I've added the Blue Shirt - Large to your cart. Ready to checkout?\"}\n\n- User: \"yes\"\n- You: {\"action\": \"checkout\", \"text\": \"Perfect! I'll set up the checkout for you.\", \"items\": [{\"name\": \"Black Shirt - Medium\", \"price\": 2999, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Component-aware flow for custom component rendering\n * This flow instructs the AI to respond with component directives in JSON format\n */\nexport const COMPONENT_FLOW: RuntypeFlowConfig = {\n name: \"Component Flow\",\n description: \"Flow configured for custom component rendering\",\n steps: [\n {\n id: \"component_prompt\",\n name: \"Component Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful assistant that can both have conversations and render custom UI components.\n\nRESPONSE FORMAT:\nAlways respond with valid JSON. Choose the appropriate format based on the user's request:\n\n1. For CONVERSATIONAL questions or text responses:\n {\"text\": \"Your response here\"}\n\n2. For VISUAL DISPLAYS or when the user asks to SHOW/DISPLAY something:\n {\"component\": \"ComponentName\", \"props\": {...}}\n\n3. For BOTH explanation AND visual:\n {\"text\": \"Your explanation here\", \"component\": \"ComponentName\", \"props\": {...}}\n\nAvailable components for visual displays:\n- ProductCard: Display product information. Props: title (string), price (number), description (string, optional), image (string, optional)\n- SimpleChart: Display a bar chart. Props: title (string), data (array of numbers), labels (array of strings, optional)\n- StatusBadge: Display a status badge. Props: status (string: \"success\", \"error\", \"warning\", \"info\", \"pending\"), message (string)\n- InfoCard: Display an information card. Props: title (string), content (string), icon (string, optional)\n\nExamples:\n- User asks \"What is the capital of France?\": {\"text\": \"The capital of France is Paris.\"}\n- User asks \"What does that chart show?\": {\"text\": \"The chart shows sales data increasing from 100 to 200 over three months.\"}\n- User asks \"Show me a product card\": {\"component\": \"ProductCard\", \"props\": {\"title\": \"Laptop\", \"price\": 999, \"description\": \"A great laptop\"}}\n- User asks \"Display a chart\": {\"component\": \"SimpleChart\", \"props\": {\"title\": \"Sales\", \"data\": [100, 150, 200], \"labels\": [\"Jan\", \"Feb\", \"Mar\"]}}\n- User asks \"Show me a chart and explain it\": {\"text\": \"Here's the sales data for Q1:\", \"component\": \"SimpleChart\", \"props\": {\"title\": \"Q1 Sales\", \"data\": [100, 150, 200], \"labels\": [\"Jan\", \"Feb\", \"Mar\"]}}\n\nIMPORTANT:\n- Use {\"text\": \"...\"} for questions, explanations, discussions, and general chat\n- Use {\"component\": \"...\", \"props\": {...}} ONLY when the user explicitly wants to SEE/VIEW/DISPLAY visual content\n- You can combine both: {\"text\": \"...\", \"component\": \"...\", \"props\": {...}} when you want to explain something AND show a visual\n- Never force a component when the user just wants information`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Bakery assistant flow configuration for \"Flour & Stone\" bakery demo\n * This flow returns JSON actions for page interaction including:\n * - Simple messages with bakery brand voice\n * - Navigation to bakery pages\n * - Add to cart interactions\n * - Stripe checkout\n *\n * Designed to guide users toward the gift card when asking for gift recommendations.\n */\nexport const BAKERY_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Bakery Assistant Flow\",\n description: \"Flour & Stone bakery shopping assistant with gift recommendations\",\n steps: [\n {\n id: \"bakery_action_prompt\",\n name: \"Bakery Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant for Flour & Stone, a premium artisan bakery known for traditional bread-making and exceptional pastries.\n\nBrand voice: Warm, knowledgeable, passionate about craft baking. Use phrases like \"fresh from the oven\", \"handcrafted with care\", \"artisan tradition\". Do not explain selectors, JSON, or templating to the user.\n\n## Live context (request inputs — substituted each turn)\n\nThe widget sends **only** these keys as dispatch **inputs** (nothing extra on the record for this demo).\n\n**Orientation**\n- Path: {{current_page}} (compare before nav_then_click; e.g. /bakery-goods.html)\n- Full URL: {{page_url}}\n- Title: {{page_title}}\n\n**Page DOM**\n- page_elements: JSON array of enriched nodes (selector, tagName, text, role, interactivity, attributes including data-*). Prefer **selector** for message_and_click when you click a specific control.\n- page_context: Same slice formatted for the LLM (structured card summaries when matched, then groups by interactivity).\n\n{{page_elements}}\n\n{{page_context}}\n\n**Cart (for checkout — mirror cart.items when user pays)**\n{{cart}}\n\n**Recent order (if any)**\n{{recent_order}}\n\nIf {{current_page}} already equals the page you would navigate to, use {\"action\":\"message\",...} instead of nav_then_click.\n\n## Discovering products\n\nUse {{page_context}} for a quick scan and {{page_elements}} for exact selectors and attributes. Product rows often include data-product in **attributes**; prices appear in **text**; add-to-cart controls are usually **clickable** with stable **selector** values.\n\n## Output: one JSON object only\n\nNo markdown fences, no commentary before/after. Valid JSON only.\n\n### 1. message\n{\"action\": \"message\", \"text\": \"...\"}\nUse for chat, clarifying questions, \"we're already on that page\", or when you need the user to choose (e.g. $25 vs $50 gift card).\n\n### 2. nav_then_click\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"...\"}\nUse root-relative paths starting with /. Only when current_page is different from the target. This **only** changes pages — it does **not** open Stripe or payment.\n\n### 3. add_to_cart\n{\"action\": \"add_to_cart\", \"text\": \"...\", \"item\": {\"id\": \"product-id\", \"name\": \"Product Name\", \"price\": 1200}}\nUse when adding from context without scrolling (optional; on goods page prefer scroll_then_add).\n\n### 4. scroll_then_add (preferred on /bakery-goods.html)\n{\"action\": \"scroll_then_add\", \"text\": \"...\", \"item\": {\"id\": \"...\", \"name\": \"...\", \"price\": 1200}}\nScrolls the product into view then adds one unit (cart merges duplicate ids into quantity).\n\n### 5. checkout → Stripe (this demo)\n{\"action\": \"checkout\", \"text\": \"Brief message\", \"items\": [{\"name\": \"...\", \"price\": 1200, \"quantity\": 2}, ...]}\n**Only** this action starts hosted checkout (Stripe). **Never** use nav_then_click to a \"/checkout\" URL for payment here.\nRequirements: cart in context must have items; **items array must list every cart line** with the same name, cent prices, and quantities as cart.items. If cart is null or empty, use message — do not checkout.\n\n### 6. message_and_click (rare)\nIf page_elements show a specific button selector and scroll_then_add is wrong, you may use message_and_click with a CSS selector — prefer scroll_then_add on bakery-goods.html.\n\n## Rules\n\n- Prices in JSON are always **integer cents** (1200 = $12.00).\n- After adding to cart, invite checkout or more shopping.\n- On checkout confirmation (\"yes\", \"checkout\", \"pay\", \"proceed\", etc.), build **items** from **cart.items** (all rows, correct quantity). Do not drop lines or invent prices.\n\n## Product catalog (ids and cent prices)\n\n- Sourdough Loaf: sourdough-loaf, 1200\n- Croissant Box (6): croissant-box, 2400\n- Cinnamon Rolls (4): cinnamon-rolls, 1800\n- Baguette Trio: baguette-trio, 900\n- Almond Tart: almond-tart, 800\n- Fruit Danish: fruit-danish, 600\n- $50 Gift Card: gift-card-50, 5000\n- $25 Gift Card: gift-card-25, 2500\n\n## Examples\n\nGift seeker on /bakery-locations.html:\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"Here are our goods! You'll find our gift cards below — $50 is our most popular. Want me to add one?\"}\n\nOn /bakery-goods.html, user wants $50 gift card:\n{\"action\": \"scroll_then_add\", \"text\": \"Added the $50 gift card. Ready to check out?\", \"item\": {\"id\": \"gift-card-50\", \"name\": \"$50 Gift Card\", \"price\": 5000}}\n\nUser on /bakery.html agrees to see products:\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"Here are our handcrafted goods — what sounds good today?\"}\n\nOn /bakery-goods.html, add sourdough:\n{\"action\": \"scroll_then_add\", \"text\": \"Sourdough is in your cart. Anything else, or shall we check out?\", \"item\": {\"id\": \"sourdough-loaf\", \"name\": \"Sourdough Loaf\", \"price\": 1200}}\n\nCart has one $50 gift card; user says yes to checkout:\n{\"action\": \"checkout\", \"text\": \"Opening secure checkout...\", \"items\": [{\"name\": \"$50 Gift Card\", \"price\": 5000, \"quantity\": 1}]}\n\nCart has sourdough (qty 1) and croissant box (qty 1); user says \"pay\":\n{\"action\": \"checkout\", \"text\": \"Taking you to checkout...\", \"items\": [{\"name\": \"Sourdough Loaf\", \"price\": 1200, \"quantity\": 1}, {\"name\": \"Croissant Box (6)\", \"price\": 2400, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","/**\n * Stripe checkout helpers using the REST API\n * This approach works on all platforms including Cloudflare Workers, Vercel Edge, etc.\n */\n\n/**\n * Pinned API version for raw HTTP calls (no SDK). Required for organization API keys and\n * keeps behavior stable across accounts. See https://docs.stripe.com/api/versioning\n */\nconst STRIPE_API_VERSION = \"2026-03-25.dahlia\";\n\nexport interface CheckoutItem {\n name: string;\n price: number; // Price in cents\n quantity: number;\n}\n\nexport interface CreateCheckoutSessionOptions {\n secretKey: string;\n items: CheckoutItem[];\n successUrl: string;\n cancelUrl: string;\n /**\n * Target account for organization API keys (`sk_org_…`), e.g. `acct_1abc…` or\n * `acct_platform/acct_connected` per Stripe. Required with org keys.\n * @see https://docs.stripe.com/keys#organization-api-keys\n */\n stripeContext?: string;\n}\n\nexport interface CheckoutSessionResponse {\n success: boolean;\n checkoutUrl?: string;\n sessionId?: string;\n error?: string;\n}\n\nfunction parseStripeApiErrorBody(body: string): string | undefined {\n try {\n const parsed = JSON.parse(body) as {\n error?: { message?: string; type?: string };\n };\n const msg = parsed?.error?.message;\n return typeof msg === \"string\" && msg.length > 0 ? msg : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Creates a Stripe checkout session using the REST API\n * @param options - Checkout session configuration\n * @returns Checkout session response with URL and session ID\n */\nexport async function createCheckoutSession(\n options: CreateCheckoutSessionOptions\n): Promise<CheckoutSessionResponse> {\n const { secretKey, items, successUrl, cancelUrl, stripeContext } = options;\n const trimmedContext = stripeContext?.trim() || undefined;\n\n try {\n if (secretKey.startsWith(\"sk_org\") && !trimmedContext) {\n return {\n success: false,\n error:\n \"Organization Stripe keys (sk_org_…) require stripeContext / STRIPE_CONTEXT with the target account (e.g. acct_…). See https://docs.stripe.com/keys#organization-api-keys\",\n };\n }\n\n // Validate items\n if (!items || !Array.isArray(items) || items.length === 0) {\n return {\n success: false,\n error: \"Items array is required\"\n };\n }\n\n for (const item of items) {\n if (!item.name || typeof item.price !== \"number\" || typeof item.quantity !== \"number\") {\n return {\n success: false,\n error: \"Each item must have name (string), price (number in cents), and quantity (number)\"\n };\n }\n if (\n !Number.isFinite(item.price) ||\n !Number.isInteger(item.price) ||\n item.price < 1 ||\n !Number.isInteger(item.quantity) ||\n item.quantity < 1\n ) {\n return {\n success: false,\n error:\n \"Each item needs a positive integer price (cents) and quantity (Stripe rejects decimals or zero)\",\n };\n }\n }\n\n // Build line items for URL encoding\n const lineItems = items.map((item) => ({\n price_data: {\n currency: \"usd\",\n product_data: {\n name: item.name,\n },\n unit_amount: item.price,\n },\n quantity: item.quantity,\n }));\n\n // Convert line items to URL-encoded format\n const params = new URLSearchParams({\n \"payment_method_types[0]\": \"card\",\n \"mode\": \"payment\",\n \"success_url\": successUrl,\n \"cancel_url\": cancelUrl,\n });\n\n // Add line items to params\n lineItems.forEach((item, index) => {\n params.append(`line_items[${index}][price_data][currency]`, item.price_data.currency);\n params.append(`line_items[${index}][price_data][product_data][name]`, item.price_data.product_data.name);\n params.append(`line_items[${index}][price_data][unit_amount]`, item.price_data.unit_amount.toString());\n params.append(`line_items[${index}][quantity]`, item.quantity.toString());\n });\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${secretKey}`,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"Stripe-Version\": STRIPE_API_VERSION,\n };\n if (trimmedContext) {\n headers[\"Stripe-Context\"] = trimmedContext;\n }\n\n // Create Stripe checkout session using REST API\n const stripeResponse = await fetch(\"https://api.stripe.com/v1/checkout/sessions\", {\n method: \"POST\",\n headers,\n body: params,\n });\n\n if (!stripeResponse.ok) {\n const errorData = await stripeResponse.text();\n const stripeMessage = parseStripeApiErrorBody(errorData);\n console.error(\"Stripe API error:\", errorData);\n return {\n success: false,\n error: stripeMessage ?? \"Failed to create checkout session\",\n };\n }\n\n const session = await stripeResponse.json() as { url: string; id: string };\n\n return {\n success: true,\n checkoutUrl: session.url,\n sessionId: session.id,\n };\n } catch (error) {\n console.error(\"Stripe checkout error:\", error);\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Failed to create checkout session\"\n };\n }\n}\n"],"mappings":"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","names":["_a","_b","_c"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/flows/conversational.ts","../src/flows/scheduling.ts","../src/flows/shopping-assistant.ts","../src/flows/components.ts","../src/flows/bakery-assistant.ts","../src/flows/storefront-assistant.ts","../src/utils/stripe.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport { handle } from \"hono/vercel\";\n\nexport type RuntypeFlowStep = {\n id: string;\n name: string;\n type: string;\n enabled: boolean;\n config: Record<string, unknown>;\n};\n\nexport type RuntypeFlowConfig = {\n name: string;\n description: string;\n steps: RuntypeFlowStep[];\n};\n\ntype RuntimeEnv = Record<string, string | undefined>;\n\n/**\n * Payload for message feedback (upvote/downvote)\n */\nexport type FeedbackPayload = {\n type: \"upvote\" | \"downvote\";\n messageId: string;\n content?: string;\n timestamp?: string;\n sessionId?: string;\n metadata?: Record<string, unknown>;\n};\n\n/**\n * Handler function for processing feedback\n */\nexport type FeedbackHandler = (feedback: FeedbackPayload) => Promise<void> | void;\n\nexport type ChatProxyOptions = {\n upstreamUrl?: string;\n apiKey?: string;\n path?: string;\n allowedOrigins?: string[];\n flowId?: string;\n flowConfig?: RuntypeFlowConfig;\n /**\n * Path for the feedback endpoint (default: \"/api/feedback\")\n */\n feedbackPath?: string;\n /**\n * Custom handler for processing feedback.\n * Use this to store feedback in a database or send to analytics.\n * \n * @example\n * ```ts\n * onFeedback: async (feedback) => {\n * await db.feedback.create({ data: feedback });\n * }\n * ```\n */\n onFeedback?: FeedbackHandler;\n};\n\nconst DEFAULT_ENDPOINT = \"https://api.runtype.com/v1/dispatch\";\nconst DEFAULT_PATH = \"/api/chat/dispatch\";\n\nconst getRuntimeEnv = (): RuntimeEnv | undefined => {\n const maybeProcess = (\n globalThis as typeof globalThis & { process?: { env?: RuntimeEnv } }\n ).process;\n return maybeProcess?.env;\n};\n\n/** True only when `NODE_ENV` is exactly `\"development\"` (unset = production). Safe when `process` is missing (e.g. some Workers runtimes). */\nconst isDevelopmentRuntime = (): boolean =>\n getRuntimeEnv()?.NODE_ENV === \"development\";\n\nconst DEFAULT_FLOW: RuntypeFlowConfig = {\n name: \"Streaming Prompt Flow\",\n description: \"Streaming chat generated by the widget\",\n steps: [\n {\n id: \"widget_prompt\",\n name: \"Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n responseFormat: \"markdown\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: \"you are a helpful assistant, chatting with a user\",\n // tools: {\n // toolIds: [\n // \"builtin:dalle\"\n // ]\n // },\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n\nconst withCors =\n (allowedOrigins: string[] | undefined) =>\n async (c: Context, next: () => Promise<void>) => {\n const origin = c.req.header(\"origin\");\n const isDevelopment = isDevelopmentRuntime();\n \n // Determine the CORS origin to allow\n let corsOrigin: string;\n if (!allowedOrigins || allowedOrigins.length === 0) {\n // No restrictions - allow any origin (or use the request origin)\n corsOrigin = origin || \"*\";\n } else if (allowedOrigins.includes(origin || \"\")) {\n // Origin is in the allowed list\n corsOrigin = origin || \"*\";\n } else if (isDevelopment && origin) {\n // In development, allow the actual origin even if not in the list\n // This helps with local development where ports might vary\n corsOrigin = origin;\n } else {\n // Production: origin not allowed - reject by not setting CORS headers\n // Return error for preflight, or continue without CORS headers\n if (c.req.method === \"OPTIONS\") {\n return c.json({ error: \"CORS policy violation: origin not allowed\" }, 403);\n }\n // For non-preflight requests, continue but browser will block due to missing CORS headers\n await next();\n return;\n }\n\n const headers: Record<string, string> = {\n \"Access-Control-Allow-Origin\": corsOrigin,\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n Vary: \"Origin\"\n };\n\n if (c.req.method === \"OPTIONS\") {\n return new Response(null, { status: 204, headers });\n }\n\n await next();\n Object.entries(headers).forEach(([key, value]) =>\n c.header(key, value, { append: false })\n );\n };\n\nexport const createChatProxyApp = (options: ChatProxyOptions = {}) => {\n const app = new Hono();\n const path = options.path ?? DEFAULT_PATH;\n const feedbackPath = options.feedbackPath ?? \"/api/feedback\";\n const upstream = options.upstreamUrl ?? DEFAULT_ENDPOINT;\n\n app.use(\"*\", withCors(options.allowedOrigins));\n\n // Feedback endpoint for collecting upvote/downvote data\n app.post(feedbackPath, async (c) => {\n let payload: FeedbackPayload;\n try {\n payload = await c.req.json();\n } catch (error) {\n return c.json({ error: \"Invalid JSON body\" }, 400);\n }\n\n // Validate payload\n if (!payload.type || ![\"upvote\", \"downvote\"].includes(payload.type)) {\n return c.json(\n { error: \"Invalid feedback type. Must be 'upvote' or 'downvote'\" },\n 400\n );\n }\n if (!payload.messageId) {\n return c.json({ error: \"Missing messageId\" }, 400);\n }\n\n // Add timestamp if not provided\n payload.timestamp = payload.timestamp ?? new Date().toISOString();\n\n const isDevelopment = isDevelopmentRuntime();\n\n if (isDevelopment) {\n console.log(\"\\n=== Feedback Received ===\");\n console.log(\"Type:\", payload.type);\n console.log(\"Message ID:\", payload.messageId);\n console.log(\"Content Length:\", payload.content?.length ?? 0);\n console.log(\"Timestamp:\", payload.timestamp);\n console.log(\"=== End Feedback ===\\n\");\n }\n\n // Call custom handler if provided\n if (options.onFeedback) {\n try {\n await options.onFeedback(payload);\n } catch (error) {\n console.error(\"[Feedback] Handler error:\", error);\n return c.json({ error: \"Feedback handler failed\" }, 500);\n }\n }\n\n return c.json({\n success: true,\n message: \"Feedback recorded\",\n feedback: {\n type: payload.type,\n messageId: payload.messageId,\n timestamp: payload.timestamp\n }\n });\n });\n\n // Chat dispatch endpoint\n app.post(path, async (c) => {\n const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;\n if (!apiKey) {\n return c.json(\n { error: \"Missing API key. Set RUNTYPE_API_KEY.\" },\n 401\n );\n }\n\n let clientPayload: Record<string, unknown>;\n try {\n clientPayload = await c.req.json();\n } catch (error) {\n return c.json(\n { error: \"Invalid JSON body\", details: error },\n 400\n );\n }\n\n const isDevelopment = isDevelopmentRuntime();\n\n // Detect agent mode: if the payload contains an `agent` field, forward it directly\n const isAgentMode = !!clientPayload.agent;\n\n let runtypePayload: Record<string, unknown>;\n\n if (isAgentMode) {\n // Agent dispatch - forward the payload as-is to the upstream API\n runtypePayload = clientPayload;\n } else {\n // Flow dispatch - build the Runtype flow payload\n const messages = (clientPayload.messages ?? []) as Array<{ role: string; content: string; createdAt?: string }>;\n const sortedMessages = [...messages].sort((a, b) => {\n const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return timeA - timeB;\n });\n const formattedMessages = sortedMessages.map((message) => ({\n role: message.role,\n content: message.content\n }));\n\n const flowId = (clientPayload.flowId as string | undefined) ?? options.flowId;\n const flowConfig = options.flowConfig ?? DEFAULT_FLOW;\n\n runtypePayload = {\n record: {\n name: \"Streaming Chat Widget\",\n type: \"standalone\",\n metadata: (clientPayload.metadata as Record<string, unknown>) || {}\n },\n messages: formattedMessages,\n options: {\n streamResponse: true,\n recordMode: \"virtual\",\n flowMode: flowId ? \"existing\" : \"virtual\",\n autoAppendMetadata: false\n }\n };\n\n const clientInputs = clientPayload.inputs;\n if (clientInputs && typeof clientInputs === \"object\" && !Array.isArray(clientInputs)) {\n runtypePayload.inputs = clientInputs;\n }\n\n if (flowId) {\n runtypePayload.flow = { id: flowId };\n } else {\n runtypePayload.flow = flowConfig;\n }\n }\n\n // Development only: do not log key material or full bodies in production.\n if (isDevelopment) {\n console.log(`\\n=== Runtype Proxy Request (${isAgentMode ? \"agent\" : \"flow\"}) ===`);\n console.log(\"URL:\", upstream);\n console.log(\"API Key Used:\", apiKey ? \"Yes\" : \"No\");\n console.log(\"API Key (first 12 chars):\", apiKey ? apiKey.substring(0, 12) : \"N/A\");\n console.log(\"Request Payload:\", JSON.stringify(runtypePayload, null, 2));\n }\n\n const response = await fetch(upstream, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(runtypePayload)\n });\n\n if (isDevelopment) {\n console.log(\"Response Status:\", response.status);\n console.log(\"Response Status Text:\", response.statusText);\n\n // If there's an error, try to read and log the response body\n if (!response.ok) {\n const clonedResponse = response.clone();\n try {\n const errorBody = await clonedResponse.text();\n console.log(\"Error Response Body:\", errorBody);\n } catch (e) {\n console.log(\"Could not read error response body:\", e);\n }\n }\n console.log(\"=== End Runtype Proxy Request ===\\n\");\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"Content-Type\":\n response.headers.get(\"content-type\") ?? \"application/json\",\n \"Cache-Control\": \"no-store\"\n }\n });\n });\n\n // Resume endpoint — forwards client-executed (LOCAL) tool results back to\n // the Runtype upstream so a paused flow execution can continue. Mounted as\n // a child of the dispatch path so the widget can derive its URL by\n // appending \"/resume\" to whatever `apiUrl` it was configured with.\n app.post(`${path}/resume`, async (c) => {\n const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;\n if (!apiKey) {\n return c.json(\n { error: \"Missing API key. Set RUNTYPE_API_KEY.\" },\n 401\n );\n }\n\n let body: Record<string, unknown>;\n try {\n body = await c.req.json();\n } catch (error) {\n return c.json(\n { error: \"Invalid JSON body\", details: error },\n 400\n );\n }\n\n const isDevelopment = isDevelopmentRuntime();\n const upstreamResumeUrl = `${upstream.replace(/\\/+$/, '')}/resume`;\n\n if (isDevelopment) {\n console.log(\"\\n=== Runtype Proxy Resume ===\");\n console.log(\"URL:\", upstreamResumeUrl);\n console.log(\n \"executionId:\",\n typeof body.executionId === \"string\" ? body.executionId : \"(missing)\"\n );\n console.log(\n \"toolOutputs keys:\",\n body.toolOutputs && typeof body.toolOutputs === \"object\"\n ? Object.keys(body.toolOutputs)\n : \"(none)\"\n );\n console.log(\"=== End Runtype Proxy Resume ===\\n\");\n }\n\n const response = await fetch(upstreamResumeUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(body)\n });\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"Content-Type\":\n response.headers.get(\"content-type\") ?? \"application/json\",\n \"Cache-Control\": \"no-store\"\n }\n });\n });\n\n return app;\n};\n\nexport const createVercelHandler = (options?: ChatProxyOptions) =>\n handle(createChatProxyApp(options));\n\n// Export pre-configured flows\nexport * from \"./flows/index.js\";\n\n// Export utility functions\nexport * from \"./utils/index.js\";\n\nexport default createChatProxyApp;\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Basic conversational assistant flow\n * This is the default flow for simple chat interactions\n */\nexport const CONVERSATIONAL_FLOW: RuntypeFlowConfig = {\n name: \"Streaming Prompt Flow\",\n description: \"Streaming chat generated by the widget\",\n steps: [\n {\n id: \"widget_prompt\",\n name: \"Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n responseFormat: \"markdown\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: \"you are a helpful assistant, chatting with a user\",\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Dynamic Form flow configuration\n * This flow returns forms as component directives for the widget to render\n */\nexport const FORM_DIRECTIVE_FLOW: RuntypeFlowConfig = {\n name: \"Dynamic Form Flow\",\n description: \"Returns dynamic forms as component directives\",\n steps: [\n {\n id: \"form_prompt\",\n name: \"Form Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful assistant that can have conversations and collect user information via forms.\n\nRESPONSE FORMAT:\nAlways respond with valid JSON. Choose the appropriate format:\n\n1. For CONVERSATIONAL responses or text answers:\n {\"text\": \"Your response here\"}\n\n2. When the user wants to SCHEDULE, BOOK, SIGN UP, or provide DETAILS (show a form):\n {\"component\": \"DynamicForm\", \"props\": {\"title\": \"Form Title\", \"description\": \"Optional description\", \"fields\": [...], \"submit_text\": \"Submit\"}}\n\n3. For BOTH explanation AND form:\n {\"text\": \"Your explanation\", \"component\": \"DynamicForm\", \"props\": {...}}\n\nFORM FIELD FORMAT:\nEach field in the \"fields\" array should have:\n- label (required): Display name for the field\n- name (optional): Field identifier (defaults to lowercase label with underscores)\n- type (optional): \"text\", \"email\", \"tel\", \"date\", \"time\", \"textarea\", \"number\" (defaults to \"text\")\n- placeholder (optional): Placeholder text\n- required (optional): true/false\n- width (optional): \"full\" or \"half\" — pair short related fields side-by-side with \"half\" (e.g. Phone + Company, City + Zip, First + Last name); use \"full\" or omit for everything else (especially textareas, emails, and standalone fields). Two consecutive \"half\" fields render in one row.\n\nEXAMPLES:\n\nUser: \"Schedule a demo for me\"\nResponse: {\"text\": \"I'd be happy to help you schedule a demo! Please fill out the form below:\", \"component\": \"DynamicForm\", \"props\": {\"title\": \"Schedule a Demo\", \"description\": \"Share your details and we'll follow up with a confirmation.\", \"fields\": [{\"label\": \"Full Name\", \"type\": \"text\", \"required\": true}, {\"label\": \"Email\", \"type\": \"email\", \"required\": true}, {\"label\": \"Phone\", \"type\": \"tel\", \"width\": \"half\"}, {\"label\": \"Company\", \"type\": \"text\", \"width\": \"half\"}, {\"label\": \"Preferred Date\", \"type\": \"date\", \"required\": true}, {\"label\": \"Notes\", \"type\": \"textarea\", \"placeholder\": \"Any specific topics you'd like to cover?\"}], \"submit_text\": \"Request Demo\"}}\n\nUser: \"What is AI?\"\nResponse: {\"text\": \"AI (Artificial Intelligence) refers to computer systems designed to perform tasks that typically require human intelligence, such as learning, reasoning, problem-solving, and understanding language.\"}\n\nUser: \"Collect my contact details\"\nResponse: {\"component\": \"DynamicForm\", \"props\": {\"title\": \"Contact Details\", \"fields\": [{\"label\": \"Name\", \"type\": \"text\", \"required\": true}, {\"label\": \"Email\", \"type\": \"email\", \"required\": true}, {\"label\": \"Phone\", \"type\": \"tel\"}], \"submit_text\": \"Save Details\"}}\n\nIMPORTANT:\n- Use {\"text\": \"...\"} for questions, explanations, and general conversation\n- Show a DynamicForm when user wants to provide information, schedule, book, or sign up\n- Create contextually appropriate form fields based on what the user is trying to do\n- Keep forms focused with only the relevant fields needed`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Shopping assistant flow configuration\n * This flow returns JSON actions for page interaction including:\n * - Simple messages\n * - Navigation with messages\n * - Element clicks with messages\n * - Stripe checkout\n */\nexport const SHOPPING_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Shopping Assistant Flow\",\n description: \"Returns JSON actions for page interaction\",\n steps: [\n {\n id: \"action_prompt\",\n name: \"Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant that can interact with web pages.\nYou will receive information about the current page's elements (class names and text content)\nand user messages. You must respond with JSON in one of these formats:\n\n1. Simple message:\n{\n \"action\": \"message\",\n \"text\": \"Your response text here\"\n}\n\n2. Navigate then show message (for navigation to another page):\n{\n \"action\": \"nav_then_click\",\n \"page\": \"http://site.com/page-url\",\n \"on_load_text\": \"Message to show after navigation\"\n}\n\n3. Show message and click an element:\n{\n \"action\": \"message_and_click\",\n \"element\": \".className-of-element\",\n \"text\": \"Your message text\"\n}\n\n4. Create Stripe checkout:\n{\n \"action\": \"checkout\",\n \"text\": \"Your message text\",\n \"items\": [\n {\"name\": \"Product Name\", \"price\": 2999, \"quantity\": 1}\n ]\n}\n\nGuidelines:\n- Use \"message\" for simple conversational responses\n- Use \"nav_then_click\" when you need to navigate to a different page (like a product detail page)\n- Use \"message_and_click\" when you want to click a button or element on the current page\n- Use \"checkout\" when the user wants to proceed to checkout/payment. Include items array with name (string), price (number in cents), and quantity (number)\n- When selecting elements, use the class names provided in the page context\n- Always respond with valid JSON only, no additional text\n- For product searches, format results as markdown links: [Product Name](url)\n- Be helpful and conversational in your messages\n- Product prices: Black Shirt - Medium: $29.99 (2999 cents), Blue Shirt - Large: $34.99 (3499 cents), Red T-Shirt - Small: $19.99 (1999 cents), Jeans - Medium: $49.99 (4999 cents)\n\nExample conversation flow:\n- User: \"I am looking for a black shirt in medium\"\n- You: {\"action\": \"message\", \"text\": \"Here are the products I found:\\\\n1. [Black Shirt - Medium](/products.html?product=black-shirt-medium) - $29.99\\\\n2. [Blue Shirt - Large](/products.html?product=blue-shirt-large) - $34.99\\\\n3. [Red T-Shirt - Small](/products.html?product=red-tshirt-small) - $19.99\\\\n4. [Jeans - Medium](/products.html?product=jeans-medium) - $49.99\\\\n\\\\nWould you like me to navigate to the first result and add it to your cart?\"}\n\n- User: \"No, I would like to add another shirt to the cart\"\n- You: {\"action\": \"message_and_click\", \"element\": \".AddToCartButton-blue-shirt-large\", \"text\": \"I've added the Blue Shirt - Large to your cart. Ready to checkout?\"}\n\n- User: \"yes\"\n- You: {\"action\": \"checkout\", \"text\": \"Perfect! I'll set up the checkout for you.\", \"items\": [{\"name\": \"Black Shirt - Medium\", \"price\": 2999, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n\n/**\n * Metadata-based shopping assistant flow configuration\n * This flow uses DOM context from record metadata instead of user message.\n * The metadata should include dom_elements, dom_body, page_url, and page_title.\n */\nexport const SHOPPING_ASSISTANT_METADATA_FLOW: RuntypeFlowConfig = {\n name: \"Metadata-Based Shopping Assistant\",\n description: \"Uses DOM context from record metadata for page interaction\",\n steps: [\n {\n id: \"metadata_action_prompt\",\n name: \"Metadata Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant that can interact with web pages.\n\nIMPORTANT: You have access to the current page's DOM elements through the record metadata, which includes:\n- dom_elements: Array of page elements with className, innerText, and tagName\n- dom_body: Complete HTML body of the page (if provided)\n- page_url: Current page URL\n- page_title: Page title\n\nThe dom_elements array provides information about clickable elements and their text content.\nUse this metadata to understand what's available on the page and help users interact with it.\n\nYou must respond with JSON in one of these formats:\n\n1. Simple message:\n{\n \"action\": \"message\",\n \"text\": \"Your response text here\"\n}\n\n2. Navigate then show message (for navigation to another page):\n{\n \"action\": \"nav_then_click\",\n \"page\": \"http://site.com/page-url\",\n \"on_load_text\": \"Message to show after navigation\"\n}\n\n3. Show message and click an element:\n{\n \"action\": \"message_and_click\",\n \"element\": \".className-of-element\",\n \"text\": \"Your message text\"\n}\n\n4. Create Stripe checkout:\n{\n \"action\": \"checkout\",\n \"text\": \"Your message text\",\n \"items\": [\n {\"name\": \"Product Name\", \"price\": 2999, \"quantity\": 1}\n ]\n}\n\nGuidelines:\n- Use \"message\" for simple conversational responses\n- Use \"nav_then_click\" when you need to navigate to a different page (like a product detail page)\n- Use \"message_and_click\" when you want to click a button or element on the current page\n- Use \"checkout\" when the user wants to proceed to checkout/payment. Include items array with name (string), price (number in cents), and quantity (number)\n- When selecting elements, use the class names from the dom_elements in the metadata\n- Always respond with valid JSON only, no additional text\n- For product searches, format results as markdown links: [Product Name](url)\n- Be helpful and conversational in your messages\n- Product prices: Black Shirt - Medium: $29.99 (2999 cents), Blue Shirt - Large: $34.99 (3499 cents), Red T-Shirt - Small: $19.99 (1999 cents), Jeans - Medium: $49.99 (4999 cents)\n\nExample conversation flow:\n- User: \"I am looking for a black shirt in medium\"\n- You: {\"action\": \"message\", \"text\": \"Here are the products I found:\\\\n1. [Black Shirt - Medium](/products.html?product=black-shirt-medium) - $29.99\\\\n2. [Blue Shirt - Large](/products.html?product=blue-shirt-large) - $34.99\\\\n3. [Red T-Shirt - Small](/products.html?product=red-tshirt-small) - $19.99\\\\n4. [Jeans - Medium](/products.html?product=jeans-medium) - $49.99\\\\n\\\\nWould you like me to navigate to the first result and add it to your cart?\"}\n\n- User: \"No, I would like to add another shirt to the cart\"\n- You: {\"action\": \"message_and_click\", \"element\": \".AddToCartButton-blue-shirt-large\", \"text\": \"I've added the Blue Shirt - Large to your cart. Ready to checkout?\"}\n\n- User: \"yes\"\n- You: {\"action\": \"checkout\", \"text\": \"Perfect! I'll set up the checkout for you.\", \"items\": [{\"name\": \"Black Shirt - Medium\", \"price\": 2999, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Component-aware flow for custom component rendering\n * This flow instructs the AI to respond with component directives in JSON format\n */\nexport const COMPONENT_FLOW: RuntypeFlowConfig = {\n name: \"Component Flow\",\n description: \"Flow configured for custom component rendering\",\n steps: [\n {\n id: \"component_prompt\",\n name: \"Component Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful assistant that can both have conversations and render custom UI components.\n\nRESPONSE FORMAT:\nAlways respond with valid JSON. Choose the appropriate format based on the user's request:\n\n1. For CONVERSATIONAL questions or text responses:\n {\"text\": \"Your response here\"}\n\n2. For VISUAL DISPLAYS or when the user asks to SHOW/DISPLAY something:\n {\"component\": \"ComponentName\", \"props\": {...}}\n\n3. For BOTH explanation AND visual:\n {\"text\": \"Your explanation here\", \"component\": \"ComponentName\", \"props\": {...}}\n\nAvailable components for visual displays:\n- ProductCard: Display product information. Props: title (string), price (number), description (string, optional), image (string, optional)\n- SimpleChart: Display a bar chart. Props: title (string), data (array of numbers), labels (array of strings, optional)\n- StatusBadge: Display a status badge. Props: status (string: \"success\", \"error\", \"warning\", \"info\", \"pending\"), message (string)\n- InfoCard: Display an information card. Props: title (string), content (string), icon (string, optional)\n\nExamples:\n- User asks \"What is the capital of France?\": {\"text\": \"The capital of France is Paris.\"}\n- User asks \"What does that chart show?\": {\"text\": \"The chart shows sales data increasing from 100 to 200 over three months.\"}\n- User asks \"Show me a product card\": {\"component\": \"ProductCard\", \"props\": {\"title\": \"Laptop\", \"price\": 999, \"description\": \"A great laptop\"}}\n- User asks \"Display a chart\": {\"component\": \"SimpleChart\", \"props\": {\"title\": \"Sales\", \"data\": [100, 150, 200], \"labels\": [\"Jan\", \"Feb\", \"Mar\"]}}\n- User asks \"Show me a chart and explain it\": {\"text\": \"Here's the sales data for Q1:\", \"component\": \"SimpleChart\", \"props\": {\"title\": \"Q1 Sales\", \"data\": [100, 150, 200], \"labels\": [\"Jan\", \"Feb\", \"Mar\"]}}\n\nIMPORTANT:\n- Use {\"text\": \"...\"} for questions, explanations, discussions, and general chat\n- Use {\"component\": \"...\", \"props\": {...}} ONLY when the user explicitly wants to SEE/VIEW/DISPLAY visual content\n- You can combine both: {\"text\": \"...\", \"component\": \"...\", \"props\": {...}} when you want to explain something AND show a visual\n- Never force a component when the user just wants information`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Bakery assistant flow configuration for \"Flour & Stone\" bakery demo\n * This flow returns JSON actions for page interaction including:\n * - Simple messages with bakery brand voice\n * - Navigation to bakery pages\n * - Add to cart interactions\n * - Stripe checkout\n *\n * Designed to guide users toward the gift card when asking for gift recommendations.\n */\nexport const BAKERY_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Bakery Assistant Flow\",\n description: \"Flour & Stone bakery shopping assistant with gift recommendations\",\n steps: [\n {\n id: \"bakery_action_prompt\",\n name: \"Bakery Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant for Flour & Stone, a premium artisan bakery known for traditional bread-making and exceptional pastries.\n\nBrand voice: Warm, knowledgeable, passionate about craft baking. Use phrases like \"fresh from the oven\", \"handcrafted with care\", \"artisan tradition\". Do not explain selectors, JSON, or templating to the user.\n\n## Live context (request inputs — substituted each turn)\n\nThe widget sends **only** these keys as dispatch **inputs** (nothing extra on the record for this demo).\n\n**Orientation**\n- Path: {{current_page}} (compare before nav_then_click; e.g. /bakery-goods.html)\n- Full URL: {{page_url}}\n- Title: {{page_title}}\n\n**Page DOM**\n- page_elements: JSON array of enriched nodes (selector, tagName, text, role, interactivity, attributes including data-*). Prefer **selector** for message_and_click when you click a specific control.\n- page_context: Same slice formatted for the LLM (structured card summaries when matched, then groups by interactivity).\n\n{{page_elements}}\n\n{{page_context}}\n\n**Cart (for checkout — mirror cart.items when user pays)**\n{{cart}}\n\n**Recent order (if any)**\n{{recent_order}}\n\nIf {{current_page}} already equals the page you would navigate to, use {\"action\":\"message\",...} instead of nav_then_click.\n\n## Discovering products\n\nUse {{page_context}} for a quick scan and {{page_elements}} for exact selectors and attributes. Product rows often include data-product in **attributes**; prices appear in **text**; add-to-cart controls are usually **clickable** with stable **selector** values.\n\n## Output: one JSON object only\n\nNo markdown fences, no commentary before/after. Valid JSON only.\n\n### 1. message\n{\"action\": \"message\", \"text\": \"...\"}\nUse for chat, clarifying questions, \"we're already on that page\", or when you need the user to choose (e.g. $25 vs $50 gift card).\n\n### 2. nav_then_click\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"...\"}\nUse root-relative paths starting with /. Only when current_page is different from the target. This **only** changes pages — it does **not** open Stripe or payment.\n\n### 3. add_to_cart\n{\"action\": \"add_to_cart\", \"text\": \"...\", \"item\": {\"id\": \"product-id\", \"name\": \"Product Name\", \"price\": 1200}}\nUse when adding from context without scrolling (optional; on goods page prefer scroll_then_add).\n\n### 4. scroll_then_add (preferred on /bakery-goods.html)\n{\"action\": \"scroll_then_add\", \"text\": \"...\", \"item\": {\"id\": \"...\", \"name\": \"...\", \"price\": 1200}}\nScrolls the product into view then adds one unit (cart merges duplicate ids into quantity).\n\n### 5. checkout → Stripe (this demo)\n{\"action\": \"checkout\", \"text\": \"Brief message\", \"items\": [{\"name\": \"...\", \"price\": 1200, \"quantity\": 2}, ...]}\n**Only** this action starts hosted checkout (Stripe). **Never** use nav_then_click to a \"/checkout\" URL for payment here.\nRequirements: cart in context must have items; **items array must list every cart line** with the same name, cent prices, and quantities as cart.items. If cart is null or empty, use message — do not checkout.\n\n### 6. message_and_click (rare)\nIf page_elements show a specific button selector and scroll_then_add is wrong, you may use message_and_click with a CSS selector — prefer scroll_then_add on bakery-goods.html.\n\n## Rules\n\n- Prices in JSON are always **integer cents** (1200 = $12.00).\n- After adding to cart, invite checkout or more shopping.\n- On checkout confirmation (\"yes\", \"checkout\", \"pay\", \"proceed\", etc.), build **items** from **cart.items** (all rows, correct quantity). Do not drop lines or invent prices.\n\n## Product catalog (ids and cent prices)\n\n- Sourdough Loaf: sourdough-loaf, 1200\n- Croissant Box (6): croissant-box, 2400\n- Cinnamon Rolls (4): cinnamon-rolls, 1800\n- Baguette Trio: baguette-trio, 900\n- Almond Tart: almond-tart, 800\n- Fruit Danish: fruit-danish, 600\n- $50 Gift Card: gift-card-50, 5000\n- $25 Gift Card: gift-card-25, 2500\n\n## Examples\n\nGift seeker on /bakery-locations.html:\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"Here are our goods! You'll find our gift cards below — $50 is our most popular. Want me to add one?\"}\n\nOn /bakery-goods.html, user wants $50 gift card:\n{\"action\": \"scroll_then_add\", \"text\": \"Added the $50 gift card. Ready to check out?\", \"item\": {\"id\": \"gift-card-50\", \"name\": \"$50 Gift Card\", \"price\": 5000}}\n\nUser on /bakery.html agrees to see products:\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"Here are our handcrafted goods — what sounds good today?\"}\n\nOn /bakery-goods.html, add sourdough:\n{\"action\": \"scroll_then_add\", \"text\": \"Sourdough is in your cart. Anything else, or shall we check out?\", \"item\": {\"id\": \"sourdough-loaf\", \"name\": \"Sourdough Loaf\", \"price\": 1200}}\n\nCart has one $50 gift card; user says yes to checkout:\n{\"action\": \"checkout\", \"text\": \"Opening secure checkout...\", \"items\": [{\"name\": \"$50 Gift Card\", \"price\": 5000, \"quantity\": 1}]}\n\nCart has sourdough (qty 1) and croissant box (qty 1); user says \"pay\":\n{\"action\": \"checkout\", \"text\": \"Taking you to checkout...\", \"items\": [{\"name\": \"Sourdough Loaf\", \"price\": 1200, \"quantity\": 1}, {\"name\": \"Croissant Box (6)\", \"price\": 2400, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Storefront assistant flow for the \"Everspun\" persistent-composer demo.\n *\n * Designed to feel like the agent is *building the storefront* in front of\n * the user: when they ask for product suggestions, the agent emits a\n * `ProductGrid` component directive carrying a small batch of structured\n * product cards (id, title, price, image, description). The persona widget\n * renders these as inline cards inside the chat panel via a registered\n * `componentRegistry` entry on the host. Plain conversational replies (fit,\n * fabric, care, styling Q&A) use a simple `{text}` JSON object and stay as\n * regular chat bubbles.\n */\nexport const STOREFRONT_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Storefront Assistant Flow\",\n description:\n \"Everspun storefront assistant — surfaces product cards via component directives\",\n steps: [\n {\n id: \"storefront_action_prompt\",\n name: \"Storefront Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n 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.\n\nBrand 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.\n\n## Live context (substituted each turn)\n\nThe current product the shopper is viewing:\n{{current_product}}\n\nThe shopper's current bag:\n{{cart}}\n\n## Output: one JSON object only\n\nNo markdown fences, no commentary before/after. Valid JSON only. Three response shapes are valid:\n\n### 1. Plain message\n{\"text\": \"...\"}\n\nUse 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.\n\n### 2. Product grid (component directive)\n{\n \"text\": \"Brief intro line shown above the cards.\",\n \"component\": \"ProductGrid\",\n \"props\": {\n \"products\": [\n {\"id\": \"...\", \"title\": \"...\", \"price\": 24800, \"image\": \"https://...\", \"description\": \"...\"}\n ]\n }\n}\n\nUse 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.\n\n### 3. Add to cart (action)\n{\"action\": \"add_to_cart\", \"text\": \"Confirmation line.\", \"item\": {\"id\": \"...\", \"title\": \"...\", \"price\": 24800}}\n\nUse 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.\n\n## Rules\n\n- Prices in JSON are always **integer cents** (24800 = $248.00).\n- When the shopper asks \"what would go with this?\", ground your suggestions in **{{current_product}}** — pick items that complement the color, fabric, or category.\n- For \"under $X\" queries, only return products from the catalog priced under that amount.\n- For gift queries, prefer the gift card SKUs or compact accessories.\n- 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:\").\n- Never invent products. The catalog below is the entire universe.\n\n## Product catalog\n\n| id | title | price (cents) | image | description |\n|---|---|---|---|---|\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n\n## Examples\n\nUser asks \"show me cashmere essentials\":\n{\"text\": \"A few cashmere essentials:\", \"component\": \"ProductGrid\", \"props\": {\"products\": [\n {\"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.\"},\n {\"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.\"},\n {\"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.\"}\n]}}\n\nUser asks \"what pants would go with this?\" (current_product = camel cashmere sweater):\n{\"text\": \"These pair well with the camel:\", \"component\": \"ProductGrid\", \"props\": {\"products\": [\n {\"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.\"},\n {\"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.\"}\n]}}\n\nUser asks \"anything under $200?\":\n{\"text\": \"A few under $200:\", \"component\": \"ProductGrid\", \"props\": {\"products\": [\n {\"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.\"},\n {\"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.\"},\n {\"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.\"},\n {\"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.\"}\n]}}\n\nUser asks \"I need a gift under $300\":\n{\"text\": \"Gifts under $300:\", \"component\": \"ProductGrid\", \"props\": {\"products\": [\n {\"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.\"},\n {\"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.\"},\n {\"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.\"}\n]}}\n\nUser asks \"add the linen pant to my bag\":\n{\"action\": \"add_to_cart\", \"text\": \"Added the linen trouser to your bag.\", \"item\": {\"id\": \"linen-trouser\", \"title\": \"Wide-Leg Linen Trouser\", \"price\": 18800}}\n\nUser asks \"how does this fit?\" (current_product is the cashmere button-down):\n{\"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.\"}\n\nUser asks \"what's the best way to care for cashmere?\":\n{\"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.\"}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","/**\n * Stripe checkout helpers using the REST API\n * This approach works on all platforms including Cloudflare Workers, Vercel Edge, etc.\n */\n\n/**\n * Pinned API version for raw HTTP calls (no SDK). Required for organization API keys and\n * keeps behavior stable across accounts. See https://docs.stripe.com/api/versioning\n */\nconst STRIPE_API_VERSION = \"2026-03-25.dahlia\";\n\nexport interface CheckoutItem {\n name: string;\n price: number; // Price in cents\n quantity: number;\n}\n\nexport interface CreateCheckoutSessionOptions {\n secretKey: string;\n items: CheckoutItem[];\n successUrl: string;\n cancelUrl: string;\n /**\n * Target account for organization API keys (`sk_org_…`), e.g. `acct_1abc…` or\n * `acct_platform/acct_connected` per Stripe. Required with org keys.\n * @see https://docs.stripe.com/keys#organization-api-keys\n */\n stripeContext?: string;\n}\n\nexport interface CheckoutSessionResponse {\n success: boolean;\n checkoutUrl?: string;\n sessionId?: string;\n error?: string;\n}\n\nfunction parseStripeApiErrorBody(body: string): string | undefined {\n try {\n const parsed = JSON.parse(body) as {\n error?: { message?: string; type?: string };\n };\n const msg = parsed?.error?.message;\n return typeof msg === \"string\" && msg.length > 0 ? msg : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Creates a Stripe checkout session using the REST API\n * @param options - Checkout session configuration\n * @returns Checkout session response with URL and session ID\n */\nexport async function createCheckoutSession(\n options: CreateCheckoutSessionOptions\n): Promise<CheckoutSessionResponse> {\n const { secretKey, items, successUrl, cancelUrl, stripeContext } = options;\n const trimmedContext = stripeContext?.trim() || undefined;\n\n try {\n if (secretKey.startsWith(\"sk_org\") && !trimmedContext) {\n return {\n success: false,\n error:\n \"Organization Stripe keys (sk_org_…) require stripeContext / STRIPE_CONTEXT with the target account (e.g. acct_…). See https://docs.stripe.com/keys#organization-api-keys\",\n };\n }\n\n // Validate items\n if (!items || !Array.isArray(items) || items.length === 0) {\n return {\n success: false,\n error: \"Items array is required\"\n };\n }\n\n for (const item of items) {\n if (!item.name || typeof item.price !== \"number\" || typeof item.quantity !== \"number\") {\n return {\n success: false,\n error: \"Each item must have name (string), price (number in cents), and quantity (number)\"\n };\n }\n if (\n !Number.isFinite(item.price) ||\n !Number.isInteger(item.price) ||\n item.price < 1 ||\n !Number.isInteger(item.quantity) ||\n item.quantity < 1\n ) {\n return {\n success: false,\n error:\n \"Each item needs a positive integer price (cents) and quantity (Stripe rejects decimals or zero)\",\n };\n }\n }\n\n // Build line items for URL encoding\n const lineItems = items.map((item) => ({\n price_data: {\n currency: \"usd\",\n product_data: {\n name: item.name,\n },\n unit_amount: item.price,\n },\n quantity: item.quantity,\n }));\n\n // Convert line items to URL-encoded format\n const params = new URLSearchParams({\n \"payment_method_types[0]\": \"card\",\n \"mode\": \"payment\",\n \"success_url\": successUrl,\n \"cancel_url\": cancelUrl,\n });\n\n // Add line items to params\n lineItems.forEach((item, index) => {\n params.append(`line_items[${index}][price_data][currency]`, item.price_data.currency);\n params.append(`line_items[${index}][price_data][product_data][name]`, item.price_data.product_data.name);\n params.append(`line_items[${index}][price_data][unit_amount]`, item.price_data.unit_amount.toString());\n params.append(`line_items[${index}][quantity]`, item.quantity.toString());\n });\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${secretKey}`,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"Stripe-Version\": STRIPE_API_VERSION,\n };\n if (trimmedContext) {\n headers[\"Stripe-Context\"] = trimmedContext;\n }\n\n // Create Stripe checkout session using REST API\n const stripeResponse = await fetch(\"https://api.stripe.com/v1/checkout/sessions\", {\n method: \"POST\",\n headers,\n body: params,\n });\n\n if (!stripeResponse.ok) {\n const errorData = await stripeResponse.text();\n const stripeMessage = parseStripeApiErrorBody(errorData);\n console.error(\"Stripe API error:\", errorData);\n return {\n success: false,\n error: stripeMessage ?? \"Failed to create checkout session\",\n };\n }\n\n const session = await stripeResponse.json() as { url: string; id: string };\n\n return {\n success: true,\n checkoutUrl: session.url,\n sessionId: session.id,\n };\n } catch (error) {\n console.error(\"Stripe checkout error:\", error);\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Failed to create checkout session\"\n };\n }\n}\n"],"mappings":"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","names":["_a","_b","_c"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -47,6 +47,20 @@ declare const COMPONENT_FLOW: RuntypeFlowConfig;
|
|
|
47
47
|
*/
|
|
48
48
|
declare const BAKERY_ASSISTANT_FLOW: RuntypeFlowConfig;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Storefront assistant flow for the "Everspun" persistent-composer demo.
|
|
52
|
+
*
|
|
53
|
+
* Designed to feel like the agent is *building the storefront* in front of
|
|
54
|
+
* the user: when they ask for product suggestions, the agent emits a
|
|
55
|
+
* `ProductGrid` component directive carrying a small batch of structured
|
|
56
|
+
* product cards (id, title, price, image, description). The persona widget
|
|
57
|
+
* renders these as inline cards inside the chat panel via a registered
|
|
58
|
+
* `componentRegistry` entry on the host. Plain conversational replies (fit,
|
|
59
|
+
* fabric, care, styling Q&A) use a simple `{text}` JSON object and stay as
|
|
60
|
+
* regular chat bubbles.
|
|
61
|
+
*/
|
|
62
|
+
declare const STOREFRONT_ASSISTANT_FLOW: RuntypeFlowConfig;
|
|
63
|
+
|
|
50
64
|
/**
|
|
51
65
|
* Stripe checkout helpers using the REST API
|
|
52
66
|
* This approach works on all platforms including Cloudflare Workers, Vercel Edge, etc.
|
|
@@ -135,4 +149,4 @@ type ChatProxyOptions = {
|
|
|
135
149
|
declare const createChatProxyApp: (options?: ChatProxyOptions) => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
136
150
|
declare const createVercelHandler: (options?: ChatProxyOptions) => (req: Request) => Response | Promise<Response>;
|
|
137
151
|
|
|
138
|
-
export { BAKERY_ASSISTANT_FLOW, COMPONENT_FLOW, CONVERSATIONAL_FLOW, type ChatProxyOptions, type CheckoutItem, type CheckoutSessionResponse, type CreateCheckoutSessionOptions, FORM_DIRECTIVE_FLOW, type FeedbackHandler, type FeedbackPayload, type RuntypeFlowConfig, type RuntypeFlowStep, SHOPPING_ASSISTANT_FLOW, SHOPPING_ASSISTANT_METADATA_FLOW, createChatProxyApp, createCheckoutSession, createVercelHandler, createChatProxyApp as default };
|
|
152
|
+
export { BAKERY_ASSISTANT_FLOW, COMPONENT_FLOW, CONVERSATIONAL_FLOW, type ChatProxyOptions, type CheckoutItem, type CheckoutSessionResponse, type CreateCheckoutSessionOptions, FORM_DIRECTIVE_FLOW, type FeedbackHandler, type FeedbackPayload, type RuntypeFlowConfig, type RuntypeFlowStep, SHOPPING_ASSISTANT_FLOW, SHOPPING_ASSISTANT_METADATA_FLOW, STOREFRONT_ASSISTANT_FLOW, createChatProxyApp, createCheckoutSession, createVercelHandler, createChatProxyApp as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,20 @@ declare const COMPONENT_FLOW: RuntypeFlowConfig;
|
|
|
47
47
|
*/
|
|
48
48
|
declare const BAKERY_ASSISTANT_FLOW: RuntypeFlowConfig;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Storefront assistant flow for the "Everspun" persistent-composer demo.
|
|
52
|
+
*
|
|
53
|
+
* Designed to feel like the agent is *building the storefront* in front of
|
|
54
|
+
* the user: when they ask for product suggestions, the agent emits a
|
|
55
|
+
* `ProductGrid` component directive carrying a small batch of structured
|
|
56
|
+
* product cards (id, title, price, image, description). The persona widget
|
|
57
|
+
* renders these as inline cards inside the chat panel via a registered
|
|
58
|
+
* `componentRegistry` entry on the host. Plain conversational replies (fit,
|
|
59
|
+
* fabric, care, styling Q&A) use a simple `{text}` JSON object and stay as
|
|
60
|
+
* regular chat bubbles.
|
|
61
|
+
*/
|
|
62
|
+
declare const STOREFRONT_ASSISTANT_FLOW: RuntypeFlowConfig;
|
|
63
|
+
|
|
50
64
|
/**
|
|
51
65
|
* Stripe checkout helpers using the REST API
|
|
52
66
|
* This approach works on all platforms including Cloudflare Workers, Vercel Edge, etc.
|
|
@@ -135,4 +149,4 @@ type ChatProxyOptions = {
|
|
|
135
149
|
declare const createChatProxyApp: (options?: ChatProxyOptions) => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
136
150
|
declare const createVercelHandler: (options?: ChatProxyOptions) => (req: Request) => Response | Promise<Response>;
|
|
137
151
|
|
|
138
|
-
export { BAKERY_ASSISTANT_FLOW, COMPONENT_FLOW, CONVERSATIONAL_FLOW, type ChatProxyOptions, type CheckoutItem, type CheckoutSessionResponse, type CreateCheckoutSessionOptions, FORM_DIRECTIVE_FLOW, type FeedbackHandler, type FeedbackPayload, type RuntypeFlowConfig, type RuntypeFlowStep, SHOPPING_ASSISTANT_FLOW, SHOPPING_ASSISTANT_METADATA_FLOW, createChatProxyApp, createCheckoutSession, createVercelHandler, createChatProxyApp as default };
|
|
152
|
+
export { BAKERY_ASSISTANT_FLOW, COMPONENT_FLOW, CONVERSATIONAL_FLOW, type ChatProxyOptions, type CheckoutItem, type CheckoutSessionResponse, type CreateCheckoutSessionOptions, FORM_DIRECTIVE_FLOW, type FeedbackHandler, type FeedbackPayload, type RuntypeFlowConfig, type RuntypeFlowStep, SHOPPING_ASSISTANT_FLOW, SHOPPING_ASSISTANT_METADATA_FLOW, STOREFRONT_ASSISTANT_FLOW, createChatProxyApp, createCheckoutSession, createVercelHandler, createChatProxyApp as default };
|
package/dist/index.js
CHANGED
|
@@ -61,11 +61,12 @@ Each field in the "fields" array should have:
|
|
|
61
61
|
- type (optional): "text", "email", "tel", "date", "time", "textarea", "number" (defaults to "text")
|
|
62
62
|
- placeholder (optional): Placeholder text
|
|
63
63
|
- required (optional): true/false
|
|
64
|
+
- width (optional): "full" or "half" \u2014 pair short related fields side-by-side with "half" (e.g. Phone + Company, City + Zip, First + Last name); use "full" or omit for everything else (especially textareas, emails, and standalone fields). Two consecutive "half" fields render in one row.
|
|
64
65
|
|
|
65
66
|
EXAMPLES:
|
|
66
67
|
|
|
67
68
|
User: "Schedule a demo for me"
|
|
68
|
-
Response: {"text": "I'd be happy to help you schedule a demo! Please fill out the form below:", "component": "DynamicForm", "props": {"title": "Schedule a Demo", "description": "Share your details and we'll follow up with a confirmation.", "fields": [{"label": "Full Name", "type": "text", "required": true}, {"label": "Email", "type": "email", "required": true}, {"label": "Company", "type": "text"}, {"label": "Preferred Date", "type": "date", "required": true}, {"label": "Notes", "type": "textarea", "placeholder": "Any specific topics you'd like to cover?"}], "submit_text": "Request Demo"}}
|
|
69
|
+
Response: {"text": "I'd be happy to help you schedule a demo! Please fill out the form below:", "component": "DynamicForm", "props": {"title": "Schedule a Demo", "description": "Share your details and we'll follow up with a confirmation.", "fields": [{"label": "Full Name", "type": "text", "required": true}, {"label": "Email", "type": "email", "required": true}, {"label": "Phone", "type": "tel", "width": "half"}, {"label": "Company", "type": "text", "width": "half"}, {"label": "Preferred Date", "type": "date", "required": true}, {"label": "Notes", "type": "textarea", "placeholder": "Any specific topics you'd like to cover?"}], "submit_text": "Request Demo"}}
|
|
69
70
|
|
|
70
71
|
User: "What is AI?"
|
|
71
72
|
Response: {"text": "AI (Artificial Intelligence) refers to computer systems designed to perform tasks that typically require human intelligence, such as learning, reasoning, problem-solving, and understanding language."}
|
|
@@ -414,6 +415,131 @@ Cart has sourdough (qty 1) and croissant box (qty 1); user says "pay":
|
|
|
414
415
|
]
|
|
415
416
|
};
|
|
416
417
|
|
|
418
|
+
// src/flows/storefront-assistant.ts
|
|
419
|
+
var STOREFRONT_ASSISTANT_FLOW = {
|
|
420
|
+
name: "Storefront Assistant Flow",
|
|
421
|
+
description: "Everspun storefront assistant \u2014 surfaces product cards via component directives",
|
|
422
|
+
steps: [
|
|
423
|
+
{
|
|
424
|
+
id: "storefront_action_prompt",
|
|
425
|
+
name: "Storefront Action Prompt",
|
|
426
|
+
type: "prompt",
|
|
427
|
+
enabled: true,
|
|
428
|
+
config: {
|
|
429
|
+
model: "mercury-2",
|
|
430
|
+
reasoning: false,
|
|
431
|
+
responseFormat: "JSON",
|
|
432
|
+
outputVariable: "prompt_result",
|
|
433
|
+
userPrompt: "{{user_message}}",
|
|
434
|
+
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.
|
|
435
|
+
|
|
436
|
+
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.
|
|
437
|
+
|
|
438
|
+
## Live context (substituted each turn)
|
|
439
|
+
|
|
440
|
+
The current product the shopper is viewing:
|
|
441
|
+
{{current_product}}
|
|
442
|
+
|
|
443
|
+
The shopper's current bag:
|
|
444
|
+
{{cart}}
|
|
445
|
+
|
|
446
|
+
## Output: one JSON object only
|
|
447
|
+
|
|
448
|
+
No markdown fences, no commentary before/after. Valid JSON only. Three response shapes are valid:
|
|
449
|
+
|
|
450
|
+
### 1. Plain message
|
|
451
|
+
{"text": "..."}
|
|
452
|
+
|
|
453
|
+
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.
|
|
454
|
+
|
|
455
|
+
### 2. Product grid (component directive)
|
|
456
|
+
{
|
|
457
|
+
"text": "Brief intro line shown above the cards.",
|
|
458
|
+
"component": "ProductGrid",
|
|
459
|
+
"props": {
|
|
460
|
+
"products": [
|
|
461
|
+
{"id": "...", "title": "...", "price": 24800, "image": "https://...", "description": "..."}
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
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\u20136** items from the catalog below \u2014 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.
|
|
467
|
+
|
|
468
|
+
### 3. Add to cart (action)
|
|
469
|
+
{"action": "add_to_cart", "text": "Confirmation line.", "item": {"id": "...", "title": "...", "price": 24800}}
|
|
470
|
+
|
|
471
|
+
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 \u2014 your text confirms the action and renders as a regular chat bubble.
|
|
472
|
+
|
|
473
|
+
## Rules
|
|
474
|
+
|
|
475
|
+
- Prices in JSON are always **integer cents** (24800 = $248.00).
|
|
476
|
+
- When the shopper asks "what would go with this?", ground your suggestions in **{{current_product}}** \u2014 pick items that complement the color, fabric, or category.
|
|
477
|
+
- For "under $X" queries, only return products from the catalog priced under that amount.
|
|
478
|
+
- For gift queries, prefer the gift card SKUs or compact accessories.
|
|
479
|
+
- After a ProductGrid response, do **not** also describe each product in the text \u2014 the cards speak for themselves. Keep text short ("A few cashmere options:", "Pieces under $200:").
|
|
480
|
+
- Never invent products. The catalog below is the entire universe.
|
|
481
|
+
|
|
482
|
+
## Product catalog
|
|
483
|
+
|
|
484
|
+
| id | title | price (cents) | image | description |
|
|
485
|
+
|---|---|---|---|---|
|
|
486
|
+
| 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. |
|
|
487
|
+
| 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. |
|
|
488
|
+
| 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. |
|
|
489
|
+
| 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. |
|
|
490
|
+
| 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. |
|
|
491
|
+
| 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. |
|
|
492
|
+
| 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. |
|
|
493
|
+
| 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. |
|
|
494
|
+
| 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. |
|
|
495
|
+
| 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. |
|
|
496
|
+
| 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. |
|
|
497
|
+
| 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. |
|
|
498
|
+
|
|
499
|
+
## Examples
|
|
500
|
+
|
|
501
|
+
User asks "show me cashmere essentials":
|
|
502
|
+
{"text": "A few cashmere essentials:", "component": "ProductGrid", "props": {"products": [
|
|
503
|
+
{"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."},
|
|
504
|
+
{"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."},
|
|
505
|
+
{"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."}
|
|
506
|
+
]}}
|
|
507
|
+
|
|
508
|
+
User asks "what pants would go with this?" (current_product = camel cashmere sweater):
|
|
509
|
+
{"text": "These pair well with the camel:", "component": "ProductGrid", "props": {"products": [
|
|
510
|
+
{"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."},
|
|
511
|
+
{"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."}
|
|
512
|
+
]}}
|
|
513
|
+
|
|
514
|
+
User asks "anything under $200?":
|
|
515
|
+
{"text": "A few under $200:", "component": "ProductGrid", "props": {"products": [
|
|
516
|
+
{"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."},
|
|
517
|
+
{"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."},
|
|
518
|
+
{"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."},
|
|
519
|
+
{"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."}
|
|
520
|
+
]}}
|
|
521
|
+
|
|
522
|
+
User asks "I need a gift under $300":
|
|
523
|
+
{"text": "Gifts under $300:", "component": "ProductGrid", "props": {"products": [
|
|
524
|
+
{"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."},
|
|
525
|
+
{"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."},
|
|
526
|
+
{"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."}
|
|
527
|
+
]}}
|
|
528
|
+
|
|
529
|
+
User asks "add the linen pant to my bag":
|
|
530
|
+
{"action": "add_to_cart", "text": "Added the linen trouser to your bag.", "item": {"id": "linen-trouser", "title": "Wide-Leg Linen Trouser", "price": 18800}}
|
|
531
|
+
|
|
532
|
+
User asks "how does this fit?" (current_product is the cashmere button-down):
|
|
533
|
+
{"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."}
|
|
534
|
+
|
|
535
|
+
User asks "what's the best way to care for cashmere?":
|
|
536
|
+
{"text": "Hand-wash cool with a wool-safe detergent, lay flat to dry, and store folded \u2014 never on a hanger. A cedar block in the drawer keeps moths off."}`,
|
|
537
|
+
previousMessages: "{{messages}}"
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
]
|
|
541
|
+
};
|
|
542
|
+
|
|
417
543
|
// src/utils/stripe.ts
|
|
418
544
|
var STRIPE_API_VERSION = "2026-03-25.dahlia";
|
|
419
545
|
function parseStripeApiErrorBody(body) {
|
|
@@ -792,6 +918,7 @@ export {
|
|
|
792
918
|
FORM_DIRECTIVE_FLOW,
|
|
793
919
|
SHOPPING_ASSISTANT_FLOW,
|
|
794
920
|
SHOPPING_ASSISTANT_METADATA_FLOW,
|
|
921
|
+
STOREFRONT_ASSISTANT_FLOW,
|
|
795
922
|
createChatProxyApp,
|
|
796
923
|
createCheckoutSession,
|
|
797
924
|
createVercelHandler,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/flows/conversational.ts","../src/flows/scheduling.ts","../src/flows/shopping-assistant.ts","../src/flows/components.ts","../src/flows/bakery-assistant.ts","../src/utils/stripe.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport { handle } from \"hono/vercel\";\n\nexport type RuntypeFlowStep = {\n id: string;\n name: string;\n type: string;\n enabled: boolean;\n config: Record<string, unknown>;\n};\n\nexport type RuntypeFlowConfig = {\n name: string;\n description: string;\n steps: RuntypeFlowStep[];\n};\n\ntype RuntimeEnv = Record<string, string | undefined>;\n\n/**\n * Payload for message feedback (upvote/downvote)\n */\nexport type FeedbackPayload = {\n type: \"upvote\" | \"downvote\";\n messageId: string;\n content?: string;\n timestamp?: string;\n sessionId?: string;\n metadata?: Record<string, unknown>;\n};\n\n/**\n * Handler function for processing feedback\n */\nexport type FeedbackHandler = (feedback: FeedbackPayload) => Promise<void> | void;\n\nexport type ChatProxyOptions = {\n upstreamUrl?: string;\n apiKey?: string;\n path?: string;\n allowedOrigins?: string[];\n flowId?: string;\n flowConfig?: RuntypeFlowConfig;\n /**\n * Path for the feedback endpoint (default: \"/api/feedback\")\n */\n feedbackPath?: string;\n /**\n * Custom handler for processing feedback.\n * Use this to store feedback in a database or send to analytics.\n * \n * @example\n * ```ts\n * onFeedback: async (feedback) => {\n * await db.feedback.create({ data: feedback });\n * }\n * ```\n */\n onFeedback?: FeedbackHandler;\n};\n\nconst DEFAULT_ENDPOINT = \"https://api.runtype.com/v1/dispatch\";\nconst DEFAULT_PATH = \"/api/chat/dispatch\";\n\nconst getRuntimeEnv = (): RuntimeEnv | undefined => {\n const maybeProcess = (\n globalThis as typeof globalThis & { process?: { env?: RuntimeEnv } }\n ).process;\n return maybeProcess?.env;\n};\n\n/** True only when `NODE_ENV` is exactly `\"development\"` (unset = production). Safe when `process` is missing (e.g. some Workers runtimes). */\nconst isDevelopmentRuntime = (): boolean =>\n getRuntimeEnv()?.NODE_ENV === \"development\";\n\nconst DEFAULT_FLOW: RuntypeFlowConfig = {\n name: \"Streaming Prompt Flow\",\n description: \"Streaming chat generated by the widget\",\n steps: [\n {\n id: \"widget_prompt\",\n name: \"Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n responseFormat: \"markdown\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: \"you are a helpful assistant, chatting with a user\",\n // tools: {\n // toolIds: [\n // \"builtin:dalle\"\n // ]\n // },\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n\nconst withCors =\n (allowedOrigins: string[] | undefined) =>\n async (c: Context, next: () => Promise<void>) => {\n const origin = c.req.header(\"origin\");\n const isDevelopment = isDevelopmentRuntime();\n \n // Determine the CORS origin to allow\n let corsOrigin: string;\n if (!allowedOrigins || allowedOrigins.length === 0) {\n // No restrictions - allow any origin (or use the request origin)\n corsOrigin = origin || \"*\";\n } else if (allowedOrigins.includes(origin || \"\")) {\n // Origin is in the allowed list\n corsOrigin = origin || \"*\";\n } else if (isDevelopment && origin) {\n // In development, allow the actual origin even if not in the list\n // This helps with local development where ports might vary\n corsOrigin = origin;\n } else {\n // Production: origin not allowed - reject by not setting CORS headers\n // Return error for preflight, or continue without CORS headers\n if (c.req.method === \"OPTIONS\") {\n return c.json({ error: \"CORS policy violation: origin not allowed\" }, 403);\n }\n // For non-preflight requests, continue but browser will block due to missing CORS headers\n await next();\n return;\n }\n\n const headers: Record<string, string> = {\n \"Access-Control-Allow-Origin\": corsOrigin,\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n Vary: \"Origin\"\n };\n\n if (c.req.method === \"OPTIONS\") {\n return new Response(null, { status: 204, headers });\n }\n\n await next();\n Object.entries(headers).forEach(([key, value]) =>\n c.header(key, value, { append: false })\n );\n };\n\nexport const createChatProxyApp = (options: ChatProxyOptions = {}) => {\n const app = new Hono();\n const path = options.path ?? DEFAULT_PATH;\n const feedbackPath = options.feedbackPath ?? \"/api/feedback\";\n const upstream = options.upstreamUrl ?? DEFAULT_ENDPOINT;\n\n app.use(\"*\", withCors(options.allowedOrigins));\n\n // Feedback endpoint for collecting upvote/downvote data\n app.post(feedbackPath, async (c) => {\n let payload: FeedbackPayload;\n try {\n payload = await c.req.json();\n } catch (error) {\n return c.json({ error: \"Invalid JSON body\" }, 400);\n }\n\n // Validate payload\n if (!payload.type || ![\"upvote\", \"downvote\"].includes(payload.type)) {\n return c.json(\n { error: \"Invalid feedback type. Must be 'upvote' or 'downvote'\" },\n 400\n );\n }\n if (!payload.messageId) {\n return c.json({ error: \"Missing messageId\" }, 400);\n }\n\n // Add timestamp if not provided\n payload.timestamp = payload.timestamp ?? new Date().toISOString();\n\n const isDevelopment = isDevelopmentRuntime();\n\n if (isDevelopment) {\n console.log(\"\\n=== Feedback Received ===\");\n console.log(\"Type:\", payload.type);\n console.log(\"Message ID:\", payload.messageId);\n console.log(\"Content Length:\", payload.content?.length ?? 0);\n console.log(\"Timestamp:\", payload.timestamp);\n console.log(\"=== End Feedback ===\\n\");\n }\n\n // Call custom handler if provided\n if (options.onFeedback) {\n try {\n await options.onFeedback(payload);\n } catch (error) {\n console.error(\"[Feedback] Handler error:\", error);\n return c.json({ error: \"Feedback handler failed\" }, 500);\n }\n }\n\n return c.json({\n success: true,\n message: \"Feedback recorded\",\n feedback: {\n type: payload.type,\n messageId: payload.messageId,\n timestamp: payload.timestamp\n }\n });\n });\n\n // Chat dispatch endpoint\n app.post(path, async (c) => {\n const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;\n if (!apiKey) {\n return c.json(\n { error: \"Missing API key. Set RUNTYPE_API_KEY.\" },\n 401\n );\n }\n\n let clientPayload: Record<string, unknown>;\n try {\n clientPayload = await c.req.json();\n } catch (error) {\n return c.json(\n { error: \"Invalid JSON body\", details: error },\n 400\n );\n }\n\n const isDevelopment = isDevelopmentRuntime();\n\n // Detect agent mode: if the payload contains an `agent` field, forward it directly\n const isAgentMode = !!clientPayload.agent;\n\n let runtypePayload: Record<string, unknown>;\n\n if (isAgentMode) {\n // Agent dispatch - forward the payload as-is to the upstream API\n runtypePayload = clientPayload;\n } else {\n // Flow dispatch - build the Runtype flow payload\n const messages = (clientPayload.messages ?? []) as Array<{ role: string; content: string; createdAt?: string }>;\n const sortedMessages = [...messages].sort((a, b) => {\n const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return timeA - timeB;\n });\n const formattedMessages = sortedMessages.map((message) => ({\n role: message.role,\n content: message.content\n }));\n\n const flowId = (clientPayload.flowId as string | undefined) ?? options.flowId;\n const flowConfig = options.flowConfig ?? DEFAULT_FLOW;\n\n runtypePayload = {\n record: {\n name: \"Streaming Chat Widget\",\n type: \"standalone\",\n metadata: (clientPayload.metadata as Record<string, unknown>) || {}\n },\n messages: formattedMessages,\n options: {\n streamResponse: true,\n recordMode: \"virtual\",\n flowMode: flowId ? \"existing\" : \"virtual\",\n autoAppendMetadata: false\n }\n };\n\n const clientInputs = clientPayload.inputs;\n if (clientInputs && typeof clientInputs === \"object\" && !Array.isArray(clientInputs)) {\n runtypePayload.inputs = clientInputs;\n }\n\n if (flowId) {\n runtypePayload.flow = { id: flowId };\n } else {\n runtypePayload.flow = flowConfig;\n }\n }\n\n // Development only: do not log key material or full bodies in production.\n if (isDevelopment) {\n console.log(`\\n=== Runtype Proxy Request (${isAgentMode ? \"agent\" : \"flow\"}) ===`);\n console.log(\"URL:\", upstream);\n console.log(\"API Key Used:\", apiKey ? \"Yes\" : \"No\");\n console.log(\"API Key (first 12 chars):\", apiKey ? apiKey.substring(0, 12) : \"N/A\");\n console.log(\"Request Payload:\", JSON.stringify(runtypePayload, null, 2));\n }\n\n const response = await fetch(upstream, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(runtypePayload)\n });\n\n if (isDevelopment) {\n console.log(\"Response Status:\", response.status);\n console.log(\"Response Status Text:\", response.statusText);\n\n // If there's an error, try to read and log the response body\n if (!response.ok) {\n const clonedResponse = response.clone();\n try {\n const errorBody = await clonedResponse.text();\n console.log(\"Error Response Body:\", errorBody);\n } catch (e) {\n console.log(\"Could not read error response body:\", e);\n }\n }\n console.log(\"=== End Runtype Proxy Request ===\\n\");\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"Content-Type\":\n response.headers.get(\"content-type\") ?? \"application/json\",\n \"Cache-Control\": \"no-store\"\n }\n });\n });\n\n // Resume endpoint — forwards client-executed (LOCAL) tool results back to\n // the Runtype upstream so a paused flow execution can continue. Mounted as\n // a child of the dispatch path so the widget can derive its URL by\n // appending \"/resume\" to whatever `apiUrl` it was configured with.\n app.post(`${path}/resume`, async (c) => {\n const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;\n if (!apiKey) {\n return c.json(\n { error: \"Missing API key. Set RUNTYPE_API_KEY.\" },\n 401\n );\n }\n\n let body: Record<string, unknown>;\n try {\n body = await c.req.json();\n } catch (error) {\n return c.json(\n { error: \"Invalid JSON body\", details: error },\n 400\n );\n }\n\n const isDevelopment = isDevelopmentRuntime();\n const upstreamResumeUrl = `${upstream.replace(/\\/+$/, '')}/resume`;\n\n if (isDevelopment) {\n console.log(\"\\n=== Runtype Proxy Resume ===\");\n console.log(\"URL:\", upstreamResumeUrl);\n console.log(\n \"executionId:\",\n typeof body.executionId === \"string\" ? body.executionId : \"(missing)\"\n );\n console.log(\n \"toolOutputs keys:\",\n body.toolOutputs && typeof body.toolOutputs === \"object\"\n ? Object.keys(body.toolOutputs)\n : \"(none)\"\n );\n console.log(\"=== End Runtype Proxy Resume ===\\n\");\n }\n\n const response = await fetch(upstreamResumeUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(body)\n });\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"Content-Type\":\n response.headers.get(\"content-type\") ?? \"application/json\",\n \"Cache-Control\": \"no-store\"\n }\n });\n });\n\n return app;\n};\n\nexport const createVercelHandler = (options?: ChatProxyOptions) =>\n handle(createChatProxyApp(options));\n\n// Export pre-configured flows\nexport * from \"./flows/index.js\";\n\n// Export utility functions\nexport * from \"./utils/index.js\";\n\nexport default createChatProxyApp;\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Basic conversational assistant flow\n * This is the default flow for simple chat interactions\n */\nexport const CONVERSATIONAL_FLOW: RuntypeFlowConfig = {\n name: \"Streaming Prompt Flow\",\n description: \"Streaming chat generated by the widget\",\n steps: [\n {\n id: \"widget_prompt\",\n name: \"Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n responseFormat: \"markdown\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: \"you are a helpful assistant, chatting with a user\",\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Dynamic Form flow configuration\n * This flow returns forms as component directives for the widget to render\n */\nexport const FORM_DIRECTIVE_FLOW: RuntypeFlowConfig = {\n name: \"Dynamic Form Flow\",\n description: \"Returns dynamic forms as component directives\",\n steps: [\n {\n id: \"form_prompt\",\n name: \"Form Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful assistant that can have conversations and collect user information via forms.\n\nRESPONSE FORMAT:\nAlways respond with valid JSON. Choose the appropriate format:\n\n1. For CONVERSATIONAL responses or text answers:\n {\"text\": \"Your response here\"}\n\n2. When the user wants to SCHEDULE, BOOK, SIGN UP, or provide DETAILS (show a form):\n {\"component\": \"DynamicForm\", \"props\": {\"title\": \"Form Title\", \"description\": \"Optional description\", \"fields\": [...], \"submit_text\": \"Submit\"}}\n\n3. For BOTH explanation AND form:\n {\"text\": \"Your explanation\", \"component\": \"DynamicForm\", \"props\": {...}}\n\nFORM FIELD FORMAT:\nEach field in the \"fields\" array should have:\n- label (required): Display name for the field\n- name (optional): Field identifier (defaults to lowercase label with underscores)\n- type (optional): \"text\", \"email\", \"tel\", \"date\", \"time\", \"textarea\", \"number\" (defaults to \"text\")\n- placeholder (optional): Placeholder text\n- required (optional): true/false\n\nEXAMPLES:\n\nUser: \"Schedule a demo for me\"\nResponse: {\"text\": \"I'd be happy to help you schedule a demo! Please fill out the form below:\", \"component\": \"DynamicForm\", \"props\": {\"title\": \"Schedule a Demo\", \"description\": \"Share your details and we'll follow up with a confirmation.\", \"fields\": [{\"label\": \"Full Name\", \"type\": \"text\", \"required\": true}, {\"label\": \"Email\", \"type\": \"email\", \"required\": true}, {\"label\": \"Company\", \"type\": \"text\"}, {\"label\": \"Preferred Date\", \"type\": \"date\", \"required\": true}, {\"label\": \"Notes\", \"type\": \"textarea\", \"placeholder\": \"Any specific topics you'd like to cover?\"}], \"submit_text\": \"Request Demo\"}}\n\nUser: \"What is AI?\"\nResponse: {\"text\": \"AI (Artificial Intelligence) refers to computer systems designed to perform tasks that typically require human intelligence, such as learning, reasoning, problem-solving, and understanding language.\"}\n\nUser: \"Collect my contact details\"\nResponse: {\"component\": \"DynamicForm\", \"props\": {\"title\": \"Contact Details\", \"fields\": [{\"label\": \"Name\", \"type\": \"text\", \"required\": true}, {\"label\": \"Email\", \"type\": \"email\", \"required\": true}, {\"label\": \"Phone\", \"type\": \"tel\"}], \"submit_text\": \"Save Details\"}}\n\nIMPORTANT:\n- Use {\"text\": \"...\"} for questions, explanations, and general conversation\n- Show a DynamicForm when user wants to provide information, schedule, book, or sign up\n- Create contextually appropriate form fields based on what the user is trying to do\n- Keep forms focused with only the relevant fields needed`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Shopping assistant flow configuration\n * This flow returns JSON actions for page interaction including:\n * - Simple messages\n * - Navigation with messages\n * - Element clicks with messages\n * - Stripe checkout\n */\nexport const SHOPPING_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Shopping Assistant Flow\",\n description: \"Returns JSON actions for page interaction\",\n steps: [\n {\n id: \"action_prompt\",\n name: \"Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant that can interact with web pages.\nYou will receive information about the current page's elements (class names and text content)\nand user messages. You must respond with JSON in one of these formats:\n\n1. Simple message:\n{\n \"action\": \"message\",\n \"text\": \"Your response text here\"\n}\n\n2. Navigate then show message (for navigation to another page):\n{\n \"action\": \"nav_then_click\",\n \"page\": \"http://site.com/page-url\",\n \"on_load_text\": \"Message to show after navigation\"\n}\n\n3. Show message and click an element:\n{\n \"action\": \"message_and_click\",\n \"element\": \".className-of-element\",\n \"text\": \"Your message text\"\n}\n\n4. Create Stripe checkout:\n{\n \"action\": \"checkout\",\n \"text\": \"Your message text\",\n \"items\": [\n {\"name\": \"Product Name\", \"price\": 2999, \"quantity\": 1}\n ]\n}\n\nGuidelines:\n- Use \"message\" for simple conversational responses\n- Use \"nav_then_click\" when you need to navigate to a different page (like a product detail page)\n- Use \"message_and_click\" when you want to click a button or element on the current page\n- Use \"checkout\" when the user wants to proceed to checkout/payment. Include items array with name (string), price (number in cents), and quantity (number)\n- When selecting elements, use the class names provided in the page context\n- Always respond with valid JSON only, no additional text\n- For product searches, format results as markdown links: [Product Name](url)\n- Be helpful and conversational in your messages\n- Product prices: Black Shirt - Medium: $29.99 (2999 cents), Blue Shirt - Large: $34.99 (3499 cents), Red T-Shirt - Small: $19.99 (1999 cents), Jeans - Medium: $49.99 (4999 cents)\n\nExample conversation flow:\n- User: \"I am looking for a black shirt in medium\"\n- You: {\"action\": \"message\", \"text\": \"Here are the products I found:\\\\n1. [Black Shirt - Medium](/products.html?product=black-shirt-medium) - $29.99\\\\n2. [Blue Shirt - Large](/products.html?product=blue-shirt-large) - $34.99\\\\n3. [Red T-Shirt - Small](/products.html?product=red-tshirt-small) - $19.99\\\\n4. [Jeans - Medium](/products.html?product=jeans-medium) - $49.99\\\\n\\\\nWould you like me to navigate to the first result and add it to your cart?\"}\n\n- User: \"No, I would like to add another shirt to the cart\"\n- You: {\"action\": \"message_and_click\", \"element\": \".AddToCartButton-blue-shirt-large\", \"text\": \"I've added the Blue Shirt - Large to your cart. Ready to checkout?\"}\n\n- User: \"yes\"\n- You: {\"action\": \"checkout\", \"text\": \"Perfect! I'll set up the checkout for you.\", \"items\": [{\"name\": \"Black Shirt - Medium\", \"price\": 2999, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n\n/**\n * Metadata-based shopping assistant flow configuration\n * This flow uses DOM context from record metadata instead of user message.\n * The metadata should include dom_elements, dom_body, page_url, and page_title.\n */\nexport const SHOPPING_ASSISTANT_METADATA_FLOW: RuntypeFlowConfig = {\n name: \"Metadata-Based Shopping Assistant\",\n description: \"Uses DOM context from record metadata for page interaction\",\n steps: [\n {\n id: \"metadata_action_prompt\",\n name: \"Metadata Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant that can interact with web pages.\n\nIMPORTANT: You have access to the current page's DOM elements through the record metadata, which includes:\n- dom_elements: Array of page elements with className, innerText, and tagName\n- dom_body: Complete HTML body of the page (if provided)\n- page_url: Current page URL\n- page_title: Page title\n\nThe dom_elements array provides information about clickable elements and their text content.\nUse this metadata to understand what's available on the page and help users interact with it.\n\nYou must respond with JSON in one of these formats:\n\n1. Simple message:\n{\n \"action\": \"message\",\n \"text\": \"Your response text here\"\n}\n\n2. Navigate then show message (for navigation to another page):\n{\n \"action\": \"nav_then_click\",\n \"page\": \"http://site.com/page-url\",\n \"on_load_text\": \"Message to show after navigation\"\n}\n\n3. Show message and click an element:\n{\n \"action\": \"message_and_click\",\n \"element\": \".className-of-element\",\n \"text\": \"Your message text\"\n}\n\n4. Create Stripe checkout:\n{\n \"action\": \"checkout\",\n \"text\": \"Your message text\",\n \"items\": [\n {\"name\": \"Product Name\", \"price\": 2999, \"quantity\": 1}\n ]\n}\n\nGuidelines:\n- Use \"message\" for simple conversational responses\n- Use \"nav_then_click\" when you need to navigate to a different page (like a product detail page)\n- Use \"message_and_click\" when you want to click a button or element on the current page\n- Use \"checkout\" when the user wants to proceed to checkout/payment. Include items array with name (string), price (number in cents), and quantity (number)\n- When selecting elements, use the class names from the dom_elements in the metadata\n- Always respond with valid JSON only, no additional text\n- For product searches, format results as markdown links: [Product Name](url)\n- Be helpful and conversational in your messages\n- Product prices: Black Shirt - Medium: $29.99 (2999 cents), Blue Shirt - Large: $34.99 (3499 cents), Red T-Shirt - Small: $19.99 (1999 cents), Jeans - Medium: $49.99 (4999 cents)\n\nExample conversation flow:\n- User: \"I am looking for a black shirt in medium\"\n- You: {\"action\": \"message\", \"text\": \"Here are the products I found:\\\\n1. [Black Shirt - Medium](/products.html?product=black-shirt-medium) - $29.99\\\\n2. [Blue Shirt - Large](/products.html?product=blue-shirt-large) - $34.99\\\\n3. [Red T-Shirt - Small](/products.html?product=red-tshirt-small) - $19.99\\\\n4. [Jeans - Medium](/products.html?product=jeans-medium) - $49.99\\\\n\\\\nWould you like me to navigate to the first result and add it to your cart?\"}\n\n- User: \"No, I would like to add another shirt to the cart\"\n- You: {\"action\": \"message_and_click\", \"element\": \".AddToCartButton-blue-shirt-large\", \"text\": \"I've added the Blue Shirt - Large to your cart. Ready to checkout?\"}\n\n- User: \"yes\"\n- You: {\"action\": \"checkout\", \"text\": \"Perfect! I'll set up the checkout for you.\", \"items\": [{\"name\": \"Black Shirt - Medium\", \"price\": 2999, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Component-aware flow for custom component rendering\n * This flow instructs the AI to respond with component directives in JSON format\n */\nexport const COMPONENT_FLOW: RuntypeFlowConfig = {\n name: \"Component Flow\",\n description: \"Flow configured for custom component rendering\",\n steps: [\n {\n id: \"component_prompt\",\n name: \"Component Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful assistant that can both have conversations and render custom UI components.\n\nRESPONSE FORMAT:\nAlways respond with valid JSON. Choose the appropriate format based on the user's request:\n\n1. For CONVERSATIONAL questions or text responses:\n {\"text\": \"Your response here\"}\n\n2. For VISUAL DISPLAYS or when the user asks to SHOW/DISPLAY something:\n {\"component\": \"ComponentName\", \"props\": {...}}\n\n3. For BOTH explanation AND visual:\n {\"text\": \"Your explanation here\", \"component\": \"ComponentName\", \"props\": {...}}\n\nAvailable components for visual displays:\n- ProductCard: Display product information. Props: title (string), price (number), description (string, optional), image (string, optional)\n- SimpleChart: Display a bar chart. Props: title (string), data (array of numbers), labels (array of strings, optional)\n- StatusBadge: Display a status badge. Props: status (string: \"success\", \"error\", \"warning\", \"info\", \"pending\"), message (string)\n- InfoCard: Display an information card. Props: title (string), content (string), icon (string, optional)\n\nExamples:\n- User asks \"What is the capital of France?\": {\"text\": \"The capital of France is Paris.\"}\n- User asks \"What does that chart show?\": {\"text\": \"The chart shows sales data increasing from 100 to 200 over three months.\"}\n- User asks \"Show me a product card\": {\"component\": \"ProductCard\", \"props\": {\"title\": \"Laptop\", \"price\": 999, \"description\": \"A great laptop\"}}\n- User asks \"Display a chart\": {\"component\": \"SimpleChart\", \"props\": {\"title\": \"Sales\", \"data\": [100, 150, 200], \"labels\": [\"Jan\", \"Feb\", \"Mar\"]}}\n- User asks \"Show me a chart and explain it\": {\"text\": \"Here's the sales data for Q1:\", \"component\": \"SimpleChart\", \"props\": {\"title\": \"Q1 Sales\", \"data\": [100, 150, 200], \"labels\": [\"Jan\", \"Feb\", \"Mar\"]}}\n\nIMPORTANT:\n- Use {\"text\": \"...\"} for questions, explanations, discussions, and general chat\n- Use {\"component\": \"...\", \"props\": {...}} ONLY when the user explicitly wants to SEE/VIEW/DISPLAY visual content\n- You can combine both: {\"text\": \"...\", \"component\": \"...\", \"props\": {...}} when you want to explain something AND show a visual\n- Never force a component when the user just wants information`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Bakery assistant flow configuration for \"Flour & Stone\" bakery demo\n * This flow returns JSON actions for page interaction including:\n * - Simple messages with bakery brand voice\n * - Navigation to bakery pages\n * - Add to cart interactions\n * - Stripe checkout\n *\n * Designed to guide users toward the gift card when asking for gift recommendations.\n */\nexport const BAKERY_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Bakery Assistant Flow\",\n description: \"Flour & Stone bakery shopping assistant with gift recommendations\",\n steps: [\n {\n id: \"bakery_action_prompt\",\n name: \"Bakery Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant for Flour & Stone, a premium artisan bakery known for traditional bread-making and exceptional pastries.\n\nBrand voice: Warm, knowledgeable, passionate about craft baking. Use phrases like \"fresh from the oven\", \"handcrafted with care\", \"artisan tradition\". Do not explain selectors, JSON, or templating to the user.\n\n## Live context (request inputs — substituted each turn)\n\nThe widget sends **only** these keys as dispatch **inputs** (nothing extra on the record for this demo).\n\n**Orientation**\n- Path: {{current_page}} (compare before nav_then_click; e.g. /bakery-goods.html)\n- Full URL: {{page_url}}\n- Title: {{page_title}}\n\n**Page DOM**\n- page_elements: JSON array of enriched nodes (selector, tagName, text, role, interactivity, attributes including data-*). Prefer **selector** for message_and_click when you click a specific control.\n- page_context: Same slice formatted for the LLM (structured card summaries when matched, then groups by interactivity).\n\n{{page_elements}}\n\n{{page_context}}\n\n**Cart (for checkout — mirror cart.items when user pays)**\n{{cart}}\n\n**Recent order (if any)**\n{{recent_order}}\n\nIf {{current_page}} already equals the page you would navigate to, use {\"action\":\"message\",...} instead of nav_then_click.\n\n## Discovering products\n\nUse {{page_context}} for a quick scan and {{page_elements}} for exact selectors and attributes. Product rows often include data-product in **attributes**; prices appear in **text**; add-to-cart controls are usually **clickable** with stable **selector** values.\n\n## Output: one JSON object only\n\nNo markdown fences, no commentary before/after. Valid JSON only.\n\n### 1. message\n{\"action\": \"message\", \"text\": \"...\"}\nUse for chat, clarifying questions, \"we're already on that page\", or when you need the user to choose (e.g. $25 vs $50 gift card).\n\n### 2. nav_then_click\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"...\"}\nUse root-relative paths starting with /. Only when current_page is different from the target. This **only** changes pages — it does **not** open Stripe or payment.\n\n### 3. add_to_cart\n{\"action\": \"add_to_cart\", \"text\": \"...\", \"item\": {\"id\": \"product-id\", \"name\": \"Product Name\", \"price\": 1200}}\nUse when adding from context without scrolling (optional; on goods page prefer scroll_then_add).\n\n### 4. scroll_then_add (preferred on /bakery-goods.html)\n{\"action\": \"scroll_then_add\", \"text\": \"...\", \"item\": {\"id\": \"...\", \"name\": \"...\", \"price\": 1200}}\nScrolls the product into view then adds one unit (cart merges duplicate ids into quantity).\n\n### 5. checkout → Stripe (this demo)\n{\"action\": \"checkout\", \"text\": \"Brief message\", \"items\": [{\"name\": \"...\", \"price\": 1200, \"quantity\": 2}, ...]}\n**Only** this action starts hosted checkout (Stripe). **Never** use nav_then_click to a \"/checkout\" URL for payment here.\nRequirements: cart in context must have items; **items array must list every cart line** with the same name, cent prices, and quantities as cart.items. If cart is null or empty, use message — do not checkout.\n\n### 6. message_and_click (rare)\nIf page_elements show a specific button selector and scroll_then_add is wrong, you may use message_and_click with a CSS selector — prefer scroll_then_add on bakery-goods.html.\n\n## Rules\n\n- Prices in JSON are always **integer cents** (1200 = $12.00).\n- After adding to cart, invite checkout or more shopping.\n- On checkout confirmation (\"yes\", \"checkout\", \"pay\", \"proceed\", etc.), build **items** from **cart.items** (all rows, correct quantity). Do not drop lines or invent prices.\n\n## Product catalog (ids and cent prices)\n\n- Sourdough Loaf: sourdough-loaf, 1200\n- Croissant Box (6): croissant-box, 2400\n- Cinnamon Rolls (4): cinnamon-rolls, 1800\n- Baguette Trio: baguette-trio, 900\n- Almond Tart: almond-tart, 800\n- Fruit Danish: fruit-danish, 600\n- $50 Gift Card: gift-card-50, 5000\n- $25 Gift Card: gift-card-25, 2500\n\n## Examples\n\nGift seeker on /bakery-locations.html:\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"Here are our goods! You'll find our gift cards below — $50 is our most popular. Want me to add one?\"}\n\nOn /bakery-goods.html, user wants $50 gift card:\n{\"action\": \"scroll_then_add\", \"text\": \"Added the $50 gift card. Ready to check out?\", \"item\": {\"id\": \"gift-card-50\", \"name\": \"$50 Gift Card\", \"price\": 5000}}\n\nUser on /bakery.html agrees to see products:\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"Here are our handcrafted goods — what sounds good today?\"}\n\nOn /bakery-goods.html, add sourdough:\n{\"action\": \"scroll_then_add\", \"text\": \"Sourdough is in your cart. Anything else, or shall we check out?\", \"item\": {\"id\": \"sourdough-loaf\", \"name\": \"Sourdough Loaf\", \"price\": 1200}}\n\nCart has one $50 gift card; user says yes to checkout:\n{\"action\": \"checkout\", \"text\": \"Opening secure checkout...\", \"items\": [{\"name\": \"$50 Gift Card\", \"price\": 5000, \"quantity\": 1}]}\n\nCart has sourdough (qty 1) and croissant box (qty 1); user says \"pay\":\n{\"action\": \"checkout\", \"text\": \"Taking you to checkout...\", \"items\": [{\"name\": \"Sourdough Loaf\", \"price\": 1200, \"quantity\": 1}, {\"name\": \"Croissant Box (6)\", \"price\": 2400, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","/**\n * Stripe checkout helpers using the REST API\n * This approach works on all platforms including Cloudflare Workers, Vercel Edge, etc.\n */\n\n/**\n * Pinned API version for raw HTTP calls (no SDK). Required for organization API keys and\n * keeps behavior stable across accounts. See https://docs.stripe.com/api/versioning\n */\nconst STRIPE_API_VERSION = \"2026-03-25.dahlia\";\n\nexport interface CheckoutItem {\n name: string;\n price: number; // Price in cents\n quantity: number;\n}\n\nexport interface CreateCheckoutSessionOptions {\n secretKey: string;\n items: CheckoutItem[];\n successUrl: string;\n cancelUrl: string;\n /**\n * Target account for organization API keys (`sk_org_…`), e.g. `acct_1abc…` or\n * `acct_platform/acct_connected` per Stripe. Required with org keys.\n * @see https://docs.stripe.com/keys#organization-api-keys\n */\n stripeContext?: string;\n}\n\nexport interface CheckoutSessionResponse {\n success: boolean;\n checkoutUrl?: string;\n sessionId?: string;\n error?: string;\n}\n\nfunction parseStripeApiErrorBody(body: string): string | undefined {\n try {\n const parsed = JSON.parse(body) as {\n error?: { message?: string; type?: string };\n };\n const msg = parsed?.error?.message;\n return typeof msg === \"string\" && msg.length > 0 ? msg : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Creates a Stripe checkout session using the REST API\n * @param options - Checkout session configuration\n * @returns Checkout session response with URL and session ID\n */\nexport async function createCheckoutSession(\n options: CreateCheckoutSessionOptions\n): Promise<CheckoutSessionResponse> {\n const { secretKey, items, successUrl, cancelUrl, stripeContext } = options;\n const trimmedContext = stripeContext?.trim() || undefined;\n\n try {\n if (secretKey.startsWith(\"sk_org\") && !trimmedContext) {\n return {\n success: false,\n error:\n \"Organization Stripe keys (sk_org_…) require stripeContext / STRIPE_CONTEXT with the target account (e.g. acct_…). See https://docs.stripe.com/keys#organization-api-keys\",\n };\n }\n\n // Validate items\n if (!items || !Array.isArray(items) || items.length === 0) {\n return {\n success: false,\n error: \"Items array is required\"\n };\n }\n\n for (const item of items) {\n if (!item.name || typeof item.price !== \"number\" || typeof item.quantity !== \"number\") {\n return {\n success: false,\n error: \"Each item must have name (string), price (number in cents), and quantity (number)\"\n };\n }\n if (\n !Number.isFinite(item.price) ||\n !Number.isInteger(item.price) ||\n item.price < 1 ||\n !Number.isInteger(item.quantity) ||\n item.quantity < 1\n ) {\n return {\n success: false,\n error:\n \"Each item needs a positive integer price (cents) and quantity (Stripe rejects decimals or zero)\",\n };\n }\n }\n\n // Build line items for URL encoding\n const lineItems = items.map((item) => ({\n price_data: {\n currency: \"usd\",\n product_data: {\n name: item.name,\n },\n unit_amount: item.price,\n },\n quantity: item.quantity,\n }));\n\n // Convert line items to URL-encoded format\n const params = new URLSearchParams({\n \"payment_method_types[0]\": \"card\",\n \"mode\": \"payment\",\n \"success_url\": successUrl,\n \"cancel_url\": cancelUrl,\n });\n\n // Add line items to params\n lineItems.forEach((item, index) => {\n params.append(`line_items[${index}][price_data][currency]`, item.price_data.currency);\n params.append(`line_items[${index}][price_data][product_data][name]`, item.price_data.product_data.name);\n params.append(`line_items[${index}][price_data][unit_amount]`, item.price_data.unit_amount.toString());\n params.append(`line_items[${index}][quantity]`, item.quantity.toString());\n });\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${secretKey}`,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"Stripe-Version\": STRIPE_API_VERSION,\n };\n if (trimmedContext) {\n headers[\"Stripe-Context\"] = trimmedContext;\n }\n\n // Create Stripe checkout session using REST API\n const stripeResponse = await fetch(\"https://api.stripe.com/v1/checkout/sessions\", {\n method: \"POST\",\n headers,\n body: params,\n });\n\n if (!stripeResponse.ok) {\n const errorData = await stripeResponse.text();\n const stripeMessage = parseStripeApiErrorBody(errorData);\n console.error(\"Stripe API error:\", errorData);\n return {\n success: false,\n error: stripeMessage ?? \"Failed to create checkout session\",\n };\n }\n\n const session = await stripeResponse.json() as { url: string; id: string };\n\n return {\n success: true,\n checkoutUrl: session.url,\n sessionId: session.id,\n };\n } catch (error) {\n console.error(\"Stripe checkout error:\", error);\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Failed to create checkout session\"\n };\n }\n}\n"],"mappings":"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","names":["_a","_b","_c"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/flows/conversational.ts","../src/flows/scheduling.ts","../src/flows/shopping-assistant.ts","../src/flows/components.ts","../src/flows/bakery-assistant.ts","../src/flows/storefront-assistant.ts","../src/utils/stripe.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport { handle } from \"hono/vercel\";\n\nexport type RuntypeFlowStep = {\n id: string;\n name: string;\n type: string;\n enabled: boolean;\n config: Record<string, unknown>;\n};\n\nexport type RuntypeFlowConfig = {\n name: string;\n description: string;\n steps: RuntypeFlowStep[];\n};\n\ntype RuntimeEnv = Record<string, string | undefined>;\n\n/**\n * Payload for message feedback (upvote/downvote)\n */\nexport type FeedbackPayload = {\n type: \"upvote\" | \"downvote\";\n messageId: string;\n content?: string;\n timestamp?: string;\n sessionId?: string;\n metadata?: Record<string, unknown>;\n};\n\n/**\n * Handler function for processing feedback\n */\nexport type FeedbackHandler = (feedback: FeedbackPayload) => Promise<void> | void;\n\nexport type ChatProxyOptions = {\n upstreamUrl?: string;\n apiKey?: string;\n path?: string;\n allowedOrigins?: string[];\n flowId?: string;\n flowConfig?: RuntypeFlowConfig;\n /**\n * Path for the feedback endpoint (default: \"/api/feedback\")\n */\n feedbackPath?: string;\n /**\n * Custom handler for processing feedback.\n * Use this to store feedback in a database or send to analytics.\n * \n * @example\n * ```ts\n * onFeedback: async (feedback) => {\n * await db.feedback.create({ data: feedback });\n * }\n * ```\n */\n onFeedback?: FeedbackHandler;\n};\n\nconst DEFAULT_ENDPOINT = \"https://api.runtype.com/v1/dispatch\";\nconst DEFAULT_PATH = \"/api/chat/dispatch\";\n\nconst getRuntimeEnv = (): RuntimeEnv | undefined => {\n const maybeProcess = (\n globalThis as typeof globalThis & { process?: { env?: RuntimeEnv } }\n ).process;\n return maybeProcess?.env;\n};\n\n/** True only when `NODE_ENV` is exactly `\"development\"` (unset = production). Safe when `process` is missing (e.g. some Workers runtimes). */\nconst isDevelopmentRuntime = (): boolean =>\n getRuntimeEnv()?.NODE_ENV === \"development\";\n\nconst DEFAULT_FLOW: RuntypeFlowConfig = {\n name: \"Streaming Prompt Flow\",\n description: \"Streaming chat generated by the widget\",\n steps: [\n {\n id: \"widget_prompt\",\n name: \"Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n responseFormat: \"markdown\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: \"you are a helpful assistant, chatting with a user\",\n // tools: {\n // toolIds: [\n // \"builtin:dalle\"\n // ]\n // },\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n\nconst withCors =\n (allowedOrigins: string[] | undefined) =>\n async (c: Context, next: () => Promise<void>) => {\n const origin = c.req.header(\"origin\");\n const isDevelopment = isDevelopmentRuntime();\n \n // Determine the CORS origin to allow\n let corsOrigin: string;\n if (!allowedOrigins || allowedOrigins.length === 0) {\n // No restrictions - allow any origin (or use the request origin)\n corsOrigin = origin || \"*\";\n } else if (allowedOrigins.includes(origin || \"\")) {\n // Origin is in the allowed list\n corsOrigin = origin || \"*\";\n } else if (isDevelopment && origin) {\n // In development, allow the actual origin even if not in the list\n // This helps with local development where ports might vary\n corsOrigin = origin;\n } else {\n // Production: origin not allowed - reject by not setting CORS headers\n // Return error for preflight, or continue without CORS headers\n if (c.req.method === \"OPTIONS\") {\n return c.json({ error: \"CORS policy violation: origin not allowed\" }, 403);\n }\n // For non-preflight requests, continue but browser will block due to missing CORS headers\n await next();\n return;\n }\n\n const headers: Record<string, string> = {\n \"Access-Control-Allow-Origin\": corsOrigin,\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n Vary: \"Origin\"\n };\n\n if (c.req.method === \"OPTIONS\") {\n return new Response(null, { status: 204, headers });\n }\n\n await next();\n Object.entries(headers).forEach(([key, value]) =>\n c.header(key, value, { append: false })\n );\n };\n\nexport const createChatProxyApp = (options: ChatProxyOptions = {}) => {\n const app = new Hono();\n const path = options.path ?? DEFAULT_PATH;\n const feedbackPath = options.feedbackPath ?? \"/api/feedback\";\n const upstream = options.upstreamUrl ?? DEFAULT_ENDPOINT;\n\n app.use(\"*\", withCors(options.allowedOrigins));\n\n // Feedback endpoint for collecting upvote/downvote data\n app.post(feedbackPath, async (c) => {\n let payload: FeedbackPayload;\n try {\n payload = await c.req.json();\n } catch (error) {\n return c.json({ error: \"Invalid JSON body\" }, 400);\n }\n\n // Validate payload\n if (!payload.type || ![\"upvote\", \"downvote\"].includes(payload.type)) {\n return c.json(\n { error: \"Invalid feedback type. Must be 'upvote' or 'downvote'\" },\n 400\n );\n }\n if (!payload.messageId) {\n return c.json({ error: \"Missing messageId\" }, 400);\n }\n\n // Add timestamp if not provided\n payload.timestamp = payload.timestamp ?? new Date().toISOString();\n\n const isDevelopment = isDevelopmentRuntime();\n\n if (isDevelopment) {\n console.log(\"\\n=== Feedback Received ===\");\n console.log(\"Type:\", payload.type);\n console.log(\"Message ID:\", payload.messageId);\n console.log(\"Content Length:\", payload.content?.length ?? 0);\n console.log(\"Timestamp:\", payload.timestamp);\n console.log(\"=== End Feedback ===\\n\");\n }\n\n // Call custom handler if provided\n if (options.onFeedback) {\n try {\n await options.onFeedback(payload);\n } catch (error) {\n console.error(\"[Feedback] Handler error:\", error);\n return c.json({ error: \"Feedback handler failed\" }, 500);\n }\n }\n\n return c.json({\n success: true,\n message: \"Feedback recorded\",\n feedback: {\n type: payload.type,\n messageId: payload.messageId,\n timestamp: payload.timestamp\n }\n });\n });\n\n // Chat dispatch endpoint\n app.post(path, async (c) => {\n const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;\n if (!apiKey) {\n return c.json(\n { error: \"Missing API key. Set RUNTYPE_API_KEY.\" },\n 401\n );\n }\n\n let clientPayload: Record<string, unknown>;\n try {\n clientPayload = await c.req.json();\n } catch (error) {\n return c.json(\n { error: \"Invalid JSON body\", details: error },\n 400\n );\n }\n\n const isDevelopment = isDevelopmentRuntime();\n\n // Detect agent mode: if the payload contains an `agent` field, forward it directly\n const isAgentMode = !!clientPayload.agent;\n\n let runtypePayload: Record<string, unknown>;\n\n if (isAgentMode) {\n // Agent dispatch - forward the payload as-is to the upstream API\n runtypePayload = clientPayload;\n } else {\n // Flow dispatch - build the Runtype flow payload\n const messages = (clientPayload.messages ?? []) as Array<{ role: string; content: string; createdAt?: string }>;\n const sortedMessages = [...messages].sort((a, b) => {\n const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return timeA - timeB;\n });\n const formattedMessages = sortedMessages.map((message) => ({\n role: message.role,\n content: message.content\n }));\n\n const flowId = (clientPayload.flowId as string | undefined) ?? options.flowId;\n const flowConfig = options.flowConfig ?? DEFAULT_FLOW;\n\n runtypePayload = {\n record: {\n name: \"Streaming Chat Widget\",\n type: \"standalone\",\n metadata: (clientPayload.metadata as Record<string, unknown>) || {}\n },\n messages: formattedMessages,\n options: {\n streamResponse: true,\n recordMode: \"virtual\",\n flowMode: flowId ? \"existing\" : \"virtual\",\n autoAppendMetadata: false\n }\n };\n\n const clientInputs = clientPayload.inputs;\n if (clientInputs && typeof clientInputs === \"object\" && !Array.isArray(clientInputs)) {\n runtypePayload.inputs = clientInputs;\n }\n\n if (flowId) {\n runtypePayload.flow = { id: flowId };\n } else {\n runtypePayload.flow = flowConfig;\n }\n }\n\n // Development only: do not log key material or full bodies in production.\n if (isDevelopment) {\n console.log(`\\n=== Runtype Proxy Request (${isAgentMode ? \"agent\" : \"flow\"}) ===`);\n console.log(\"URL:\", upstream);\n console.log(\"API Key Used:\", apiKey ? \"Yes\" : \"No\");\n console.log(\"API Key (first 12 chars):\", apiKey ? apiKey.substring(0, 12) : \"N/A\");\n console.log(\"Request Payload:\", JSON.stringify(runtypePayload, null, 2));\n }\n\n const response = await fetch(upstream, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(runtypePayload)\n });\n\n if (isDevelopment) {\n console.log(\"Response Status:\", response.status);\n console.log(\"Response Status Text:\", response.statusText);\n\n // If there's an error, try to read and log the response body\n if (!response.ok) {\n const clonedResponse = response.clone();\n try {\n const errorBody = await clonedResponse.text();\n console.log(\"Error Response Body:\", errorBody);\n } catch (e) {\n console.log(\"Could not read error response body:\", e);\n }\n }\n console.log(\"=== End Runtype Proxy Request ===\\n\");\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"Content-Type\":\n response.headers.get(\"content-type\") ?? \"application/json\",\n \"Cache-Control\": \"no-store\"\n }\n });\n });\n\n // Resume endpoint — forwards client-executed (LOCAL) tool results back to\n // the Runtype upstream so a paused flow execution can continue. Mounted as\n // a child of the dispatch path so the widget can derive its URL by\n // appending \"/resume\" to whatever `apiUrl` it was configured with.\n app.post(`${path}/resume`, async (c) => {\n const apiKey = options.apiKey ?? getRuntimeEnv()?.RUNTYPE_API_KEY;\n if (!apiKey) {\n return c.json(\n { error: \"Missing API key. Set RUNTYPE_API_KEY.\" },\n 401\n );\n }\n\n let body: Record<string, unknown>;\n try {\n body = await c.req.json();\n } catch (error) {\n return c.json(\n { error: \"Invalid JSON body\", details: error },\n 400\n );\n }\n\n const isDevelopment = isDevelopmentRuntime();\n const upstreamResumeUrl = `${upstream.replace(/\\/+$/, '')}/resume`;\n\n if (isDevelopment) {\n console.log(\"\\n=== Runtype Proxy Resume ===\");\n console.log(\"URL:\", upstreamResumeUrl);\n console.log(\n \"executionId:\",\n typeof body.executionId === \"string\" ? body.executionId : \"(missing)\"\n );\n console.log(\n \"toolOutputs keys:\",\n body.toolOutputs && typeof body.toolOutputs === \"object\"\n ? Object.keys(body.toolOutputs)\n : \"(none)\"\n );\n console.log(\"=== End Runtype Proxy Resume ===\\n\");\n }\n\n const response = await fetch(upstreamResumeUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(body)\n });\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"Content-Type\":\n response.headers.get(\"content-type\") ?? \"application/json\",\n \"Cache-Control\": \"no-store\"\n }\n });\n });\n\n return app;\n};\n\nexport const createVercelHandler = (options?: ChatProxyOptions) =>\n handle(createChatProxyApp(options));\n\n// Export pre-configured flows\nexport * from \"./flows/index.js\";\n\n// Export utility functions\nexport * from \"./utils/index.js\";\n\nexport default createChatProxyApp;\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Basic conversational assistant flow\n * This is the default flow for simple chat interactions\n */\nexport const CONVERSATIONAL_FLOW: RuntypeFlowConfig = {\n name: \"Streaming Prompt Flow\",\n description: \"Streaming chat generated by the widget\",\n steps: [\n {\n id: \"widget_prompt\",\n name: \"Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n responseFormat: \"markdown\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: \"you are a helpful assistant, chatting with a user\",\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Dynamic Form flow configuration\n * This flow returns forms as component directives for the widget to render\n */\nexport const FORM_DIRECTIVE_FLOW: RuntypeFlowConfig = {\n name: \"Dynamic Form Flow\",\n description: \"Returns dynamic forms as component directives\",\n steps: [\n {\n id: \"form_prompt\",\n name: \"Form Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful assistant that can have conversations and collect user information via forms.\n\nRESPONSE FORMAT:\nAlways respond with valid JSON. Choose the appropriate format:\n\n1. For CONVERSATIONAL responses or text answers:\n {\"text\": \"Your response here\"}\n\n2. When the user wants to SCHEDULE, BOOK, SIGN UP, or provide DETAILS (show a form):\n {\"component\": \"DynamicForm\", \"props\": {\"title\": \"Form Title\", \"description\": \"Optional description\", \"fields\": [...], \"submit_text\": \"Submit\"}}\n\n3. For BOTH explanation AND form:\n {\"text\": \"Your explanation\", \"component\": \"DynamicForm\", \"props\": {...}}\n\nFORM FIELD FORMAT:\nEach field in the \"fields\" array should have:\n- label (required): Display name for the field\n- name (optional): Field identifier (defaults to lowercase label with underscores)\n- type (optional): \"text\", \"email\", \"tel\", \"date\", \"time\", \"textarea\", \"number\" (defaults to \"text\")\n- placeholder (optional): Placeholder text\n- required (optional): true/false\n- width (optional): \"full\" or \"half\" — pair short related fields side-by-side with \"half\" (e.g. Phone + Company, City + Zip, First + Last name); use \"full\" or omit for everything else (especially textareas, emails, and standalone fields). Two consecutive \"half\" fields render in one row.\n\nEXAMPLES:\n\nUser: \"Schedule a demo for me\"\nResponse: {\"text\": \"I'd be happy to help you schedule a demo! Please fill out the form below:\", \"component\": \"DynamicForm\", \"props\": {\"title\": \"Schedule a Demo\", \"description\": \"Share your details and we'll follow up with a confirmation.\", \"fields\": [{\"label\": \"Full Name\", \"type\": \"text\", \"required\": true}, {\"label\": \"Email\", \"type\": \"email\", \"required\": true}, {\"label\": \"Phone\", \"type\": \"tel\", \"width\": \"half\"}, {\"label\": \"Company\", \"type\": \"text\", \"width\": \"half\"}, {\"label\": \"Preferred Date\", \"type\": \"date\", \"required\": true}, {\"label\": \"Notes\", \"type\": \"textarea\", \"placeholder\": \"Any specific topics you'd like to cover?\"}], \"submit_text\": \"Request Demo\"}}\n\nUser: \"What is AI?\"\nResponse: {\"text\": \"AI (Artificial Intelligence) refers to computer systems designed to perform tasks that typically require human intelligence, such as learning, reasoning, problem-solving, and understanding language.\"}\n\nUser: \"Collect my contact details\"\nResponse: {\"component\": \"DynamicForm\", \"props\": {\"title\": \"Contact Details\", \"fields\": [{\"label\": \"Name\", \"type\": \"text\", \"required\": true}, {\"label\": \"Email\", \"type\": \"email\", \"required\": true}, {\"label\": \"Phone\", \"type\": \"tel\"}], \"submit_text\": \"Save Details\"}}\n\nIMPORTANT:\n- Use {\"text\": \"...\"} for questions, explanations, and general conversation\n- Show a DynamicForm when user wants to provide information, schedule, book, or sign up\n- Create contextually appropriate form fields based on what the user is trying to do\n- Keep forms focused with only the relevant fields needed`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Shopping assistant flow configuration\n * This flow returns JSON actions for page interaction including:\n * - Simple messages\n * - Navigation with messages\n * - Element clicks with messages\n * - Stripe checkout\n */\nexport const SHOPPING_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Shopping Assistant Flow\",\n description: \"Returns JSON actions for page interaction\",\n steps: [\n {\n id: \"action_prompt\",\n name: \"Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant that can interact with web pages.\nYou will receive information about the current page's elements (class names and text content)\nand user messages. You must respond with JSON in one of these formats:\n\n1. Simple message:\n{\n \"action\": \"message\",\n \"text\": \"Your response text here\"\n}\n\n2. Navigate then show message (for navigation to another page):\n{\n \"action\": \"nav_then_click\",\n \"page\": \"http://site.com/page-url\",\n \"on_load_text\": \"Message to show after navigation\"\n}\n\n3. Show message and click an element:\n{\n \"action\": \"message_and_click\",\n \"element\": \".className-of-element\",\n \"text\": \"Your message text\"\n}\n\n4. Create Stripe checkout:\n{\n \"action\": \"checkout\",\n \"text\": \"Your message text\",\n \"items\": [\n {\"name\": \"Product Name\", \"price\": 2999, \"quantity\": 1}\n ]\n}\n\nGuidelines:\n- Use \"message\" for simple conversational responses\n- Use \"nav_then_click\" when you need to navigate to a different page (like a product detail page)\n- Use \"message_and_click\" when you want to click a button or element on the current page\n- Use \"checkout\" when the user wants to proceed to checkout/payment. Include items array with name (string), price (number in cents), and quantity (number)\n- When selecting elements, use the class names provided in the page context\n- Always respond with valid JSON only, no additional text\n- For product searches, format results as markdown links: [Product Name](url)\n- Be helpful and conversational in your messages\n- Product prices: Black Shirt - Medium: $29.99 (2999 cents), Blue Shirt - Large: $34.99 (3499 cents), Red T-Shirt - Small: $19.99 (1999 cents), Jeans - Medium: $49.99 (4999 cents)\n\nExample conversation flow:\n- User: \"I am looking for a black shirt in medium\"\n- You: {\"action\": \"message\", \"text\": \"Here are the products I found:\\\\n1. [Black Shirt - Medium](/products.html?product=black-shirt-medium) - $29.99\\\\n2. [Blue Shirt - Large](/products.html?product=blue-shirt-large) - $34.99\\\\n3. [Red T-Shirt - Small](/products.html?product=red-tshirt-small) - $19.99\\\\n4. [Jeans - Medium](/products.html?product=jeans-medium) - $49.99\\\\n\\\\nWould you like me to navigate to the first result and add it to your cart?\"}\n\n- User: \"No, I would like to add another shirt to the cart\"\n- You: {\"action\": \"message_and_click\", \"element\": \".AddToCartButton-blue-shirt-large\", \"text\": \"I've added the Blue Shirt - Large to your cart. Ready to checkout?\"}\n\n- User: \"yes\"\n- You: {\"action\": \"checkout\", \"text\": \"Perfect! I'll set up the checkout for you.\", \"items\": [{\"name\": \"Black Shirt - Medium\", \"price\": 2999, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n\n/**\n * Metadata-based shopping assistant flow configuration\n * This flow uses DOM context from record metadata instead of user message.\n * The metadata should include dom_elements, dom_body, page_url, and page_title.\n */\nexport const SHOPPING_ASSISTANT_METADATA_FLOW: RuntypeFlowConfig = {\n name: \"Metadata-Based Shopping Assistant\",\n description: \"Uses DOM context from record metadata for page interaction\",\n steps: [\n {\n id: \"metadata_action_prompt\",\n name: \"Metadata Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant that can interact with web pages.\n\nIMPORTANT: You have access to the current page's DOM elements through the record metadata, which includes:\n- dom_elements: Array of page elements with className, innerText, and tagName\n- dom_body: Complete HTML body of the page (if provided)\n- page_url: Current page URL\n- page_title: Page title\n\nThe dom_elements array provides information about clickable elements and their text content.\nUse this metadata to understand what's available on the page and help users interact with it.\n\nYou must respond with JSON in one of these formats:\n\n1. Simple message:\n{\n \"action\": \"message\",\n \"text\": \"Your response text here\"\n}\n\n2. Navigate then show message (for navigation to another page):\n{\n \"action\": \"nav_then_click\",\n \"page\": \"http://site.com/page-url\",\n \"on_load_text\": \"Message to show after navigation\"\n}\n\n3. Show message and click an element:\n{\n \"action\": \"message_and_click\",\n \"element\": \".className-of-element\",\n \"text\": \"Your message text\"\n}\n\n4. Create Stripe checkout:\n{\n \"action\": \"checkout\",\n \"text\": \"Your message text\",\n \"items\": [\n {\"name\": \"Product Name\", \"price\": 2999, \"quantity\": 1}\n ]\n}\n\nGuidelines:\n- Use \"message\" for simple conversational responses\n- Use \"nav_then_click\" when you need to navigate to a different page (like a product detail page)\n- Use \"message_and_click\" when you want to click a button or element on the current page\n- Use \"checkout\" when the user wants to proceed to checkout/payment. Include items array with name (string), price (number in cents), and quantity (number)\n- When selecting elements, use the class names from the dom_elements in the metadata\n- Always respond with valid JSON only, no additional text\n- For product searches, format results as markdown links: [Product Name](url)\n- Be helpful and conversational in your messages\n- Product prices: Black Shirt - Medium: $29.99 (2999 cents), Blue Shirt - Large: $34.99 (3499 cents), Red T-Shirt - Small: $19.99 (1999 cents), Jeans - Medium: $49.99 (4999 cents)\n\nExample conversation flow:\n- User: \"I am looking for a black shirt in medium\"\n- You: {\"action\": \"message\", \"text\": \"Here are the products I found:\\\\n1. [Black Shirt - Medium](/products.html?product=black-shirt-medium) - $29.99\\\\n2. [Blue Shirt - Large](/products.html?product=blue-shirt-large) - $34.99\\\\n3. [Red T-Shirt - Small](/products.html?product=red-tshirt-small) - $19.99\\\\n4. [Jeans - Medium](/products.html?product=jeans-medium) - $49.99\\\\n\\\\nWould you like me to navigate to the first result and add it to your cart?\"}\n\n- User: \"No, I would like to add another shirt to the cart\"\n- You: {\"action\": \"message_and_click\", \"element\": \".AddToCartButton-blue-shirt-large\", \"text\": \"I've added the Blue Shirt - Large to your cart. Ready to checkout?\"}\n\n- User: \"yes\"\n- You: {\"action\": \"checkout\", \"text\": \"Perfect! I'll set up the checkout for you.\", \"items\": [{\"name\": \"Black Shirt - Medium\", \"price\": 2999, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Component-aware flow for custom component rendering\n * This flow instructs the AI to respond with component directives in JSON format\n */\nexport const COMPONENT_FLOW: RuntypeFlowConfig = {\n name: \"Component Flow\",\n description: \"Flow configured for custom component rendering\",\n steps: [\n {\n id: \"component_prompt\",\n name: \"Component Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful assistant that can both have conversations and render custom UI components.\n\nRESPONSE FORMAT:\nAlways respond with valid JSON. Choose the appropriate format based on the user's request:\n\n1. For CONVERSATIONAL questions or text responses:\n {\"text\": \"Your response here\"}\n\n2. For VISUAL DISPLAYS or when the user asks to SHOW/DISPLAY something:\n {\"component\": \"ComponentName\", \"props\": {...}}\n\n3. For BOTH explanation AND visual:\n {\"text\": \"Your explanation here\", \"component\": \"ComponentName\", \"props\": {...}}\n\nAvailable components for visual displays:\n- ProductCard: Display product information. Props: title (string), price (number), description (string, optional), image (string, optional)\n- SimpleChart: Display a bar chart. Props: title (string), data (array of numbers), labels (array of strings, optional)\n- StatusBadge: Display a status badge. Props: status (string: \"success\", \"error\", \"warning\", \"info\", \"pending\"), message (string)\n- InfoCard: Display an information card. Props: title (string), content (string), icon (string, optional)\n\nExamples:\n- User asks \"What is the capital of France?\": {\"text\": \"The capital of France is Paris.\"}\n- User asks \"What does that chart show?\": {\"text\": \"The chart shows sales data increasing from 100 to 200 over three months.\"}\n- User asks \"Show me a product card\": {\"component\": \"ProductCard\", \"props\": {\"title\": \"Laptop\", \"price\": 999, \"description\": \"A great laptop\"}}\n- User asks \"Display a chart\": {\"component\": \"SimpleChart\", \"props\": {\"title\": \"Sales\", \"data\": [100, 150, 200], \"labels\": [\"Jan\", \"Feb\", \"Mar\"]}}\n- User asks \"Show me a chart and explain it\": {\"text\": \"Here's the sales data for Q1:\", \"component\": \"SimpleChart\", \"props\": {\"title\": \"Q1 Sales\", \"data\": [100, 150, 200], \"labels\": [\"Jan\", \"Feb\", \"Mar\"]}}\n\nIMPORTANT:\n- Use {\"text\": \"...\"} for questions, explanations, discussions, and general chat\n- Use {\"component\": \"...\", \"props\": {...}} ONLY when the user explicitly wants to SEE/VIEW/DISPLAY visual content\n- You can combine both: {\"text\": \"...\", \"component\": \"...\", \"props\": {...}} when you want to explain something AND show a visual\n- Never force a component when the user just wants information`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Bakery assistant flow configuration for \"Flour & Stone\" bakery demo\n * This flow returns JSON actions for page interaction including:\n * - Simple messages with bakery brand voice\n * - Navigation to bakery pages\n * - Add to cart interactions\n * - Stripe checkout\n *\n * Designed to guide users toward the gift card when asking for gift recommendations.\n */\nexport const BAKERY_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Bakery Assistant Flow\",\n description: \"Flour & Stone bakery shopping assistant with gift recommendations\",\n steps: [\n {\n id: \"bakery_action_prompt\",\n name: \"Bakery Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n systemPrompt: `You are a helpful shopping assistant for Flour & Stone, a premium artisan bakery known for traditional bread-making and exceptional pastries.\n\nBrand voice: Warm, knowledgeable, passionate about craft baking. Use phrases like \"fresh from the oven\", \"handcrafted with care\", \"artisan tradition\". Do not explain selectors, JSON, or templating to the user.\n\n## Live context (request inputs — substituted each turn)\n\nThe widget sends **only** these keys as dispatch **inputs** (nothing extra on the record for this demo).\n\n**Orientation**\n- Path: {{current_page}} (compare before nav_then_click; e.g. /bakery-goods.html)\n- Full URL: {{page_url}}\n- Title: {{page_title}}\n\n**Page DOM**\n- page_elements: JSON array of enriched nodes (selector, tagName, text, role, interactivity, attributes including data-*). Prefer **selector** for message_and_click when you click a specific control.\n- page_context: Same slice formatted for the LLM (structured card summaries when matched, then groups by interactivity).\n\n{{page_elements}}\n\n{{page_context}}\n\n**Cart (for checkout — mirror cart.items when user pays)**\n{{cart}}\n\n**Recent order (if any)**\n{{recent_order}}\n\nIf {{current_page}} already equals the page you would navigate to, use {\"action\":\"message\",...} instead of nav_then_click.\n\n## Discovering products\n\nUse {{page_context}} for a quick scan and {{page_elements}} for exact selectors and attributes. Product rows often include data-product in **attributes**; prices appear in **text**; add-to-cart controls are usually **clickable** with stable **selector** values.\n\n## Output: one JSON object only\n\nNo markdown fences, no commentary before/after. Valid JSON only.\n\n### 1. message\n{\"action\": \"message\", \"text\": \"...\"}\nUse for chat, clarifying questions, \"we're already on that page\", or when you need the user to choose (e.g. $25 vs $50 gift card).\n\n### 2. nav_then_click\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"...\"}\nUse root-relative paths starting with /. Only when current_page is different from the target. This **only** changes pages — it does **not** open Stripe or payment.\n\n### 3. add_to_cart\n{\"action\": \"add_to_cart\", \"text\": \"...\", \"item\": {\"id\": \"product-id\", \"name\": \"Product Name\", \"price\": 1200}}\nUse when adding from context without scrolling (optional; on goods page prefer scroll_then_add).\n\n### 4. scroll_then_add (preferred on /bakery-goods.html)\n{\"action\": \"scroll_then_add\", \"text\": \"...\", \"item\": {\"id\": \"...\", \"name\": \"...\", \"price\": 1200}}\nScrolls the product into view then adds one unit (cart merges duplicate ids into quantity).\n\n### 5. checkout → Stripe (this demo)\n{\"action\": \"checkout\", \"text\": \"Brief message\", \"items\": [{\"name\": \"...\", \"price\": 1200, \"quantity\": 2}, ...]}\n**Only** this action starts hosted checkout (Stripe). **Never** use nav_then_click to a \"/checkout\" URL for payment here.\nRequirements: cart in context must have items; **items array must list every cart line** with the same name, cent prices, and quantities as cart.items. If cart is null or empty, use message — do not checkout.\n\n### 6. message_and_click (rare)\nIf page_elements show a specific button selector and scroll_then_add is wrong, you may use message_and_click with a CSS selector — prefer scroll_then_add on bakery-goods.html.\n\n## Rules\n\n- Prices in JSON are always **integer cents** (1200 = $12.00).\n- After adding to cart, invite checkout or more shopping.\n- On checkout confirmation (\"yes\", \"checkout\", \"pay\", \"proceed\", etc.), build **items** from **cart.items** (all rows, correct quantity). Do not drop lines or invent prices.\n\n## Product catalog (ids and cent prices)\n\n- Sourdough Loaf: sourdough-loaf, 1200\n- Croissant Box (6): croissant-box, 2400\n- Cinnamon Rolls (4): cinnamon-rolls, 1800\n- Baguette Trio: baguette-trio, 900\n- Almond Tart: almond-tart, 800\n- Fruit Danish: fruit-danish, 600\n- $50 Gift Card: gift-card-50, 5000\n- $25 Gift Card: gift-card-25, 2500\n\n## Examples\n\nGift seeker on /bakery-locations.html:\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"Here are our goods! You'll find our gift cards below — $50 is our most popular. Want me to add one?\"}\n\nOn /bakery-goods.html, user wants $50 gift card:\n{\"action\": \"scroll_then_add\", \"text\": \"Added the $50 gift card. Ready to check out?\", \"item\": {\"id\": \"gift-card-50\", \"name\": \"$50 Gift Card\", \"price\": 5000}}\n\nUser on /bakery.html agrees to see products:\n{\"action\": \"nav_then_click\", \"page\": \"/bakery-goods.html\", \"on_load_text\": \"Here are our handcrafted goods — what sounds good today?\"}\n\nOn /bakery-goods.html, add sourdough:\n{\"action\": \"scroll_then_add\", \"text\": \"Sourdough is in your cart. Anything else, or shall we check out?\", \"item\": {\"id\": \"sourdough-loaf\", \"name\": \"Sourdough Loaf\", \"price\": 1200}}\n\nCart has one $50 gift card; user says yes to checkout:\n{\"action\": \"checkout\", \"text\": \"Opening secure checkout...\", \"items\": [{\"name\": \"$50 Gift Card\", \"price\": 5000, \"quantity\": 1}]}\n\nCart has sourdough (qty 1) and croissant box (qty 1); user says \"pay\":\n{\"action\": \"checkout\", \"text\": \"Taking you to checkout...\", \"items\": [{\"name\": \"Sourdough Loaf\", \"price\": 1200, \"quantity\": 1}, {\"name\": \"Croissant Box (6)\", \"price\": 2400, \"quantity\": 1}]}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","import type { RuntypeFlowConfig } from \"../index.js\";\n\n/**\n * Storefront assistant flow for the \"Everspun\" persistent-composer demo.\n *\n * Designed to feel like the agent is *building the storefront* in front of\n * the user: when they ask for product suggestions, the agent emits a\n * `ProductGrid` component directive carrying a small batch of structured\n * product cards (id, title, price, image, description). The persona widget\n * renders these as inline cards inside the chat panel via a registered\n * `componentRegistry` entry on the host. Plain conversational replies (fit,\n * fabric, care, styling Q&A) use a simple `{text}` JSON object and stay as\n * regular chat bubbles.\n */\nexport const STOREFRONT_ASSISTANT_FLOW: RuntypeFlowConfig = {\n name: \"Storefront Assistant Flow\",\n description:\n \"Everspun storefront assistant — surfaces product cards via component directives\",\n steps: [\n {\n id: \"storefront_action_prompt\",\n name: \"Storefront Action Prompt\",\n type: \"prompt\",\n enabled: true,\n config: {\n model: \"mercury-2\",\n reasoning: false,\n responseFormat: \"JSON\",\n outputVariable: \"prompt_result\",\n userPrompt: \"{{user_message}}\",\n 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.\n\nBrand 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.\n\n## Live context (substituted each turn)\n\nThe current product the shopper is viewing:\n{{current_product}}\n\nThe shopper's current bag:\n{{cart}}\n\n## Output: one JSON object only\n\nNo markdown fences, no commentary before/after. Valid JSON only. Three response shapes are valid:\n\n### 1. Plain message\n{\"text\": \"...\"}\n\nUse 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.\n\n### 2. Product grid (component directive)\n{\n \"text\": \"Brief intro line shown above the cards.\",\n \"component\": \"ProductGrid\",\n \"props\": {\n \"products\": [\n {\"id\": \"...\", \"title\": \"...\", \"price\": 24800, \"image\": \"https://...\", \"description\": \"...\"}\n ]\n }\n}\n\nUse 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.\n\n### 3. Add to cart (action)\n{\"action\": \"add_to_cart\", \"text\": \"Confirmation line.\", \"item\": {\"id\": \"...\", \"title\": \"...\", \"price\": 24800}}\n\nUse 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.\n\n## Rules\n\n- Prices in JSON are always **integer cents** (24800 = $248.00).\n- When the shopper asks \"what would go with this?\", ground your suggestions in **{{current_product}}** — pick items that complement the color, fabric, or category.\n- For \"under $X\" queries, only return products from the catalog priced under that amount.\n- For gift queries, prefer the gift card SKUs or compact accessories.\n- 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:\").\n- Never invent products. The catalog below is the entire universe.\n\n## Product catalog\n\n| id | title | price (cents) | image | description |\n|---|---|---|---|---|\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n\n## Examples\n\nUser asks \"show me cashmere essentials\":\n{\"text\": \"A few cashmere essentials:\", \"component\": \"ProductGrid\", \"props\": {\"products\": [\n {\"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.\"},\n {\"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.\"},\n {\"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.\"}\n]}}\n\nUser asks \"what pants would go with this?\" (current_product = camel cashmere sweater):\n{\"text\": \"These pair well with the camel:\", \"component\": \"ProductGrid\", \"props\": {\"products\": [\n {\"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.\"},\n {\"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.\"}\n]}}\n\nUser asks \"anything under $200?\":\n{\"text\": \"A few under $200:\", \"component\": \"ProductGrid\", \"props\": {\"products\": [\n {\"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.\"},\n {\"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.\"},\n {\"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.\"},\n {\"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.\"}\n]}}\n\nUser asks \"I need a gift under $300\":\n{\"text\": \"Gifts under $300:\", \"component\": \"ProductGrid\", \"props\": {\"products\": [\n {\"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.\"},\n {\"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.\"},\n {\"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.\"}\n]}}\n\nUser asks \"add the linen pant to my bag\":\n{\"action\": \"add_to_cart\", \"text\": \"Added the linen trouser to your bag.\", \"item\": {\"id\": \"linen-trouser\", \"title\": \"Wide-Leg Linen Trouser\", \"price\": 18800}}\n\nUser asks \"how does this fit?\" (current_product is the cashmere button-down):\n{\"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.\"}\n\nUser asks \"what's the best way to care for cashmere?\":\n{\"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.\"}`,\n previousMessages: \"{{messages}}\"\n }\n }\n ]\n};\n","/**\n * Stripe checkout helpers using the REST API\n * This approach works on all platforms including Cloudflare Workers, Vercel Edge, etc.\n */\n\n/**\n * Pinned API version for raw HTTP calls (no SDK). Required for organization API keys and\n * keeps behavior stable across accounts. See https://docs.stripe.com/api/versioning\n */\nconst STRIPE_API_VERSION = \"2026-03-25.dahlia\";\n\nexport interface CheckoutItem {\n name: string;\n price: number; // Price in cents\n quantity: number;\n}\n\nexport interface CreateCheckoutSessionOptions {\n secretKey: string;\n items: CheckoutItem[];\n successUrl: string;\n cancelUrl: string;\n /**\n * Target account for organization API keys (`sk_org_…`), e.g. `acct_1abc…` or\n * `acct_platform/acct_connected` per Stripe. Required with org keys.\n * @see https://docs.stripe.com/keys#organization-api-keys\n */\n stripeContext?: string;\n}\n\nexport interface CheckoutSessionResponse {\n success: boolean;\n checkoutUrl?: string;\n sessionId?: string;\n error?: string;\n}\n\nfunction parseStripeApiErrorBody(body: string): string | undefined {\n try {\n const parsed = JSON.parse(body) as {\n error?: { message?: string; type?: string };\n };\n const msg = parsed?.error?.message;\n return typeof msg === \"string\" && msg.length > 0 ? msg : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Creates a Stripe checkout session using the REST API\n * @param options - Checkout session configuration\n * @returns Checkout session response with URL and session ID\n */\nexport async function createCheckoutSession(\n options: CreateCheckoutSessionOptions\n): Promise<CheckoutSessionResponse> {\n const { secretKey, items, successUrl, cancelUrl, stripeContext } = options;\n const trimmedContext = stripeContext?.trim() || undefined;\n\n try {\n if (secretKey.startsWith(\"sk_org\") && !trimmedContext) {\n return {\n success: false,\n error:\n \"Organization Stripe keys (sk_org_…) require stripeContext / STRIPE_CONTEXT with the target account (e.g. acct_…). See https://docs.stripe.com/keys#organization-api-keys\",\n };\n }\n\n // Validate items\n if (!items || !Array.isArray(items) || items.length === 0) {\n return {\n success: false,\n error: \"Items array is required\"\n };\n }\n\n for (const item of items) {\n if (!item.name || typeof item.price !== \"number\" || typeof item.quantity !== \"number\") {\n return {\n success: false,\n error: \"Each item must have name (string), price (number in cents), and quantity (number)\"\n };\n }\n if (\n !Number.isFinite(item.price) ||\n !Number.isInteger(item.price) ||\n item.price < 1 ||\n !Number.isInteger(item.quantity) ||\n item.quantity < 1\n ) {\n return {\n success: false,\n error:\n \"Each item needs a positive integer price (cents) and quantity (Stripe rejects decimals or zero)\",\n };\n }\n }\n\n // Build line items for URL encoding\n const lineItems = items.map((item) => ({\n price_data: {\n currency: \"usd\",\n product_data: {\n name: item.name,\n },\n unit_amount: item.price,\n },\n quantity: item.quantity,\n }));\n\n // Convert line items to URL-encoded format\n const params = new URLSearchParams({\n \"payment_method_types[0]\": \"card\",\n \"mode\": \"payment\",\n \"success_url\": successUrl,\n \"cancel_url\": cancelUrl,\n });\n\n // Add line items to params\n lineItems.forEach((item, index) => {\n params.append(`line_items[${index}][price_data][currency]`, item.price_data.currency);\n params.append(`line_items[${index}][price_data][product_data][name]`, item.price_data.product_data.name);\n params.append(`line_items[${index}][price_data][unit_amount]`, item.price_data.unit_amount.toString());\n params.append(`line_items[${index}][quantity]`, item.quantity.toString());\n });\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${secretKey}`,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"Stripe-Version\": STRIPE_API_VERSION,\n };\n if (trimmedContext) {\n headers[\"Stripe-Context\"] = trimmedContext;\n }\n\n // Create Stripe checkout session using REST API\n const stripeResponse = await fetch(\"https://api.stripe.com/v1/checkout/sessions\", {\n method: \"POST\",\n headers,\n body: params,\n });\n\n if (!stripeResponse.ok) {\n const errorData = await stripeResponse.text();\n const stripeMessage = parseStripeApiErrorBody(errorData);\n console.error(\"Stripe API error:\", errorData);\n return {\n success: false,\n error: stripeMessage ?? \"Failed to create checkout session\",\n };\n }\n\n const session = await stripeResponse.json() as { url: string; id: string };\n\n return {\n success: true,\n checkoutUrl: session.url,\n sessionId: session.id,\n };\n } catch (error) {\n console.error(\"Stripe checkout error:\", error);\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Failed to create checkout session\"\n };\n }\n}\n"],"mappings":"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","names":["_a","_b","_c"]}
|
package/package.json
CHANGED
package/src/flows/index.ts
CHANGED
package/src/flows/scheduling.ts
CHANGED
|
@@ -40,11 +40,12 @@ Each field in the "fields" array should have:
|
|
|
40
40
|
- type (optional): "text", "email", "tel", "date", "time", "textarea", "number" (defaults to "text")
|
|
41
41
|
- placeholder (optional): Placeholder text
|
|
42
42
|
- required (optional): true/false
|
|
43
|
+
- width (optional): "full" or "half" — pair short related fields side-by-side with "half" (e.g. Phone + Company, City + Zip, First + Last name); use "full" or omit for everything else (especially textareas, emails, and standalone fields). Two consecutive "half" fields render in one row.
|
|
43
44
|
|
|
44
45
|
EXAMPLES:
|
|
45
46
|
|
|
46
47
|
User: "Schedule a demo for me"
|
|
47
|
-
Response: {"text": "I'd be happy to help you schedule a demo! Please fill out the form below:", "component": "DynamicForm", "props": {"title": "Schedule a Demo", "description": "Share your details and we'll follow up with a confirmation.", "fields": [{"label": "Full Name", "type": "text", "required": true}, {"label": "Email", "type": "email", "required": true}, {"label": "Company", "type": "text"}, {"label": "Preferred Date", "type": "date", "required": true}, {"label": "Notes", "type": "textarea", "placeholder": "Any specific topics you'd like to cover?"}], "submit_text": "Request Demo"}}
|
|
48
|
+
Response: {"text": "I'd be happy to help you schedule a demo! Please fill out the form below:", "component": "DynamicForm", "props": {"title": "Schedule a Demo", "description": "Share your details and we'll follow up with a confirmation.", "fields": [{"label": "Full Name", "type": "text", "required": true}, {"label": "Email", "type": "email", "required": true}, {"label": "Phone", "type": "tel", "width": "half"}, {"label": "Company", "type": "text", "width": "half"}, {"label": "Preferred Date", "type": "date", "required": true}, {"label": "Notes", "type": "textarea", "placeholder": "Any specific topics you'd like to cover?"}], "submit_text": "Request Demo"}}
|
|
48
49
|
|
|
49
50
|
User: "What is AI?"
|
|
50
51
|
Response: {"text": "AI (Artificial Intelligence) refers to computer systems designed to perform tasks that typically require human intelligence, such as learning, reasoning, problem-solving, and understanding language."}
|
|
@@ -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
|
+
};
|