@webmcp-auto-ui/agent 2.5.20 → 2.5.22
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 +1 -1
- package/src/autoui-server.ts +12 -1
- package/src/diagnostics.ts +21 -12
- package/src/discovery-cache.ts +11 -3
- package/src/index.ts +2 -2
- package/src/loop.ts +15 -8
- package/src/providers/remote.ts +2 -2
- package/src/providers/wasm.ts +5 -27
- package/src/recipe-browser.ts +9 -4
- package/src/tool-layers.ts +2 -1
- package/src/types.ts +1 -4
- package/tests/loop.test.ts +11 -11
package/package.json
CHANGED
package/src/autoui-server.ts
CHANGED
|
@@ -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
|
// ---------------------------------------------------------------------------
|
package/src/diagnostics.ts
CHANGED
|
@@ -22,11 +22,15 @@ 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; providerKind?: 'remote' | 'wasm' | 'gemma' | 'local' },
|
|
26
26
|
/** Original (pre-sanitize) tools — used for check #5 to detect patchable schemas */
|
|
27
27
|
rawTools?: ProviderTool[],
|
|
28
28
|
): Diagnostic[] {
|
|
29
29
|
const diagnostics: Diagnostic[] = [];
|
|
30
|
+
// Gemma uses a native tool-declaration format that ignores JSON-Schema features
|
|
31
|
+
// like additionalProperties, oneOf/anyOf/$ref, strict mode, etc.
|
|
32
|
+
// Skip schema-schema checks for Gemma — they produce noise without actionable value.
|
|
33
|
+
const skipSchemaChecks = schemaOptions?.providerKind === 'gemma';
|
|
30
34
|
|
|
31
35
|
// 1. Tool name hygiene — check for residual "mcp"/"server" noise in prefixes
|
|
32
36
|
for (const layer of layers) {
|
|
@@ -63,7 +67,9 @@ export function runDiagnostics(
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
// 3. Schema depth warning for Gemma when flatten is OFF
|
|
66
|
-
|
|
70
|
+
// Skipped entirely for Gemma provider — its native format ignores schema structure beyond one level,
|
|
71
|
+
// so the "flatten" toggle has no effect there.
|
|
72
|
+
if (!skipSchemaChecks && schemaOptions && !schemaOptions.flatten) {
|
|
67
73
|
for (const tool of tools) {
|
|
68
74
|
const schema = tool.input_schema as Record<string, unknown>;
|
|
69
75
|
if (hasNestedObjects(schema)) {
|
|
@@ -105,16 +111,19 @@ export function runDiagnostics(
|
|
|
105
111
|
|
|
106
112
|
// 5. Strict mode — schemas that were auto-patched
|
|
107
113
|
// Must run on raw (pre-sanitize) schemas; sanitized tools will never show patches.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
// Skipped for Gemma — it doesn't use additionalProperties/strict mode at all.
|
|
115
|
+
if (!skipSchemaChecks) {
|
|
116
|
+
const checkTools = rawTools ?? tools;
|
|
117
|
+
for (const tool of checkTools) {
|
|
118
|
+
const { patches } = sanitizeSchemaWithReport(tool.input_schema as JsonSchema);
|
|
119
|
+
if (patches.length > 0) {
|
|
120
|
+
diagnostics.push({
|
|
121
|
+
severity: 'warning',
|
|
122
|
+
title: `Schema patched: ${tool.name}`,
|
|
123
|
+
detail: `${patches.length} correction(s) for strict mode: ${patches.map(p => p.path).join(', ')}. additionalProperties: false added automatically.`,
|
|
124
|
+
codeFix: `Add "additionalProperties": false to the MCP server schema for ${tool.name}.`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
118
127
|
}
|
|
119
128
|
}
|
|
120
129
|
|
package/src/discovery-cache.ts
CHANGED
|
@@ -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;
|
|
@@ -107,9 +112,12 @@ export class DiscoveryCache {
|
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
case 'get_recipe': {
|
|
110
|
-
const
|
|
111
|
-
const recipe = cache.recipes.find(r =>
|
|
112
|
-
|
|
115
|
+
const key = String(params.name ?? params.id ?? '').toLowerCase();
|
|
116
|
+
const recipe = cache.recipes.find(r =>
|
|
117
|
+
(r.name?.toLowerCase() === key) ||
|
|
118
|
+
((r as Record<string, unknown>).id as string | undefined)?.toLowerCase() === key
|
|
119
|
+
);
|
|
120
|
+
if (!recipe) return JSON.stringify({ error: `Recipe "${key}" not found` });
|
|
113
121
|
return JSON.stringify(recipe);
|
|
114
122
|
}
|
|
115
123
|
|
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,
|
|
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,
|
|
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
|
|
540
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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:
|
|
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}` });
|
package/src/providers/remote.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LLMProvider, LLMResponse, ChatMessage,
|
|
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:
|
|
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;
|
package/src/providers/wasm.ts
CHANGED
|
@@ -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,
|
|
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:
|
|
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:
|
|
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
|
|
@@ -260,8 +260,6 @@ export class WasmProvider implements LLMProvider {
|
|
|
260
260
|
let lastToken = '';
|
|
261
261
|
let repeatCount = 0;
|
|
262
262
|
const MAX_REPEATS = 20;
|
|
263
|
-
// P2 fix: track if we have a complete tool call to enable early cancellation
|
|
264
|
-
let hasCompleteToolCall = false;
|
|
265
263
|
const TOOL_CALL_MAX_CHARS = 3000;
|
|
266
264
|
|
|
267
265
|
const result = await this.inference.generateResponse(prompt, (partialResult: string, _done: boolean) => {
|
|
@@ -297,18 +295,6 @@ export class WasmProvider implements LLMProvider {
|
|
|
297
295
|
}
|
|
298
296
|
}
|
|
299
297
|
|
|
300
|
-
// Cancel immediately after complete tool call — don't let Gemma hallucinate
|
|
301
|
-
if (!hasCompleteToolCall && fullText.includes('<tool_call|>')) {
|
|
302
|
-
hasCompleteToolCall = true;
|
|
303
|
-
// Check if there's a new tool_call opening after the last closing
|
|
304
|
-
const lastEnd = fullText.lastIndexOf('<tool_call|>');
|
|
305
|
-
const afterEnd = fullText.slice(lastEnd + '<tool_call|>'.length);
|
|
306
|
-
if (!afterEnd.includes('<|tool_call>')) {
|
|
307
|
-
// No new tool call — cancel immediately
|
|
308
|
-
this.inference?.cancelProcessing();
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
298
|
// Safety: if text grows way too long, force cancel
|
|
313
299
|
if (fullText.length > TOOL_CALL_MAX_CHARS * 2) {
|
|
314
300
|
this.inference?.cancelProcessing();
|
|
@@ -601,7 +587,7 @@ export class WasmProvider implements LLMProvider {
|
|
|
601
587
|
/**
|
|
602
588
|
* Format a tool declaration in Gemma 4 native syntax.
|
|
603
589
|
*/
|
|
604
|
-
private static formatToolDeclaration(tool:
|
|
590
|
+
private static formatToolDeclaration(tool: ProviderTool): string {
|
|
605
591
|
const q = '<|"|>';
|
|
606
592
|
let decl = `<|tool>declaration:${tool.name}{\n`;
|
|
607
593
|
decl += ` description:${q}${tool.description}${q}`;
|
|
@@ -671,7 +657,7 @@ export class WasmProvider implements LLMProvider {
|
|
|
671
657
|
return `<|tool_call>call:${name}{${entries.join(',')}}<tool_call|>`;
|
|
672
658
|
}
|
|
673
659
|
|
|
674
|
-
private buildPrompt(messages: ChatMessage[], tools:
|
|
660
|
+
private buildPrompt(messages: ChatMessage[], tools: ProviderTool[], systemPrompt?: string, maxTools?: number): string {
|
|
675
661
|
const systemParts: string[] = [];
|
|
676
662
|
|
|
677
663
|
// Inject system prompt from settings if provided
|
|
@@ -691,16 +677,8 @@ export class WasmProvider implements LLMProvider {
|
|
|
691
677
|
]
|
|
692
678
|
: tools;
|
|
693
679
|
|
|
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
680
|
// Native Gemma 4 tool declarations
|
|
700
681
|
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
682
|
}
|
|
705
683
|
|
|
706
684
|
// Build a map of tool_use_id → tool_name from all messages for tool_result resolution
|
package/src/recipe-browser.ts
CHANGED
|
@@ -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
|
/**
|
package/src/tool-layers.ts
CHANGED
|
@@ -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; //
|
|
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;
|
package/tests/loop.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { buildSystemPrompt,
|
|
3
|
-
import type { McpToolDef, LLMProvider, LLMResponse,
|
|
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('
|
|
19
|
+
describe('toProviderTools', () => {
|
|
20
20
|
it('converts tool definitions correctly', () => {
|
|
21
|
-
const
|
|
22
|
-
expect(
|
|
23
|
-
expect(
|
|
24
|
-
expect(
|
|
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
|
|
34
|
-
expect((
|
|
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
|
|
40
|
-
expect(
|
|
39
|
+
const converted = toProviderTools(tools);
|
|
40
|
+
expect(converted[0].input_schema).toBeDefined();
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
43
|
|