Skip to content

Commit 6a2c290

Browse files
7418claude
andcommitted
fix: progressive widget MCP — only register when conversation needs widgets
The codepilot-widget MCP server (added in 0.38) registers an in-process tool for loading detailed design specs. SDK tool discovery during init adds overhead to every request, even plain text conversations. Change to progressive loading: detect widget intent via keywords in the user prompt, conversation history (show-widget fences), or system prompt. Only register the MCP server when the conversation likely involves widget generation. - Plain text conversations: no MCP server, no overhead (same as v0.37) - Widget conversations: MCP server registered, full design specs available - Keywords: 可视化/图表/流程图/visualize/diagram/chart/flowchart etc. - Conversation history check: existing show-widget output triggers it Also reverts the WIDGET_SYSTEM_PROMPT_HINT experiment — keeps the full WIDGET_SYSTEM_PROMPT in system prompt (same as v0.37) since prompt token count is not the bottleneck. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 303facb commit 6a2c290

File tree

4 files changed

+36
-45
lines changed

4 files changed

+36
-45
lines changed

src/__tests__/unit/widget-system.test.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
} from '../../components/chat/MessageItem';
2828

2929
import { WIDGET_CSS_BRIDGE } from '../../lib/widget-css-bridge';
30-
import { WIDGET_SYSTEM_PROMPT, WIDGET_SYSTEM_PROMPT_HINT, getGuidelines, createWidgetMcpServer } from '../../lib/widget-guidelines';
30+
import { WIDGET_SYSTEM_PROMPT, getGuidelines, createWidgetMcpServer } from '../../lib/widget-guidelines';
3131

3232
// ── Sanitization ────────────────────────────────────────────────────────
3333

@@ -297,23 +297,7 @@ describe('WIDGET_CSS_BRIDGE', () => {
297297
});
298298
});
299299

300-
// ── System prompt hint (injected into system prompt) ────────────────────
301-
302-
describe('WIDGET_SYSTEM_PROMPT_HINT', () => {
303-
it('references the codepilot_load_widget_guidelines tool', () => {
304-
assert.ok(WIDGET_SYSTEM_PROMPT_HINT.includes('codepilot_load_widget_guidelines'));
305-
});
306-
307-
it('is ultra-minimal to reduce per-request token overhead', () => {
308-
assert.ok(WIDGET_SYSTEM_PROMPT_HINT.length < 250, `hint should be <250 chars, got ${WIDGET_SYSTEM_PROMPT_HINT.length}`);
309-
});
310-
311-
it('mentions widget capability', () => {
312-
assert.ok(WIDGET_SYSTEM_PROMPT_HINT.includes('widget-capability'));
313-
});
314-
});
315-
316-
// ── Full system prompt (loaded on demand via MCP tool) ──────────────────
300+
// ── System prompt ───────────────────────────────────────────────────────
317301

318302
describe('WIDGET_SYSTEM_PROMPT', () => {
319303
it('includes show-widget fence format', () => {

src/app/api/chat/route.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,15 +330,13 @@ Start by greeting the user and asking the first question.
330330
// CLI tools context injection failed — don't block chat
331331
}
332332

333-
// Generative UI: gated by user setting (default: enabled).
334-
// Only a short hint is injected into the system prompt — full guidelines
335-
// are loaded on-demand via the codepilot-widget MCP tool to save tokens.
333+
// Inject widget (generative UI) system prompt — gated by user setting (default: enabled)
336334
const generativeUISetting = getSetting('generative_ui_enabled');
337335
const generativeUIEnabled = generativeUISetting !== 'false';
338336
if (generativeUIEnabled) {
339337
try {
340-
const { WIDGET_SYSTEM_PROMPT_HINT } = await import('@/lib/widget-guidelines');
341-
finalSystemPrompt = (finalSystemPrompt || '') + '\n\n' + WIDGET_SYSTEM_PROMPT_HINT;
338+
const { WIDGET_SYSTEM_PROMPT } = await import('@/lib/widget-guidelines');
339+
finalSystemPrompt = (finalSystemPrompt || '') + '\n\n' + WIDGET_SYSTEM_PROMPT;
342340
} catch {
343341
// Widget prompt injection failed — don't block chat
344342
}

src/lib/claude-client.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
445445
console.warn('[claude-client] No API key found: no active provider, no legacy settings, and no ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN in environment');
446446
}
447447

448+
448449
// Check if dangerously_skip_permissions is enabled globally or per-session
449450
const globalSkip = getSetting('dangerously_skip_permissions') === 'true';
450451
const skipPermissions = globalSkip || !!sessionBypassPermissions;
@@ -509,15 +510,32 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
509510
queryOptions.mcpServers = toSdkMcpConfig(mcpServers);
510511
}
511512

512-
// Widget guidelines: in-process MCP server for on-demand loading.
513-
// Model calls codepilot_load_widget_guidelines before generating widgets.
513+
// Widget guidelines: progressive loading strategy.
514+
// The system prompt always includes WIDGET_SYSTEM_PROMPT with format rules.
515+
// The MCP server (detailed design specs) is only registered when the
516+
// conversation likely involves widget generation — detected by keywords in
517+
// the user's prompt or existing show-widget output in conversation history.
518+
// This avoids SDK tool discovery overhead (~1s) on plain text conversations.
514519
if (generativeUI !== false) {
515-
const { createWidgetMcpServer } = await import('@/lib/widget-guidelines');
516-
const widgetServer = createWidgetMcpServer();
517-
queryOptions.mcpServers = {
518-
...(queryOptions.mcpServers || {}),
519-
'codepilot-widget': widgetServer,
520-
};
520+
const needsWidgetSpecs = (() => {
521+
const widgetKeywords = /|||线|||visualiz|diagram|chart|flowchart|timeline|infographic|interactive|widget|show-widget|hierarchy|dashboard/i;
522+
// Check current prompt
523+
if (widgetKeywords.test(prompt)) return true;
524+
// Check if conversation already has widgets (resume context)
525+
if (conversationHistory?.some(m => m.content.includes('show-widget'))) return true;
526+
// Check system prompt for image/widget agent mode
527+
if (systemPrompt && widgetKeywords.test(systemPrompt)) return true;
528+
return false;
529+
})();
530+
531+
if (needsWidgetSpecs) {
532+
const { createWidgetMcpServer } = await import('@/lib/widget-guidelines');
533+
const widgetServer = createWidgetMcpServer();
534+
queryOptions.mcpServers = {
535+
...(queryOptions.mcpServers || {}),
536+
'codepilot-widget': widgetServer,
537+
};
538+
}
521539
}
522540

523541
// Pass through SDK-specific options from ClaudeStreamOptions
@@ -806,7 +824,6 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
806824
let tokenUsage: TokenUsage | null = null;
807825
// Track pending TodoWrite tool_use_ids so we can sync after successful execution
808826
const pendingTodoWrites = new Map<string, Array<{ content: string; status: string; activeForm?: string }>>();
809-
810827
for await (const message of conversation) {
811828
if (abortController?.signal.aborted) {
812829
break;

src/lib/widget-guidelines.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,15 @@
44
* Based on Anthropic's actual generative UI guidelines extracted from claude.ai,
55
* adapted for CodePilot's code-fence trigger mechanism and CSS variable bridge.
66
*
7-
* WIDGET_SYSTEM_PROMPT_HINT is a tiny capability declaration (~30 tokens),
8-
* always injected into the system prompt when generative UI is enabled.
9-
* The full WIDGET_SYSTEM_PROMPT (format + rules) and detailed module guidelines
10-
* are loaded on demand via the `codepilot_load_widget_guidelines` MCP tool,
11-
* saving ~90% system prompt tokens on conversations that don't involve widgets.
7+
* The WIDGET_SYSTEM_PROMPT is a minimal capability declaration (~150 tokens),
8+
* always injected into the system prompt. Full module guidelines are loaded
9+
* on demand via the `codepilot_load_widget_guidelines` in-process MCP tool.
1210
*/
1311

1412
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
1513
import { z } from 'zod';
1614

17-
// ── System prompt hint (always injected — ultra-minimal) ────────────────────
18-
19-
export const WIDGET_SYSTEM_PROMPT_HINT = `<widget-capability>
20-
You can create interactive visualizations. Call \`codepilot_load_widget_guidelines\` before generating your first widget to load the required format and design specs.
21-
</widget-capability>`;
22-
23-
// ── Full widget rules (loaded on demand via MCP tool) ───────────────────────
15+
// ── System prompt (always injected — minimal version) ───────────────────────
2416

2517
export const WIDGET_SYSTEM_PROMPT = `<widget-capability>
2618
You can create interactive visualizations using the \`show-widget\` code fence.
@@ -263,7 +255,7 @@ export function createWidgetMcpServer() {
263255
'Load detailed design guidelines for generating visual widgets. Call this before generating your first widget. Available modules: interactive (HTML controls), chart (Chart.js), mockup (UI mockups), art (SVG illustrations), diagram (flowcharts/timelines/hierarchies).',
264256
{ modules: z.array(z.enum(['interactive', 'chart', 'mockup', 'art', 'diagram'])) },
265257
async ({ modules }) => ({
266-
content: [{ type: 'text' as const, text: WIDGET_SYSTEM_PROMPT + '\n\n' + getGuidelines(modules) }],
258+
content: [{ type: 'text' as const, text: getGuidelines(modules) }],
267259
}),
268260
),
269261
],

0 commit comments

Comments
 (0)