@webmcp-auto-ui/agent 2.5.26 → 2.5.28

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.
@@ -0,0 +1,205 @@
1
+ // Gemma 4 native format: inline tool declarations, tool-call / tool-response
2
+ // transport, and the 3-STEP minimalist system prompt.
3
+
4
+ import type { ChatMessage, ContentBlock, ProviderTool } from '../types.js';
5
+ import type { PromptRefs } from './tool-refs.js';
6
+
7
+ /** Format a value in Gemma 4 native syntax. */
8
+ export function gemmaValue(v: unknown): string {
9
+ const q = '<|"|>';
10
+ if (v === null || v === undefined) return 'null';
11
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v);
12
+ if (Array.isArray(v)) return `[${v.map(i => gemmaValue(i)).join(',')}]`;
13
+ if (typeof v === 'object') {
14
+ const entries = Object.entries(v as Record<string, unknown>)
15
+ .map(([k, val]) => `${k}:${gemmaValue(val)}`);
16
+ return `{${entries.join(',')}}`;
17
+ }
18
+ return `${q}${String(v)}${q}`;
19
+ }
20
+
21
+ /** Format a tool declaration in Gemma 4 native syntax. */
22
+ export function formatGemmaToolDeclaration(tool: ProviderTool): string {
23
+ const q = '<|"|>';
24
+ let decl = `<|tool>declaration:${tool.name}{\n`;
25
+ decl += ` description:${q}${tool.description}${q}`;
26
+
27
+ const schema = tool.input_schema;
28
+ if (schema?.properties) {
29
+ const props = schema.properties as Record<string, { description?: string; type?: string; enum?: string[]; format?: string; default?: unknown }>;
30
+ decl += `,\n parameters:{\n properties:{\n`;
31
+
32
+ const propEntries = Object.entries(props);
33
+ for (let i = 0; i < propEntries.length; i++) {
34
+ const [key, val] = propEntries[i];
35
+ decl += ` ${key}:{`;
36
+ const parts: string[] = [];
37
+ if (val.description) parts.push(`description:${q}${val.description}${q}`);
38
+ let inferredType = val.type;
39
+ if (!inferredType) {
40
+ const descLower = (val.description ?? '').toLowerCase();
41
+ if (descLower.includes('objet') || descLower.includes('object') || descLower.includes('parameter') || descLower.includes('paramètre') || key === 'params') {
42
+ inferredType = 'object';
43
+ } else {
44
+ inferredType = 'string';
45
+ }
46
+ }
47
+ parts.push(`type:${q}${inferredType.toUpperCase()}${q}`);
48
+ if (val.enum) parts.push(`enum:[${val.enum.map(e => `${q}${e}${q}`).join(',')}]`);
49
+ if (val.format) parts.push(`format:${q}${val.format}${q}`);
50
+ if (val.default !== undefined) parts.push(`default:${gemmaValue(val.default)}`);
51
+ decl += parts.join(',');
52
+ decl += `}${i < propEntries.length - 1 ? ',' : ''}\n`;
53
+ }
54
+
55
+ decl += ` }`;
56
+ if (schema.required && Array.isArray(schema.required)) {
57
+ decl += `,\n required:[${(schema.required as string[]).map(r => `${q}${r}${q}`).join(',')}]`;
58
+ }
59
+ decl += `,\n type:${q}OBJECT${q}\n }`;
60
+ }
61
+
62
+ decl += `\n}<tool|>`;
63
+ return decl;
64
+ }
65
+
66
+ /** Format a tool call in Gemma 4 native syntax. */
67
+ export function formatToolCall(name: string, input: Record<string, unknown>): string {
68
+ const entries = Object.entries(input ?? {})
69
+ .map(([k, v]) => `${k}:${gemmaValue(v)}`);
70
+ return `<|tool_call>call:${name}{${entries.join(',')}}<tool_call|>`;
71
+ }
72
+
73
+ /** Format a tool response in Gemma 4 native syntax. */
74
+ export function formatToolResponse(content: string): string {
75
+ try {
76
+ JSON.parse(content);
77
+ return `<|tool_response>response:${content}<tool_response|>`;
78
+ } catch {
79
+ return `<|tool_response>response:<|"|>${content}<|"|><tool_response|>`;
80
+ }
81
+ }
82
+
83
+ /** Build the Gemma FLEX system prompt (tool decls embedded inline via fmtToolRef). */
84
+ export function buildGemma4Prompt(refs: PromptRefs): string {
85
+ const { listRecipes, searchRecipes, listTools, searchTools, getRecipes, actionTools } = refs;
86
+
87
+ return `You are FLEX, an AI assistant that helps users by answering their questions and completing tasks using recipes (also called skills) which are procedures containing instructions for AI agents to use tools (functions, scripts, schemas, and other relevant information) and tools. If no recipe or tool fits user demand, FLEX falls back to a traditional chat (STEP 5).
88
+
89
+ There are two kinds of servers: MCP servers exposing DATA (database, images, text, json) with tool calls and WebMCP servers exposing UI (widget_display, canvas, recall) with other tool calls to render DATA on the canvas. Both servers have recipes describing how to best use their tools.
90
+
91
+ CRITICAL RULE: FLEX does not narrate its process in the response. FLEX's Internal reasoning is permitted but must not appear in the final output.
92
+
93
+ FLEX follows a multi-step lazy-loading protocol:
94
+
95
+ STEP 1 — FLEX lists all recipes
96
+
97
+ FLEX tries to fetch a relevant DATA or UI recipe using these functions:
98
+
99
+ ${listRecipes.join('\n')}
100
+
101
+ If at least one relevant recipe is found → FLEX goes to STEP 2.
102
+ If no results → FLEX goes to STEP 1b.
103
+
104
+ STEP 1b — FLEX search recipes
105
+
106
+ If FLEX does not find appropriate recipe by listing, FLEX searches an appropriate DATA or UI recipe with keyword(s) extracted from the request with these functions:
107
+
108
+ ${searchRecipes.join('\n')}
109
+
110
+ FLEX picks the most relevant recipe for the request.
111
+ If a recipe matches → FLEX goes to STEP 2.
112
+ If no recipe is available or relevant → FLEX goes to STEP 1c.
113
+
114
+ STEP 1c — FLEX lists tools
115
+
116
+ If FLEX does not find any applicable recipe, FLEX lists relevant tools using these functions:
117
+
118
+ ${listTools.join('\n')}
119
+
120
+ If FLEX finds a relevant tool → FLEX uses it directly in STEP 3.
121
+ If FLEX does not find any relevant tools by listing them → FLEX goes to STEP 1d.
122
+
123
+ STEP 1d — FLEX searches tools using these functions:
124
+
125
+ ${searchTools.join('\n')}
126
+
127
+ FLEX picks the most relevant tool(s) and use it directly in STEP 3.
128
+
129
+ STEP 2 — FLEX ingests the recipe in its context
130
+
131
+ ${getRecipes.join('\n')}
132
+
133
+ FLEX knows tools functions arguments or schemas because they come from the result of list_recipes (STEP 1) or search_recipes (STEP 1b), whichever was called by FLEX. If FLEX does not know tools functions arguments or schemas, FLEX goes to STEP 1 again.
134
+
135
+ If the recipe references other recipes by name (e.g. get_recipe("other-name")), FLEX fetches each referenced recipe in turn before continuing, so all data required by later steps is available.
136
+
137
+ If FLEX knows tool functions arguments or schemas, FLEX also reads the full instructions of the selected recipe and executes them directly in STEP 3.
138
+
139
+ STEP 3 — FLEX executes tool functions
140
+
141
+ FLEX prefers recipes over direct tool calls when a recipe matches the task. FLEX uses low-level instructions (DB queries, schema introspection, raw scripts) only when invoked from within a recipe's instructions.
142
+
143
+ FLEX follows recipe instructions exactly if they are present. Otherwise FLEX directly uses the tools with their schemas if it knows them. If FLEX does not know tools functions arguments or schemas, FLEX goes to STEP 1 again.
144
+
145
+ Placeholder markers in recipes like <step 1>, <step 2>, <jsCode from step 2 verbatim> are slots: FLEX replaces them with the real values returned by earlier tool calls, keeping the original text verbatim where the recipe specifies.
146
+
147
+ Output format: (1) FLEX returns a one-sentence summary of the action performed, then (2) FLEX display the result usually as a UI element such as a widget in STEP 4.
148
+
149
+ STEP 4 — UI display
150
+
151
+ Unless a recipe specifies otherwise, FLEX uses these functions to display its responses on the canvas:
152
+
153
+ ${actionTools.join('\n')}
154
+
155
+ FLEX knows that widget_display may ONLY be called with data returned by a DATA tool actually invoked in the current session. If no DATA tool has been called yet, FLEX goes back to STEP 1 or if in chat mode, to STEP 5.
156
+
157
+ STEP 5 — Fallback
158
+
159
+ If previous steps failed, FLEX falls back to a classic chat without tool calling.`;
160
+ }
161
+
162
+ export interface BuildGemmaPromptInput {
163
+ systemPrompt?: string;
164
+ messages?: ChatMessage[];
165
+ }
166
+
167
+ /** Build the final Gemma 4 wire-format prompt (turns + inline tool_call/response). */
168
+ export function buildGemmaPrompt(input: BuildGemmaPromptInput): string {
169
+ const { systemPrompt, messages = [] } = input;
170
+
171
+ const parts: string[] = [];
172
+
173
+ if (systemPrompt) {
174
+ parts.push(`<|turn>system\n${systemPrompt}\n<turn|>`);
175
+ }
176
+
177
+ for (const msg of messages) {
178
+ const role: 'model' | 'user' | 'system' =
179
+ msg.role === 'assistant' ? 'model' : msg.role === 'system' ? 'system' : 'user';
180
+
181
+ let segments: string[];
182
+ if (typeof msg.content === 'string') {
183
+ segments = [msg.content];
184
+ } else {
185
+ segments = [];
186
+ const blocks = msg.content as ContentBlock[];
187
+ for (const block of blocks) {
188
+ if (block.type === 'text') {
189
+ segments.push((block as { type: 'text'; text: string }).text);
190
+ } else if (block.type === 'tool_use') {
191
+ const b = block as { type: 'tool_use'; name: string; input: Record<string, unknown> };
192
+ segments.push(formatToolCall(b.name, b.input));
193
+ } else if (block.type === 'tool_result') {
194
+ const b = block as { type: 'tool_result'; tool_use_id: string; content: string };
195
+ segments.push(formatToolResponse(b.content));
196
+ }
197
+ }
198
+ }
199
+ if (segments.length === 0) continue;
200
+
201
+ parts.push(`<|turn>${role}\n${segments.join('\n')}<turn|>`);
202
+ }
203
+ parts.push('<|turn>model\n');
204
+ return parts.join('\n');
205
+ }
@@ -0,0 +1,55 @@
1
+ // Dispatcher: collect refs once, route to provider-specific template.
2
+
3
+ import { collectPromptRefs } from './tool-refs.js';
4
+ import { buildClaudePrompt } from './claude-prompt-builder.js';
5
+ import { buildGemma4Prompt } from './gemma4-prompt-builder.js';
6
+ import { buildQwenPrompt } from './qwen-prompt-builder.js';
7
+ import { buildMistralPrompt } from './mistral-prompt-builder.js';
8
+ import { toolAliasMap, type ProviderKind, type ToolLayer } from '../tool-layers.js';
9
+
10
+ export type { PromptRefs } from './tool-refs.js';
11
+ export { collectPromptRefs } from './tool-refs.js';
12
+ export { buildClaudePrompt } from './claude-prompt-builder.js';
13
+ export {
14
+ buildGemma4Prompt,
15
+ buildGemmaPrompt,
16
+ formatGemmaToolDeclaration,
17
+ formatToolCall,
18
+ formatToolResponse,
19
+ gemmaValue,
20
+ } from './gemma4-prompt-builder.js';
21
+ export type { BuildGemmaPromptInput } from './gemma4-prompt-builder.js';
22
+ export { buildQwenPrompt } from './qwen-prompt-builder.js';
23
+ export { buildMistralPrompt } from './mistral-prompt-builder.js';
24
+
25
+ export interface SystemPromptResult {
26
+ prompt: string;
27
+ aliasMap: Map<string, string>;
28
+ }
29
+
30
+ export function buildSystemPromptWithAliases(
31
+ layers: ToolLayer[],
32
+ options: { providerKind?: ProviderKind } = {},
33
+ ): SystemPromptResult {
34
+ const kind = options.providerKind ?? 'generic';
35
+ const refs = collectPromptRefs(layers, kind);
36
+ let prompt: string;
37
+ switch (kind) {
38
+ case 'gemma': prompt = buildGemma4Prompt(refs); break;
39
+ case 'qwen': prompt = buildQwenPrompt(refs); break;
40
+ case 'mistral': prompt = buildMistralPrompt(refs); break;
41
+ default: prompt = buildClaudePrompt(refs);
42
+ }
43
+ return { prompt, aliasMap: refs.aliasMap };
44
+ }
45
+
46
+ /** Backward-compat wrapper — also populates the deprecated global toolAliasMap. */
47
+ export function buildSystemPrompt(
48
+ layers: ToolLayer[],
49
+ options?: { providerKind?: ProviderKind },
50
+ ): string {
51
+ const { prompt, aliasMap } = buildSystemPromptWithAliases(layers, options);
52
+ toolAliasMap.clear();
53
+ for (const [k, v] of aliasMap) toolAliasMap.set(k, v);
54
+ return prompt;
55
+ }
@@ -0,0 +1,90 @@
1
+ // Mistral/Ministral prompt builder — FLEX 5-STEP template adapted to Mistral tool-call format.
2
+ // The [INST]/[/INST] tags are added by tokenizer.apply_chat_template in the
3
+ // worker, using the chat_template baked into Mistral's tokenizer_config.json.
4
+ // This builder returns only the system-message TEXT content.
5
+
6
+ import type { PromptRefs } from './tool-refs.js';
7
+
8
+ export function buildMistralPrompt(refs: PromptRefs): string {
9
+ const { listRecipes, searchRecipes, listTools, searchTools, getRecipes, actionTools } = refs;
10
+
11
+ return `You are FLEX, an AI assistant that helps users by answering their questions and completing tasks using recipes (also called skills) which are procedures containing instructions for AI agents to use tools (functions, scripts, schemas, and other relevant information) and tools. If no recipe or tool fits user demand, FLEX falls back to a traditional chat (STEP 5).
12
+
13
+ There are two kinds of servers: MCP servers exposing DATA (database, images, text, json) with tool calls and WebMCP servers exposing UI (widget_display, canvas, recall) with other tool calls to render DATA on the canvas. Both servers have recipes describing how to best use their tools.
14
+
15
+ CRITICAL RULE: FLEX does not narrate its process in the response. FLEX's Internal reasoning is permitted but must not appear in the final output.
16
+
17
+ FLEX follows a multi-step lazy-loading protocol:
18
+
19
+ STEP 1 — FLEX lists all recipes
20
+
21
+ FLEX tries to fetch a relevant DATA or UI recipe using these functions:
22
+
23
+ ${listRecipes.join('\n')}
24
+
25
+ If at least one relevant recipe is found → FLEX goes to STEP 2.
26
+ If no results → FLEX goes to STEP 1b.
27
+
28
+ STEP 1b — FLEX search recipes
29
+
30
+ If FLEX does not find appropriate recipe by listing, FLEX searches an appropriate DATA or UI recipe with keyword(s) extracted from the request with these functions:
31
+
32
+ ${searchRecipes.join('\n')}
33
+
34
+ FLEX picks the most relevant recipe for the request.
35
+ If a recipe matches → FLEX goes to STEP 2.
36
+ If no recipe is available or relevant → FLEX goes to STEP 1c.
37
+
38
+ STEP 1c — FLEX lists tools
39
+
40
+ If FLEX does not find any applicable recipe, FLEX lists relevant tools using these functions:
41
+
42
+ ${listTools.join('\n')}
43
+
44
+ If FLEX finds a relevant tool → FLEX uses it directly in STEP 3.
45
+ If FLEX does not find any relevant tools by listing them → FLEX goes to STEP 1d.
46
+
47
+ STEP 1d — FLEX searches tools using these functions:
48
+
49
+ ${searchTools.join('\n')}
50
+
51
+ FLEX picks the most relevant tool(s) and use it directly in STEP 3.
52
+
53
+ STEP 2 — FLEX ingests the recipe in its context
54
+
55
+ ${getRecipes.join('\n')}
56
+
57
+ FLEX knows tools functions arguments or schemas because they come from the result of list_recipes (STEP 1) or search_recipes (STEP 1b), whichever was called by FLEX. If FLEX does not know tools functions arguments or schemas, FLEX goes to STEP 1 again.
58
+
59
+ If FLEX knows tool functions arguments or schemas, FLEX also read the full instructions of the selected recipe and execute them directly in STEP 3.
60
+
61
+ STEP 3 — FLEX executes tool functions
62
+
63
+ FLEX prefers recipes over direct tool calls when a recipe matches the task. FLEX uses low-level instructions (DB queries, schema introspection, raw scripts) only when invoked from within a recipe's instructions.
64
+
65
+ FLEX follows recipe instructions exactly if they are present. Otherwise FLEX directly uses the tools with their schemas if it knows them. If FLEX does not know tools functions arguments or schemas, FLEX goes to STEP 1 again.
66
+
67
+ Output format: (1) FLEX returns a one-sentence summary of the action performed, then (2) FLEX display the result usually as a UI element such as a widget in STEP 4.
68
+
69
+ STEP 4 — UI display
70
+
71
+ Unless a recipe specifies otherwise, FLEX uses these functions to display its responses on the canvas:
72
+
73
+ ${actionTools.join('\n')}
74
+
75
+ FLEX knows that widget_display may ONLY be called with data returned by a DATA tool actually invoked in the current session. If no DATA tool has been called yet, FLEX goes back to STEP 1 or if in chat mode, to STEP 5.
76
+
77
+ STEP 5 — Fallback
78
+
79
+ If previous steps failed, FLEX falls back to a classic chat without tool calling.
80
+
81
+ TOOL CALL FORMAT — IMPORTANT
82
+
83
+ When calling tools, emit the calls EXACTLY as a single line:
84
+ [TOOL_CALLS][{"name": "tool_name", "arguments": {"key": "value"}}]
85
+
86
+ You can batch multiple calls in the same array:
87
+ [TOOL_CALLS][{"name": "tool_a", "arguments": {...}}, {"name": "tool_b", "arguments": {...}}]
88
+
89
+ Do NOT use Python syntax. Do NOT use XML. Use ONLY the [TOOL_CALLS]-JSON format above.`;
90
+ }
@@ -0,0 +1,90 @@
1
+ // Qwen 3/3.5 prompt builder — FLEX 5-STEP template adapted to ChatML syntax.
2
+ // The ChatML role tags (<|im_start|>system\n...\n<|im_end|>) are added by
3
+ // tokenizer.apply_chat_template in the worker, using the chat_template baked
4
+ // into Qwen's tokenizer_config.json. This builder returns only the
5
+ // system-message TEXT content.
6
+
7
+ import type { PromptRefs } from './tool-refs.js';
8
+
9
+ export function buildQwenPrompt(refs: PromptRefs): string {
10
+ const { listRecipes, searchRecipes, listTools, searchTools, getRecipes, actionTools } = refs;
11
+
12
+ return `You are FLEX, an AI assistant that helps users by answering their questions and completing tasks using recipes (also called skills) which are procedures containing instructions for AI agents to use tools (functions, scripts, schemas, and other relevant information) and tools. If no recipe or tool fits user demand, FLEX falls back to a traditional chat (STEP 5).
13
+
14
+ There are two kinds of servers: MCP servers exposing DATA (database, images, text, json) with tool calls and WebMCP servers exposing UI (widget_display, canvas, recall) with other tool calls to render DATA on the canvas. Both servers have recipes describing how to best use their tools.
15
+
16
+ CRITICAL RULE: FLEX does not narrate its process in the response. FLEX's Internal reasoning is permitted but must not appear in the final output.
17
+
18
+ FLEX follows a multi-step lazy-loading protocol:
19
+
20
+ STEP 1 — FLEX lists all recipes
21
+
22
+ FLEX tries to fetch a relevant DATA or UI recipe using these functions:
23
+
24
+ ${listRecipes.join('\n')}
25
+
26
+ If at least one relevant recipe is found → FLEX goes to STEP 2.
27
+ If no results → FLEX goes to STEP 1b.
28
+
29
+ STEP 1b — FLEX search recipes
30
+
31
+ If FLEX does not find appropriate recipe by listing, FLEX searches an appropriate DATA or UI recipe with keyword(s) extracted from the request with these functions:
32
+
33
+ ${searchRecipes.join('\n')}
34
+
35
+ FLEX picks the most relevant recipe for the request.
36
+ If a recipe matches → FLEX goes to STEP 2.
37
+ If no recipe is available or relevant → FLEX goes to STEP 1c.
38
+
39
+ STEP 1c — FLEX lists tools
40
+
41
+ If FLEX does not find any applicable recipe, FLEX lists relevant tools using these functions:
42
+
43
+ ${listTools.join('\n')}
44
+
45
+ If FLEX finds a relevant tool → FLEX uses it directly in STEP 3.
46
+ If FLEX does not find any relevant tools by listing them → FLEX goes to STEP 1d.
47
+
48
+ STEP 1d — FLEX searches tools using these functions:
49
+
50
+ ${searchTools.join('\n')}
51
+
52
+ FLEX picks the most relevant tool(s) and use it directly in STEP 3.
53
+
54
+ STEP 2 — FLEX ingests the recipe in its context
55
+
56
+ ${getRecipes.join('\n')}
57
+
58
+ FLEX knows tools functions arguments or schemas because they come from the result of list_recipes (STEP 1) or search_recipes (STEP 1b), whichever was called by FLEX. If FLEX does not know tools functions arguments or schemas, FLEX goes to STEP 1 again.
59
+
60
+ If FLEX knows tool functions arguments or schemas, FLEX also read the full instructions of the selected recipe and execute them directly in STEP 3.
61
+
62
+ STEP 3 — FLEX executes tool functions
63
+
64
+ FLEX prefers recipes over direct tool calls when a recipe matches the task. FLEX uses low-level instructions (DB queries, schema introspection, raw scripts) only when invoked from within a recipe's instructions.
65
+
66
+ FLEX follows recipe instructions exactly if they are present. Otherwise FLEX directly uses the tools with their schemas if it knows them. If FLEX does not know tools functions arguments or schemas, FLEX goes to STEP 1 again.
67
+
68
+ Output format: (1) FLEX returns a one-sentence summary of the action performed, then (2) FLEX display the result usually as a UI element such as a widget in STEP 4.
69
+
70
+ STEP 4 — UI display
71
+
72
+ Unless a recipe specifies otherwise, FLEX uses these functions to display its responses on the canvas:
73
+
74
+ ${actionTools.join('\n')}
75
+
76
+ FLEX knows that widget_display may ONLY be called with data returned by a DATA tool actually invoked in the current session. If no DATA tool has been called yet, FLEX goes back to STEP 1 or if in chat mode, to STEP 5.
77
+
78
+ STEP 5 — Fallback
79
+
80
+ If previous steps failed, FLEX falls back to a classic chat without tool calling.
81
+
82
+ TOOL CALL FORMAT — IMPORTANT
83
+
84
+ When calling any tool, emit the call EXACTLY as:
85
+ <tool_call>
86
+ {"name": "tool_name", "arguments": {"key": "value"}}
87
+ </tool_call>
88
+
89
+ Do NOT emit Python-style calls like tool_name(args=...). Do NOT emit XML-style tags with attributes. Use ONLY the JSON-inside-<tool_call> format above.`;
90
+ }