Skip to content

Commit 59ffe91

Browse files
authored
fix(showcase/aimock): align local Docker setup with production (CopilotKit#4896)
## Summary Fixes 4 critical differences between local and production aimock setup that caused behavior divergence: 1. **Remove catch-all fixture from feature-parity.json** -- the `"match": {}` entry at the end intercepted ALL unmatched requests with a generic response, preventing `--proxy-only` from falling through to real providers (OpenAI/Anthropic/Gemini) 2. **Merge d5-recorded fixtures into d5-all.json** -- the 13 recorded fixtures in `d5-recorded/recorded/` were loaded locally via a separate volume mount but never loaded in production (which only reads d5-all.json, smoke.json, feature-parity.json). Now they live in d5-all.json and the separate volume mount + `--fixtures` entry are removed. 3. **Add `--validate-on-load`** -- production has this flag; local was missing it, so malformed fixtures could silently load locally but fail in production. 4. **Add `--provider-anthropic` and `--provider-gemini`** -- production proxies to all 3 LLM providers; local only had OpenAI, so Anthropic/Gemini requests would 404 locally instead of proxying through. ## Test plan - [ ] `node -e "JSON.parse(require('fs').readFileSync('showcase/aimock/d5-all.json','utf8'));console.log('Valid')"` passes - [ ] `node -e "JSON.parse(require('fs').readFileSync('showcase/aimock/feature-parity.json','utf8'));console.log('Valid')"` passes - [ ] `docker compose -f showcase/docker-compose.local.yml up aimock` starts without validation errors - [ ] Unmatched requests proxy to real providers instead of returning generic catch-all
2 parents 13d2cc8 + 680a4eb commit 59ffe91

3 files changed

Lines changed: 277 additions & 15 deletions

File tree

showcase/aimock/d5-all.json

Lines changed: 272 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2604,11 +2604,7 @@
26042604
{
26052605
"id": "status-col",
26062606
"component": "Column",
2607-
"children": [
2608-
"status-api",
2609-
"status-db",
2610-
"status-workers"
2611-
],
2607+
"children": ["status-api", "status-db", "status-workers"],
26122608
"gap": 8
26132609
},
26142610
{
@@ -2824,6 +2820,277 @@
28242820
"response": {
28252821
"content": "Based on your context I can see your name and recent activity. I'll keep responses calibrated to your timezone."
28262822
}
2823+
},
2824+
{
2825+
"match": {
2826+
"userMessage": "First use the query_data tool to fetch the financial sales data, then using A2UI, show me a sales dashboard with total revenue, new customers, and conversion rate metrics. Include a pie chart of revenue by category and a bar chart of monthly sales.",
2827+
"model": "gpt-4o",
2828+
"turnIndex": 0,
2829+
"hasToolResult": false
2830+
},
2831+
"response": {
2832+
"toolCalls": [
2833+
{
2834+
"name": "query_data",
2835+
"arguments": "{\"query\":\"financial sales data including total revenue, new customers, conversion rate, revenue by category, and monthly sales\"}",
2836+
"id": "call_jWKVlcdnnYiV6MT3wzNZphnF"
2837+
}
2838+
]
2839+
},
2840+
"metadata": {
2841+
"systemHash": "989f650c",
2842+
"toolsHash": "6e36f0b6"
2843+
},
2844+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-54-59-257Z-b614f3f8.json"
2845+
},
2846+
{
2847+
"match": {
2848+
"userMessage": "First use the query_data tool to fetch the financial sales data, then using A2UI, show me a sales dashboard with total revenue, new customers, and conversion rate metrics. Include a pie chart of revenue by category and a bar chart of monthly sales.",
2849+
"model": "gpt-4o",
2850+
"turnIndex": 1,
2851+
"hasToolResult": true
2852+
},
2853+
"response": {
2854+
"toolCalls": [
2855+
{
2856+
"name": "generate_a2ui",
2857+
"arguments": "{}",
2858+
"id": "call_K6au6wp1CjG4OuiGzhL1yCXL"
2859+
}
2860+
]
2861+
},
2862+
"metadata": {
2863+
"systemHash": "989f650c",
2864+
"toolsHash": "6e36f0b6"
2865+
},
2866+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-55-00-312Z-814153b0.json"
2867+
},
2868+
{
2869+
"match": {
2870+
"model": "gpt-4.1",
2871+
"turnIndex": 0,
2872+
"hasToolResult": false
2873+
},
2874+
"response": {
2875+
"toolCalls": [
2876+
{
2877+
"name": "render_a2ui",
2878+
"arguments": "{\"surfaceId\":\"dashboard-001\",\"catalogId\":\"copilotkit://app-dashboard-catalog\"}",
2879+
"id": "call_Q3rTavqr4dXJ08ne9bEesL9d"
2880+
}
2881+
]
2882+
},
2883+
"metadata": {
2884+
"systemHash": "28fe3ed7",
2885+
"toolsHash": "2cbb50da"
2886+
},
2887+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-55-02-522Z-e88af85f.json"
2888+
},
2889+
{
2890+
"match": {
2891+
"userMessage": "First use the query_data tool to fetch the financial sales data, then using A2UI, show me a sales dashboard with total revenue, new customers, and conversion rate metrics. Include a pie chart of revenue by category and a bar chart of monthly sales.",
2892+
"model": "gpt-4o",
2893+
"turnIndex": 2,
2894+
"hasToolResult": true
2895+
},
2896+
"response": {
2897+
"toolCalls": [
2898+
{
2899+
"name": "pieChart",
2900+
"arguments": "{\"title\": \"Revenue by Category\", \"description\": \"Distribution of revenue across different categories\", \"data\": [{\"label\": \"Enterprise Subscriptions\", \"value\": 94000}, {\"label\": \"Pro Tier Upgrades\", \"value\": 66500}, {\"label\": \"API Usage Overages\", \"value\": 34500}, {\"label\": \"Consulting Services\", \"value\": 54000}, {\"label\": \"Marketplace Sales\", \"value\": 42800}, {\"label\": \"Partnership Revenue\", \"value\": 25700}, {\"label\": \"Training & Workshops\", \"value\": 10200}]}",
2901+
"id": "call_OJDsrTtDcWoV0PObgKtU9fXv"
2902+
},
2903+
{
2904+
"name": "barChart",
2905+
"arguments": "{\"title\": \"Monthly Sales\", \"description\": \"Sales performance over the months\", \"data\": [{\"label\": \"January\", \"value\": 82800}, {\"label\": \"February\", \"value\": 119000}, {\"label\": \"March\", \"value\": 135700}]}",
2906+
"id": "call_o9RdNrZTq8qV0Majn12OTZcu"
2907+
}
2908+
]
2909+
},
2910+
"metadata": {
2911+
"systemHash": "989f650c",
2912+
"toolsHash": "6e36f0b6"
2913+
},
2914+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-55-05-134Z-890eead9.json"
2915+
},
2916+
{
2917+
"match": {
2918+
"userMessage": "First use the query_data tool to fetch the financial sales data, then using A2UI, show me a sales dashboard with total revenue, new customers, and conversion rate metrics. Include a pie chart of revenue by category and a bar chart of monthly sales.",
2919+
"model": "gpt-4o",
2920+
"turnIndex": 3,
2921+
"hasToolResult": true
2922+
},
2923+
"response": {
2924+
"content": "The sales dashboard is now available, featuring metrics for total revenue, new customers, and conversion rate. Additionally, you can view a pie chart of revenue by category and a bar chart of monthly sales."
2925+
},
2926+
"metadata": {
2927+
"systemHash": "989f650c",
2928+
"toolsHash": "6e36f0b6"
2929+
},
2930+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-55-07-715Z-9531bf64.json"
2931+
},
2932+
{
2933+
"match": {
2934+
"userMessage": "Using the generateSandboxedUi tool, build a modern calculator with standard buttons plus labeled metric shortcut buttons that insert their values into the display when clicked. Use sample company data.",
2935+
"model": "gpt-4o",
2936+
"turnIndex": 0,
2937+
"hasToolResult": false
2938+
},
2939+
"response": {
2940+
"toolCalls": [
2941+
{
2942+
"name": "generateSandboxedUi",
2943+
"arguments": "{\"initialHeight\":400,\"placeholderMessages\":[\"Building calculator UI...\"],\"css\":\"body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f4f4f9; } .calculator { width: 320px; background-color: #fff; border-radius: 10px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); } .display { background-color: #222; color: #fff; font-size: 2em; padding: 20px; text-align: right; border-top-left-radius: 10px; border-top-right-radius: 10px; } .buttons { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 1px; } .button { background-color: #e0e0e0; padding: 20px; font-size: 1.2em; text-align: center; cursor: pointer; transition: background-color 0.2s; } .button:hover { background-color: #d0d0d0; } .metric-button { background-color: #4caf50; color: #fff; }\",\"html\":\"<div class='calculator'> <div class='display' id='display'>0</div> <div class='buttons'> <div class='button' onclick='appendNumber(7)'>7</div> <div class='button' onclick='appendNumber(8)'>8</div> <div class='button' onclick='appendNumber(9)'>9</div> <div class='button' onclick='operate(\\\"/\\\")'>/</div> <div class='button' onclick='appendNumber(4)'>4</div> <div class='button' onclick='appendNumber(5)'>5</div> <div class='button' onclick='appendNumber(6)'>6</div> <div class='button' onclick='operate(\\\"*\\\")'>*</div> <div class='button' onclick='appendNumber(1)'>1</div> <div class='button' onclick='appendNumber(2)'>2</div> <div class='button' onclick='appendNumber(3)'>3</div> <div class='button' onclick='operate(\\\"-\\\")'>-</div> <div class='button' onclick='appendNumber(0)'>0</div> <div class='button' onclick='clearDisplay()'>C</div> <div class='button' onclick='calculate()'>=</div> <div class='button' onclick='operate(\\\"+\\\")'>+</div> <div class='button metric-button' onclick='insertMetric(1000)'>Revenue</div> <div class='button metric-button' onclick='insertMetric(500)'>Expenses</div> <div class='button metric-button' onclick='insertMetric(200)'>Profit</div> </div> </div>\",\"jsFunctions\":\"function appendNumber(number) { const display = document.getElementById('display'); if (display.innerText === '0') { display.innerText = number; } else { display.innerText += number; } } function operate(operator) { const display = document.getElementById('display'); display.innerText += ' ' + operator + ' '; } function clearDisplay() { document.getElementById('display').innerText = '0'; } function calculate() { const display = document.getElementById('display'); try { display.innerText = eval(display.innerText); } catch { display.innerText = 'Error'; } } function insertMetric(value) { const display = document.getElementById('display'); if (display.innerText === '0') { display.innerText = value; } else { display.innerText += value; } }\",\"jsExpressions\":[]}",
2944+
"id": "call_u2aFJvWZUKzzlvqfnDeWhMyC"
2945+
}
2946+
]
2947+
},
2948+
"metadata": {
2949+
"systemHash": "989f650c",
2950+
"toolsHash": "6e36f0b6"
2951+
},
2952+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-56-02-547Z-7afa30ad.json"
2953+
},
2954+
{
2955+
"match": {
2956+
"userMessage": "Using the generateSandboxedUi tool, build a modern calculator with standard buttons plus labeled metric shortcut buttons that insert their values into the display when clicked. Use sample company data.",
2957+
"model": "gpt-4o",
2958+
"turnIndex": 1,
2959+
"hasToolResult": true
2960+
},
2961+
"response": {
2962+
"content": "The modern calculator with standard buttons and labeled metric shortcut buttons is ready for use. You can perform calculations and insert sample company data values like Revenue, Expenses, and Profit directly into the display."
2963+
},
2964+
"metadata": {
2965+
"systemHash": "989f650c",
2966+
"toolsHash": "6e36f0b6"
2967+
},
2968+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-56-04-559Z-1bafed55.json"
2969+
},
2970+
{
2971+
"match": {
2972+
"userMessage": "Enable app mode and add three todos about learning CopilotKit: one about reading the docs, one about building a prototype, and one about exploring agent state.",
2973+
"model": "gpt-4o",
2974+
"turnIndex": 0,
2975+
"hasToolResult": false
2976+
},
2977+
"response": {
2978+
"toolCalls": [
2979+
{
2980+
"name": "enableAppMode",
2981+
"arguments": "{}",
2982+
"id": "call_c35xh8AQaOh9TPQ2R2iQvztQ"
2983+
},
2984+
{
2985+
"name": "manage_todos",
2986+
"arguments": "{\"todos\": [{\"title\": \"Read CopilotKit documentation\", \"description\": \"Go through the official documentation to understand the basics.\", \"emoji\": \"📚\", \"status\": \"pending\"}, {\"title\": \"Build a CopilotKit prototype\", \"description\": \"Create a simple prototype using CopilotKit to test its features.\", \"emoji\": \"🛠️\", \"status\": \"pending\"}, {\"title\": \"Explore CopilotKit agent state\", \"description\": \"Investigate how agent state is managed and utilized in CopilotKit.\", \"emoji\": \"🔍\", \"status\": \"pending\"}]}",
2987+
"id": "call_luLxT9XGJjj6HX5tfQUzkfiA"
2988+
}
2989+
]
2990+
},
2991+
"metadata": {
2992+
"systemHash": "989f650c",
2993+
"toolsHash": "6e36f0b6"
2994+
},
2995+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-56-51-948Z-f0ba1066.json"
2996+
},
2997+
{
2998+
"match": {
2999+
"userMessage": "Enable app mode and add three todos about learning CopilotKit: one about reading the docs, one about building a prototype, and one about exploring agent state.",
3000+
"model": "gpt-4o",
3001+
"turnIndex": 1,
3002+
"hasToolResult": true
3003+
},
3004+
"response": {
3005+
"error": {
3006+
"message": "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_luLxT9XGJjj6HX5tfQUzkfiA",
3007+
"type": "invalid_request_error"
3008+
},
3009+
"status": 400
3010+
},
3011+
"metadata": {
3012+
"systemHash": "989f650c",
3013+
"toolsHash": "6e36f0b6"
3014+
},
3015+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T20-56-53-134Z-531d8259.json"
3016+
},
3017+
{
3018+
"match": {
3019+
"userMessage": "What is the weather in Tokyo?",
3020+
"model": "gpt-4o-mini",
3021+
"turnIndex": 0,
3022+
"hasToolResult": false
3023+
},
3024+
"response": {
3025+
"toolCalls": [
3026+
{
3027+
"name": "get_weather",
3028+
"arguments": "{\"location\":\"Tokyo\"}",
3029+
"id": "call_0uXBAr4ySTrLQGQgszQYFtEJ"
3030+
}
3031+
]
3032+
},
3033+
"metadata": {
3034+
"systemHash": "26c3eb38",
3035+
"toolsHash": "686e39e9"
3036+
},
3037+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T22-01-34-384Z-edf48666.json"
3038+
},
3039+
{
3040+
"match": {
3041+
"userMessage": "What is the weather in Tokyo?",
3042+
"model": "gpt-4o-mini",
3043+
"turnIndex": 1,
3044+
"hasToolResult": true
3045+
},
3046+
"response": {
3047+
"content": "The weather in Tokyo is currently 68°F, with 55% humidity, a wind speed of 10 mph, and sunny conditions."
3048+
},
3049+
"metadata": {
3050+
"systemHash": "26c3eb38",
3051+
"toolsHash": "686e39e9"
3052+
},
3053+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T22-01-35-344Z-6ea6b18a.json"
3054+
},
3055+
{
3056+
"match": {
3057+
"userMessage": "intro call with the sales team",
3058+
"hasToolResult": false
3059+
},
3060+
"response": {
3061+
"toolCalls": [
3062+
{
3063+
"name": "book_call",
3064+
"arguments": "{\"topic\":\"Intro with sales\",\"attendee\":\"User\"}",
3065+
"id": "call_jisCjcR5jdyQQ0JHJXhjqUlm"
3066+
}
3067+
]
3068+
},
3069+
"metadata": {
3070+
"systemHash": "72621894",
3071+
"toolsHash": "7c3b6590"
3072+
},
3073+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T22-01-44-366Z-ec8eaa56.json"
3074+
},
3075+
{
3076+
"match": {
3077+
"userMessage": "1:1 with Alice",
3078+
"hasToolResult": false
3079+
},
3080+
"response": {
3081+
"toolCalls": [
3082+
{
3083+
"name": "book_call",
3084+
"arguments": "{\"topic\":\"Review Q2 goals\",\"attendee\":\"Alice\"}",
3085+
"id": "call_xwbY36SO2wpay1F9cHCUuQGx"
3086+
}
3087+
]
3088+
},
3089+
"metadata": {
3090+
"systemHash": "72621894",
3091+
"toolsHash": "7c3b6590"
3092+
},
3093+
"_comment": "Recorded fixture from d5-recorded: openai-2026-05-15T22-02-33-401Z-bb9eb9db.json"
28273094
}
28283095
]
28293096
}

showcase/aimock/feature-parity.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,13 +1428,6 @@
14281428
"response": {
14291429
"content": "Based on your recent activity, I'd suggest exploring features you haven't tried yet — check out the documentation for advanced use-cases, or dive into a hands-on tutorial to deepen your understanding."
14301430
}
1431-
},
1432-
{
1433-
"_comment": "Last-resort catch-all for neutral/default assistant demos (chat-customization-css, prebuilt-sidebar, prebuilt-popup, chat-slots, headless-simple). Matches any message not caught by a more specific fixture above, preventing aimock from proxying to real OpenAI with a fake key.",
1434-
"match": {},
1435-
"response": {
1436-
"content": "I'm a helpful assistant. I can help with a wide range of tasks — writing, analysis, coding, math, and more. What would you like to work on?"
1437-
}
14381431
}
14391432
]
14401433
}

showcase/docker-compose.local.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ services:
4646
- ./aimock/d5-all.json:/showcase-fixtures/d5-all.json:ro
4747
- ./aimock/smoke.json:/showcase-fixtures/smoke.json:ro
4848
- ./aimock/feature-parity.json:/showcase-fixtures/feature-parity.json:ro
49-
- ./aimock/d5-recorded:/showcase-fixtures/d5-recorded:ro
5049
# `--proxy-only` + `--provider-openai` mirrors the Railway aimock setup:
5150
# matched fixtures short-circuit deterministically; unmatched requests
5251
# fall through to real OpenAI (using OPENAI_API_KEY from .env). Without
@@ -61,6 +60,10 @@ services:
6160
"--proxy-only",
6261
"--provider-openai",
6362
"https://api.openai.com",
63+
"--provider-anthropic",
64+
"https://api.anthropic.com",
65+
"--provider-gemini",
66+
"https://generativelanguage.googleapis.com",
6467
# Natural-feel streaming so demos look like a real LLM rather than
6568
# an instant dump. 8 chars/chunk × 60 ms = ~130 chars/sec ≈ 30-40
6669
# tokens/sec, the lower end of Gemini 2.5-flash's real streaming
@@ -77,8 +80,7 @@ services:
7780
"/showcase-fixtures/smoke.json",
7881
"--fixtures",
7982
"/showcase-fixtures/feature-parity.json",
80-
"--fixtures",
81-
"/showcase-fixtures/d5-recorded",
83+
"--validate-on-load",
8284
]
8385
healthcheck:
8486
test:

0 commit comments

Comments
 (0)