tool-prune 0.1.0

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/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # tool-prune
2
+
3
+ Calibrated tool selection and schema pruning for AI agents. Zero dependencies.
4
+
5
+ ```bash
6
+ npm install tool-prune
7
+ ```
8
+
9
+ ## Quick start
10
+
11
+ ```js
12
+ import prune from 'tool-prune';
13
+
14
+ const tools = {
15
+ readFile: 'Read raw text from local filesystem path',
16
+ runQuery: 'Execute SQL queries against database',
17
+ webSearch: 'Search public web for documentation or articles'
18
+ };
19
+
20
+ const match = await prune('what tables exist in the db?', tools);
21
+ console.log(match.tool); // 'runQuery'
22
+ console.log(match.confidence); // 1.0
23
+ ```
24
+
25
+ `prune()` narrows schemas to Top-K candidates or identifies direct matches in ~140ms. `dispatch()` runs immediate tool execution if confidence clears the threshold. That's the whole API.
26
+
27
+ ## Schema pruning for LLMs
28
+
29
+ ```js
30
+ const router = prune(tools);
31
+ const topTools = await router.filter(userPrompt, { k: 3 });
32
+
33
+ const response = await llm.chat({
34
+ tools: topTools,
35
+ messages: [{ role: 'user', content: userPrompt }]
36
+ });
37
+ ```
38
+
39
+ Drops prompt tokens by 75-85% and eliminates context distraction without losing tools.
40
+
41
+ ## Fast-path direct dispatch
42
+
43
+ ```js
44
+ const result = await router.dispatch('read ./package.json', {
45
+ readFile: (query) => fs.readFileSync('package.json', 'utf8'),
46
+ runQuery: (query) => db.query(query),
47
+ fallback: (query, match) => callLLM(query)
48
+ });
49
+ ```
50
+
51
+ Runs deterministic handlers in under 160ms with zero token cost.
52
+
53
+ ## Demo
54
+
55
+ ```bash
56
+ npm run demo
57
+ ```
58
+
59
+ Runs live quickstart routing with your `TYPESAFE_API_KEY`.
60
+
61
+ ## License
62
+
63
+ MIT © [Hemanth.HM](https://h3manth.com)
package/index.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ export interface ToolDefinition {
2
+ name: string;
3
+ description?: string;
4
+ criteria?: string;
5
+ [key: string]: any;
6
+ }
7
+
8
+ export type ToolInput =
9
+ | Record<string, string | Partial<ToolDefinition>>
10
+ | ToolDefinition[];
11
+
12
+ export interface ToolPruneOptions {
13
+ apiKey?: string;
14
+ endpoint?: string;
15
+ model?: string;
16
+ threshold?: number;
17
+ topK?: number;
18
+ }
19
+
20
+ export interface CandidateTool {
21
+ name: string;
22
+ probability: number;
23
+ tool?: any;
24
+ }
25
+
26
+ export interface SelectionResult {
27
+ tool: string;
28
+ confidence: number;
29
+ probability: number;
30
+ topK: CandidateTool[];
31
+ requiresGeneration: number;
32
+ latency: number;
33
+ usage?: {
34
+ input_tokens: number;
35
+ output_tokens: number;
36
+ };
37
+ raw?: any;
38
+ }
39
+
40
+ export class ToolPruner {
41
+ constructor(tools: ToolInput, options?: ToolPruneOptions);
42
+ select(query: string | Record<string, any>, options?: Partial<ToolPruneOptions>): Promise<SelectionResult>;
43
+ filter(query: string | Record<string, any>, options?: { k?: number } & Partial<ToolPruneOptions>): Promise<any[]>;
44
+ dispatch<T = any>(
45
+ query: string | Record<string, any>,
46
+ handlers: Record<string, (query: any, selection: SelectionResult) => Promise<T> | T>,
47
+ options?: Partial<ToolPruneOptions>
48
+ ): Promise<T | SelectionResult>;
49
+ }
50
+
51
+ export default function toolPrune(query: string, tools: ToolInput, options?: ToolPruneOptions): Promise<SelectionResult>;
52
+ export default function toolPrune(tools: ToolInput, options?: ToolPruneOptions): ToolPruner;
53
+
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import toolPrune, { ToolPruner, normalizeTools } from './lib/router.js';
2
+
3
+ export default toolPrune;
4
+ export { ToolPruner, normalizeTools, toolPrune };
package/lib/router.js ADDED
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Normalize diverse tool definitions (Dict, Array, MCP Schema) into a clean criteria map.
3
+ */
4
+ export function normalizeTools(tools) {
5
+ const criteria = {};
6
+ const registry = new Map();
7
+
8
+ if (Array.isArray(tools)) {
9
+ for (const t of tools) {
10
+ if (!t || typeof t !== 'object') continue;
11
+ const name = t.name || t.id;
12
+ if (!name) continue;
13
+ const desc = t.criteria || t.description || t.summary || name;
14
+ criteria[name] = desc;
15
+ registry.set(name, t);
16
+ }
17
+ } else if (tools && typeof tools === 'object') {
18
+ for (const [name, val] of Object.entries(tools)) {
19
+ if (typeof val === 'string') {
20
+ criteria[name] = val;
21
+ registry.set(name, { name, description: val });
22
+ } else if (val && typeof val === 'object') {
23
+ const desc = val.criteria || val.description || val.summary || name;
24
+ criteria[name] = desc;
25
+ registry.set(name, { name, ...val });
26
+ }
27
+ }
28
+ }
29
+
30
+ return { criteria, registry };
31
+ }
32
+
33
+ export class ToolPruner {
34
+ constructor(tools, options = {}) {
35
+ const { criteria, registry } = normalizeTools(tools);
36
+ this.criteria = criteria;
37
+ this.registry = registry;
38
+ this.apiKey = options.apiKey || (typeof process !== 'undefined' ? process.env?.TYPESAFE_API_KEY : undefined);
39
+ this.endpoint = options.endpoint || 'https://api.typesafe.ai/v1/systemone';
40
+ this.model = options.model || 'jev-latest';
41
+ this.threshold = options.threshold ?? 0.85;
42
+ this.defaultTopK = options.topK ?? 3;
43
+ }
44
+
45
+ /**
46
+ * Select the most appropriate tool for a given query with calibrated confidence.
47
+ */
48
+ async select(query, options = {}) {
49
+ if (!this.apiKey) {
50
+ throw new Error('TypeSafe API key required. Set TYPESAFE_API_KEY or pass { apiKey }.');
51
+ }
52
+
53
+ const state = typeof query === 'string' ? { intent: query } : query;
54
+ const body = {
55
+ model: options.model || this.model,
56
+ state,
57
+ questions: {
58
+ tool: {
59
+ type: 'choice',
60
+ instructions: options.instructions || 'Which specific tool is required to satisfy this intent?',
61
+ criteria: this.criteria
62
+ },
63
+ requires_generation: {
64
+ type: 'noul',
65
+ instructions: 'Does fulfilling this request require open-ended creative text or arbitrary code generation rather than a deterministic tool execution?'
66
+ }
67
+ }
68
+ };
69
+
70
+ const start = performance.now();
71
+ const res = await fetch(this.endpoint, {
72
+ method: 'POST',
73
+ headers: {
74
+ Authorization: `Bearer ${this.apiKey}`,
75
+ 'Content-Type': 'application/json'
76
+ },
77
+ body: JSON.stringify(body)
78
+ });
79
+
80
+ if (!res.ok) {
81
+ const errText = await res.text().catch(() => '');
82
+ throw new Error(`tool-prune API error (${res.status}): ${errText || res.statusText}`);
83
+ }
84
+
85
+ const data = await res.json();
86
+ const latency = performance.now() - start;
87
+
88
+ const toolAnswer = data.answers?.tool;
89
+ const probs = toolAnswer?.probabilities || {};
90
+ const sorted = Object.entries(probs).sort((a, b) => b[1] - a[1]);
91
+ const topKCount = options.topK || this.defaultTopK;
92
+ const topK = sorted.slice(0, topKCount).map(([name, p]) => ({
93
+ name,
94
+ probability: p,
95
+ tool: this.registry.get(name)
96
+ }));
97
+
98
+ const selectedName = toolAnswer?.choice;
99
+ const probability = probs[selectedName] ?? 0;
100
+ const confidence = toolAnswer?.confidence ?? probability;
101
+ const requiresGeneration = data.answers?.requires_generation?.probability ?? 0;
102
+
103
+ return {
104
+ tool: selectedName,
105
+ confidence,
106
+ probability,
107
+ topK,
108
+ requiresGeneration,
109
+ latency,
110
+ usage: data.usage,
111
+ raw: data
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Filter tool collection down to top-K candidates to prune LLM prompt bloat.
117
+ */
118
+ async filter(query, options = {}) {
119
+ const res = await this.select(query, options);
120
+ const k = options.k || options.topK || this.defaultTopK;
121
+ return res.topK.slice(0, k).map(item => item.tool || item.name);
122
+ }
123
+
124
+ /**
125
+ * Route and execute matching handler function directly when confidence exceeds threshold.
126
+ */
127
+ async dispatch(query, handlers = {}, options = {}) {
128
+ const selection = await this.select(query, options);
129
+ const threshold = options.threshold ?? this.threshold;
130
+
131
+ if (selection.confidence >= threshold && handlers[selection.tool]) {
132
+ return await handlers[selection.tool](query, selection);
133
+ }
134
+
135
+ if (handlers.fallback) {
136
+ return await handlers.fallback(query, selection);
137
+ }
138
+
139
+ return selection;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Main function following Hemanth module style:
145
+ * - One-shot: await toolPrune(query, tools, options)
146
+ * - Configured: const pruner = toolPrune(tools, options)
147
+ */
148
+ export default function toolPrune(arg1, arg2, options) {
149
+ if (typeof arg1 === 'string' && (Array.isArray(arg2) || (arg2 && typeof arg2 === 'object'))) {
150
+ const pruner = new ToolPruner(arg2, options);
151
+ return pruner.select(arg1, options);
152
+ }
153
+ return new ToolPruner(arg1, arg2);
154
+ }
155
+
156
+ toolPrune.ToolPruner = ToolPruner;
157
+ toolPrune.normalizeTools = normalizeTools;
158
+
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "tool-prune",
3
+ "version": "0.1.0",
4
+ "description": "Fast, calibrated tool selection and schema pruning for AI agents using TypeSafe System One.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "types": "index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./index.d.ts",
11
+ "default": "./index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "lib/",
16
+ "index.js",
17
+ "index.d.ts",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "test": "node --test test/index.test.js",
23
+ "demo": "node examples/quickstart.js"
24
+ },
25
+ "keywords": [
26
+ "tool-prune",
27
+ "tool-selection",
28
+ "mcp",
29
+ "agent",
30
+ "typesafe",
31
+ "router",
32
+ "system-one",
33
+ "llm",
34
+ "function-calling"
35
+ ],
36
+ "author": "Hemanth HM <hemanth.hm@gmail.com> (https://h3manth.com)",
37
+ "license": "MIT",
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/hemanth/tool-prune"
44
+ }
45
+ }