@webmcp-auto-ui/agent 2.5.20 → 2.5.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/agent",
3
- "version": "2.5.20",
3
+ "version": "2.5.21",
4
4
  "description": "LLM agent loop + remote/WASM/local providers + MCP wrapper",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -2,7 +2,7 @@
2
2
  // AutoUI WebMCP Server — built-in UI widgets + canvas/recall tools
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
- import { createWebMcpServer } from '@webmcp-auto-ui/core';
5
+ import { createWebMcpServer, parseFrontmatter } from '@webmcp-auto-ui/core';
6
6
 
7
7
  // ---------------------------------------------------------------------------
8
8
  // Inline recipes (frontmatter + body)
@@ -1007,6 +1007,17 @@ for (const recipe of RECIPES) {
1007
1007
  autoui.registerWidget(recipe, undefined);
1008
1008
  }
1009
1009
 
1010
+ // Expose recipe summaries to the UI browser
1011
+ const parsedRecipes = RECIPES.map((md) => {
1012
+ const { frontmatter, body } = parseFrontmatter(md);
1013
+ return {
1014
+ name: (frontmatter.widget as string) ?? '',
1015
+ description: (frontmatter.description as string) ?? '',
1016
+ body,
1017
+ };
1018
+ }).filter((r) => r.name);
1019
+ autoui.setRecipes(parsedRecipes);
1020
+
1010
1021
  // ---------------------------------------------------------------------------
1011
1022
  // Custom tool: canvas
1012
1023
  // ---------------------------------------------------------------------------
@@ -22,7 +22,7 @@ export function runDiagnostics(
22
22
  layers: ToolLayer[],
23
23
  tools: ProviderTool[],
24
24
  systemPrompt: string,
25
- schemaOptions?: { sanitize?: boolean; flatten?: boolean },
25
+ schemaOptions?: { sanitize?: boolean; flatten?: boolean; strict?: boolean },
26
26
  /** Original (pre-sanitize) tools — used for check #5 to detect patchable schemas */
27
27
  rawTools?: ProviderTool[],
28
28
  ): Diagnostic[] {
@@ -69,6 +69,11 @@ export class DiscoveryCache {
69
69
  return this.servers.get(serverPrefix)?.recipes.length ?? 0;
70
70
  }
71
71
 
72
+ /** Get the cached recipes for a specific server prefix. */
73
+ recipesFor(serverPrefix: string): CachedRecipe[] {
74
+ return this.servers.get(serverPrefix)?.recipes ?? [];
75
+ }
76
+
72
77
  /** Tool count for a specific server */
73
78
  toolCount(serverPrefix: string): number {
74
79
  return this.servers.get(serverPrefix)?.tools.length ?? 0;
package/src/index.ts CHANGED
@@ -17,7 +17,7 @@ export { GemmaProvider } from './providers/gemma.js';
17
17
  export type { GemmaProviderOptions, GemmaStatus } from './providers/gemma.js';
18
18
 
19
19
  // Agent loop
20
- export { runAgentLoop, toProviderTools, mcpToolsToAnthropic, fromMcpTools, trimConversationHistory } from './loop.js';
20
+ export { runAgentLoop, toProviderTools, fromMcpTools, trimConversationHistory } from './loop.js';
21
21
  export { buildSystemPrompt } from './tool-layers.js';
22
22
  export type { AgentLoopOptions } from './loop.js';
23
23
 
@@ -69,7 +69,7 @@ export { ContextRAG, type ContextRAGOptions } from './nano-rag/mod.js';
69
69
  // Types
70
70
  export type {
71
71
  RemoteModelId, WasmModelId, LLMId, ModelId,
72
- ChatMessage, ContentBlock, McpToolDef, ProviderTool, AnthropicTool,
72
+ ChatMessage, ContentBlock, McpToolDef, ProviderTool,
73
73
  LLMProvider, LLMResponse, ToolCall, AgentMetrics, AgentResult, AgentCallbacks,
74
74
  Recipe, McpRecipe,
75
75
  } from './types.js';
package/src/loop.ts CHANGED
@@ -68,8 +68,6 @@ function compressOldToolResults(messages: ChatMessage[], previewSize: number, re
68
68
 
69
69
  // Re-export toProviderTools
70
70
  export { toProviderTools };
71
- /** @deprecated Use toProviderTools */
72
- export const mcpToolsToAnthropic = toProviderTools;
73
71
 
74
72
  function truncateResult(result: string, maxLen: number = MAX_RESULT_LEN): string {
75
73
  if (result.length <= maxLen) return result;
@@ -113,6 +111,8 @@ export interface AgentLoopOptions {
113
111
  discoveryCache?: DiscoveryCache;
114
112
  /** Nano-RAG context compaction — ingest tool results, query before LLM calls */
115
113
  contextRAG?: import('./nano-rag/context-rag.js').ContextRAG;
114
+ /** Size of inline residue (chars) left in tool_result after nano-RAG ingestion. 0 = stub only. Default: 200 */
115
+ ragResidueSize?: number;
116
116
  }
117
117
 
118
118
  export async function runAgentLoop(
@@ -137,6 +137,7 @@ export async function runAgentLoop(
137
137
  schemaOptions,
138
138
  discoveryCache,
139
139
  contextRAG,
140
+ ragResidueSize = 200,
140
141
  } = options;
141
142
 
142
143
  // Buffer for recall — stores full tool results keyed by tool_use_id
@@ -536,25 +537,31 @@ export async function runAgentLoop(
536
537
  // Store full result in buffer for later recall
537
538
  resultBuffer.set(block.id, result);
538
539
 
539
- // Nano-RAG: ingest tool result for future context retrieval
540
- if (contextRAG && result) {
540
+ // Nano-RAG: ingest tool result and replace with compact stub
541
+ let compactedResult = result;
542
+ if (contextRAG && result && !isDiscoveryTool(block.name)) {
541
543
  const realName = toolMatch ? toolMatch[3] : block.name;
542
544
  const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
543
- // Find the last user message for contextual embeddings
544
545
  const lastUserText = [...messages].reverse()
545
546
  .find(m => m.role === 'user')?.content;
546
547
  const userQuery = typeof lastUserText === 'string'
547
548
  ? lastUserText
548
549
  : (lastUserText as any[])?.find((b: any) => b.type === 'text')?.text ?? '';
549
- contextRAG.ingest(realName, block.id, resultStr, userQuery).then((chunkCount) => {
550
+ try {
551
+ const chunkCount = await contextRAG.ingest(realName, block.id, resultStr, userQuery);
550
552
  if (chunkCount > 0) {
551
553
  callbacks.onTrace?.(`[nano-rag] ingested ${chunkCount} chunks from ${realName} (${resultStr.length} chars)`);
554
+ // Replace with compact stub — full data retrievable via RAG query
555
+ const residue = ragResidueSize > 0 ? resultStr.slice(0, ragResidueSize) : '';
556
+ compactedResult = residue
557
+ ? `${residue}… [${resultStr.length} chars ingested into RAG]`
558
+ : `[${resultStr.length} chars ingested into RAG — query context for details]`;
552
559
  }
553
- }).catch(() => {});
560
+ } catch { /* RAG failure is non-fatal — keep full result */ }
554
561
  }
555
562
 
556
563
  call.result = result;
557
- toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: result });
564
+ toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: compactedResult });
558
565
  } catch (e) {
559
566
  call.error = e instanceof Error ? e.message : String(e);
560
567
  toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: `Error: ${call.error}` });
@@ -1,4 +1,4 @@
1
- import type { LLMProvider, LLMResponse, ChatMessage, AnthropicTool, RemoteModelId, ContentBlock } from '../types.js';
1
+ import type { LLMProvider, LLMResponse, ChatMessage, ProviderTool, RemoteModelId, ContentBlock } from '../types.js';
2
2
 
3
3
  export interface RemoteLLMProviderOptions {
4
4
  proxyUrl: string;
@@ -34,7 +34,7 @@ export class RemoteLLMProvider implements LLMProvider {
34
34
 
35
35
  async chat(
36
36
  messages: ChatMessage[],
37
- tools: AnthropicTool[],
37
+ tools: ProviderTool[],
38
38
  options?: { signal?: AbortSignal; cacheEnabled?: boolean; system?: string; maxTokens?: number; temperature?: number; topK?: number }
39
39
  ): Promise<LLMResponse> {
40
40
  const cache = options?.cacheEnabled ?? false;
@@ -3,7 +3,7 @@
3
3
  * No Web Worker needed — @mediapipe/tasks-genai is not compatible with ES module workers.
4
4
  * Uses dynamic import() to avoid bundling MediaPipe when only Claude is used.
5
5
  */
6
- import type { LLMProvider, LLMResponse, ChatMessage, AnthropicTool, WasmModelId, ContentBlock } from '../types.js';
6
+ import type { LLMProvider, LLMResponse, ChatMessage, ProviderTool, WasmModelId, ContentBlock } from '../types.js';
7
7
  import type { PipelineTrace } from '../pipeline-trace.js';
8
8
 
9
9
  export type WasmStatus = 'idle' | 'loading' | 'ready' | 'error';
@@ -175,7 +175,7 @@ export class WasmProvider implements LLMProvider {
175
175
 
176
176
  async chat(
177
177
  messages: ChatMessage[],
178
- tools: AnthropicTool[],
178
+ tools: ProviderTool[],
179
179
  options?: { signal?: AbortSignal; maxTokens?: number; temperature?: number; topK?: number; onToken?: (token: string) => void; system?: string; maxTools?: number }
180
180
  ): Promise<LLMResponse> {
181
181
  if (this.status !== 'ready') await this.initialize();
@@ -201,7 +201,7 @@ export class WasmProvider implements LLMProvider {
201
201
 
202
202
  private async _chat(
203
203
  messages: ChatMessage[],
204
- tools: AnthropicTool[],
204
+ tools: ProviderTool[],
205
205
  options?: { signal?: AbortSignal; maxTokens?: number; temperature?: number; topK?: number; onToken?: (token: string) => void; system?: string; maxTools?: number }
206
206
  ): Promise<LLMResponse> {
207
207
  // Apply per-request options
@@ -601,7 +601,7 @@ export class WasmProvider implements LLMProvider {
601
601
  /**
602
602
  * Format a tool declaration in Gemma 4 native syntax.
603
603
  */
604
- private static formatToolDeclaration(tool: AnthropicTool): string {
604
+ private static formatToolDeclaration(tool: ProviderTool): string {
605
605
  const q = '<|"|>';
606
606
  let decl = `<|tool>declaration:${tool.name}{\n`;
607
607
  decl += ` description:${q}${tool.description}${q}`;
@@ -671,7 +671,7 @@ export class WasmProvider implements LLMProvider {
671
671
  return `<|tool_call>call:${name}{${entries.join(',')}}<tool_call|>`;
672
672
  }
673
673
 
674
- private buildPrompt(messages: ChatMessage[], tools: AnthropicTool[], systemPrompt?: string, maxTools?: number): string {
674
+ private buildPrompt(messages: ChatMessage[], tools: ProviderTool[], systemPrompt?: string, maxTools?: number): string {
675
675
  const systemParts: string[] = [];
676
676
 
677
677
  // Inject system prompt from settings if provided
@@ -691,16 +691,8 @@ export class WasmProvider implements LLMProvider {
691
691
  ]
692
692
  : tools;
693
693
 
694
- // Minimal instruction — Gemma 4 is trained on native tool format
695
- systemParts.push(`You are a UI assistant connected to MCP servers.
696
- Use the available tools to respond. After each DATA call, render visually with component().
697
- Do NOT ask for confirmation. Execute directly.`);
698
-
699
694
  // Native Gemma 4 tool declarations
700
695
  systemParts.push(limitedTools.map(t => WasmProvider.formatToolDeclaration(t)).join('\n'));
701
-
702
- // Enable thinking mode — Gemma 4 reasons before tool calls
703
- systemParts.push('Before each tool call, think briefly in a <|channel>thought block then call the tool.');
704
696
  }
705
697
 
706
698
  // Build a map of tool_use_id → tool_name from all messages for tool_result resolution
@@ -10,11 +10,16 @@ export function filterRecipes<T extends { name: string; description?: string }>(
10
10
  ): T[] {
11
11
  const q = query.trim().toLowerCase();
12
12
  if (!q) return recipes;
13
- return recipes.filter(
14
- (r) =>
13
+ return recipes.filter((r) => {
14
+ const srv = ((r as Record<string, unknown>).server as string | undefined)
15
+ ?? ((r as Record<string, unknown>).serverName as string | undefined)
16
+ ?? '';
17
+ return (
15
18
  r.name.toLowerCase().includes(q) ||
16
- (r.description && r.description.toLowerCase().includes(q)),
17
- );
19
+ (r.description && r.description.toLowerCase().includes(q)) ||
20
+ srv.toLowerCase().includes(q)
21
+ );
22
+ });
18
23
  }
19
24
 
20
25
  /**
@@ -2,7 +2,7 @@
2
2
 
3
3
  import type { McpToolDef, ProviderTool } from './types.js';
4
4
  import type { McpRecipe } from './recipes/types.js';
5
- import type { WebMcpToolDef } from '@webmcp-auto-ui/core';
5
+ import type { WebMcpToolDef, McpRecipeSummary } from '@webmcp-auto-ui/core';
6
6
  import { sanitizeSchema, sanitizeSchemaWithReport, flattenSchema } from '@webmcp-auto-ui/core';
7
7
  import type { SchemaPatch } from '@webmcp-auto-ui/core';
8
8
  import { DiscoveryCache, type ServerCache } from './discovery-cache.js';
@@ -39,6 +39,7 @@ export interface WebMcpLayer {
39
39
  serverName: string;
40
40
  description: string;
41
41
  tools: WebMcpToolDef[];
42
+ recipes?: McpRecipeSummary[];
42
43
  }
43
44
 
44
45
  export type ToolLayer = McpLayer | WebMcpLayer;
package/src/types.ts CHANGED
@@ -35,12 +35,9 @@ export interface ProviderTool {
35
35
  name: string;
36
36
  description: string;
37
37
  input_schema: Record<string, unknown>;
38
- strict?: boolean; // Anthropic strict tool use — grammar-constrained sampling
38
+ strict?: boolean; // Strict tool use — grammar-constrained sampling (provider-dependent)
39
39
  }
40
40
 
41
- /** @deprecated Use ProviderTool */
42
- export type AnthropicTool = ProviderTool;
43
-
44
41
  export interface LLMResponse {
45
42
  content: ContentBlock[];
46
43
  stopReason: string;
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
- import { buildSystemPrompt, mcpToolsToAnthropic, fromMcpTools } from '../src/loop.js';
3
- import type { McpToolDef, LLMProvider, LLMResponse, AnthropicTool, ChatMessage } from '../src/types.js';
2
+ import { buildSystemPrompt, toProviderTools, fromMcpTools } from '../src/loop.js';
3
+ import type { McpToolDef, LLMProvider, LLMResponse, ProviderTool, ChatMessage } from '../src/types.js';
4
4
 
5
5
  const TOOLS: McpToolDef[] = [
6
6
  { name: 'search', description: 'Search for things', inputSchema: { type: 'object', properties: { q: { type: 'string' } } } },
@@ -16,12 +16,12 @@ describe('buildSystemPrompt', () => {
16
16
  });
17
17
  });
18
18
 
19
- describe('mcpToolsToAnthropic', () => {
19
+ describe('toProviderTools', () => {
20
20
  it('converts tool definitions correctly', () => {
21
- const anthropic = mcpToolsToAnthropic(TOOLS);
22
- expect(anthropic[0].name).toBe('search');
23
- expect(anthropic[0].description).toBe('Search for things');
24
- expect(anthropic[0].input_schema).toBeDefined();
21
+ const result = toProviderTools(TOOLS);
22
+ expect(result[0].name).toBe('search');
23
+ expect(result[0].description).toBe('Search for things');
24
+ expect(result[0].input_schema).toBeDefined();
25
25
  });
26
26
 
27
27
  it('strips oneOf/anyOf/allOf via sanitizeSchema', () => {
@@ -30,14 +30,14 @@ describe('mcpToolsToAnthropic', () => {
30
30
  description: 'x',
31
31
  inputSchema: { type: 'object', oneOf: [{ type: 'string' }], properties: {} } as Record<string,unknown>,
32
32
  }];
33
- const result = mcpToolsToAnthropic(tools);
34
- expect((result[0].input_schema as Record<string, unknown>)['oneOf']).toBeUndefined();
33
+ const converted = toProviderTools(tools);
34
+ expect((converted[0].input_schema as Record<string, unknown>)['oneOf']).toBeUndefined();
35
35
  });
36
36
 
37
37
  it('falls back to empty schema when inputSchema absent', () => {
38
38
  const tools: McpToolDef[] = [{ name: 'bare', description: 'no schema' }];
39
- const result = mcpToolsToAnthropic(tools);
40
- expect(result[0].input_schema).toBeDefined();
39
+ const converted = toProviderTools(tools);
40
+ expect(converted[0].input_schema).toBeDefined();
41
41
  });
42
42
  });
43
43