@bevel-software/platform-mcp-core 0.23.0 → 0.24.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/dist/chain-example.d.ts +58 -0
- package/dist/chain-example.d.ts.map +1 -0
- package/dist/chain-example.js +259 -0
- package/dist/chain-example.js.map +1 -0
- package/dist/chain-runtime.d.ts +122 -0
- package/dist/chain-runtime.d.ts.map +1 -0
- package/dist/chain-runtime.js +352 -0
- package/dist/chain-runtime.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/meta-tools.d.ts +100 -1
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +181 -53
- package/dist/meta-tools.js.map +1 -1
- package/dist/results.d.ts +20 -0
- package/dist/results.d.ts.map +1 -1
- package/dist/results.js +111 -1
- package/dist/results.js.map +1 -1
- package/dist/retired-tools.d.ts +0 -12
- package/dist/retired-tools.d.ts.map +1 -1
- package/dist/retired-tools.js +0 -22
- package/dist/retired-tools.js.map +1 -1
- package/package.json +1 -1
- package/src/chain-example.ts +315 -0
- package/src/chain-runtime.ts +382 -0
- package/src/index.ts +29 -2
- package/src/meta-tools.ts +212 -58
- package/src/results.ts +106 -1
- package/src/retired-tools.ts +0 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retired-tools.js","sourceRoot":"","sources":["../src/retired-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAqC,MAAM,CAAC,MAAM,CAAC;IACnF,oBAAoB,EAClB,8GAA8G;QAC9G,6GAA6G;QAC7G,wDAAwD;CAC3D,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC;AAEnG;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,EAAE,CAAC;QACvE,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,OAAO,EAAE,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,EAAE,CAAC;YAAE,OAAO,OAAO,CAAC;IACxG,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,EAAE,CAAC;QACvE,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,IAAI,IAAI,MAAM,CAAC,eAAe,OAAO,mCAAmC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;IAC1G,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC
|
|
1
|
+
{"version":3,"file":"retired-tools.js","sourceRoot":"","sources":["../src/retired-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAqC,MAAM,CAAC,MAAM,CAAC;IACnF,oBAAoB,EAClB,8GAA8G;QAC9G,6GAA6G;QAC7G,wDAAwD;CAC3D,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC;AAEnG;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,EAAE,CAAC;QACvE,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,OAAO,EAAE,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,EAAE,CAAC;YAAE,OAAO,OAAO,CAAC;IACxG,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,EAAE,CAAC;QACvE,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,IAAI,IAAI,MAAM,CAAC,eAAe,OAAO,mCAAmC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;IAC1G,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bevel-software/platform-mcp-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "The transport-agnostic half of Bevel's MCP surface: UTCP manual registration, tool discovery, MCP listing/dispatch and the code-mode meta-tools. Shared by the hosted proxy and the local stdio server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { sanitizeIdentifier, utcpNameToTsInterfaceName } from './code-mode-names.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The example call in `call_tool_chain`'s description, derived from the live
|
|
5
|
+
* catalog — the NAME and the ARGUMENTS both.
|
|
6
|
+
*
|
|
7
|
+
* The name alone was not enough. A first pass read the callable name off the
|
|
8
|
+
* catalog (which the two surfaces spell differently: `KNOWLEDGE_BASE.read_file`
|
|
9
|
+
* on the hosted endpoint, `hexis.hexis_read_file` through the local server) but
|
|
10
|
+
* kept the arguments as fixed text, `{ body: { path: '…' } }`. Every knowledge-
|
|
11
|
+
* base tool declares `branch` REQUIRED, so an agent copying that example got
|
|
12
|
+
* `400 … \`branch\` is required` — the acceptance criterion is that a copied
|
|
13
|
+
* example WORKS, and a right name with wrong arguments fails it just as
|
|
14
|
+
* squarely as a wrong name.
|
|
15
|
+
*
|
|
16
|
+
* So the arguments come from the tool's own input schema, and only when the
|
|
17
|
+
* schema determines them. `branch` is a free-form required string with no
|
|
18
|
+
* `default` and no `enum`: nothing in the catalog says WHICH branch, and
|
|
19
|
+
* `'main'` would be a guess that breaks on any deployment whose default branch
|
|
20
|
+
* is named otherwise — as would a guessed `path`, which has to name a file that
|
|
21
|
+
* exists. A tool like that is therefore not used for the example at all.
|
|
22
|
+
* Instead the example is the simplest call the catalog fully determines, which
|
|
23
|
+
* in practice is a no-argument discovery tool (`start_session({ body: {} })`):
|
|
24
|
+
* it demonstrates the namespace, the dotted name and the `{ body: … }` wrapper
|
|
25
|
+
* — the three things an agent actually gets wrong — and it cannot be stale,
|
|
26
|
+
* because every value in it was read from the schema rather than invented.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** One tool of the surface's catalog, as both surfaces already hold it. */
|
|
30
|
+
export interface ChainExampleTool {
|
|
31
|
+
/** The UTCP name, e.g. `KNOWLEDGE_BASE.read_file`. */
|
|
32
|
+
utcpName: string;
|
|
33
|
+
/** The tool's UTCP input schema (`ProxiedTool.inputSchema`). */
|
|
34
|
+
inputSchema?: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ChainExample {
|
|
38
|
+
/** The namespace, as the chain runtime spells it. */
|
|
39
|
+
namespace: string;
|
|
40
|
+
/**
|
|
41
|
+
* A callable NAME from the catalog — safe to print on its own, no arguments
|
|
42
|
+
* implied. Null when the catalog affords none (it is empty, or every name in
|
|
43
|
+
* it is shared by two tools): a name nothing here serves is the very thing
|
|
44
|
+
* an agent copies into a chain and watches die of `ReferenceError`.
|
|
45
|
+
*/
|
|
46
|
+
name: string | null;
|
|
47
|
+
/**
|
|
48
|
+
* A complete call that works as written, or null when the catalog affords
|
|
49
|
+
* none. Null prints no example at all rather than a call that would fail:
|
|
50
|
+
* an example an agent cannot trust is worse than the shape plus `tools_info`.
|
|
51
|
+
*/
|
|
52
|
+
call: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** How deep into a schema to look before giving up on writing a value for it. */
|
|
56
|
+
const MAX_DEPTH = 6;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The one name preferred as the example call when several are equally simple.
|
|
60
|
+
*
|
|
61
|
+
* `start_session` is the call an external agent is told to make first in any
|
|
62
|
+
* case, and the one with no preconditions — no branch, no path, no per-user
|
|
63
|
+
* credential, nothing that has to already exist. Among no-argument tools that
|
|
64
|
+
* makes it the one least able to fail for a reason the schema cannot see.
|
|
65
|
+
*/
|
|
66
|
+
const PREFERRED_EXAMPLE_TOOLS = ['start_session'];
|
|
67
|
+
|
|
68
|
+
interface SchemaLike {
|
|
69
|
+
type?: unknown;
|
|
70
|
+
properties?: unknown;
|
|
71
|
+
required?: unknown;
|
|
72
|
+
enum?: unknown;
|
|
73
|
+
default?: unknown;
|
|
74
|
+
const?: unknown;
|
|
75
|
+
minItems?: unknown;
|
|
76
|
+
minProperties?: unknown;
|
|
77
|
+
minimum?: unknown;
|
|
78
|
+
maximum?: unknown;
|
|
79
|
+
exclusiveMinimum?: unknown;
|
|
80
|
+
exclusiveMaximum?: unknown;
|
|
81
|
+
multipleOf?: unknown;
|
|
82
|
+
minLength?: unknown;
|
|
83
|
+
maxLength?: unknown;
|
|
84
|
+
pattern?: unknown;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A single-quoted JavaScript string, safe to paste into chain source AND into
|
|
89
|
+
* the Markdown code span the description prints the example inside.
|
|
90
|
+
*
|
|
91
|
+
* Every escape here is load-bearing. A raw line terminator inside a
|
|
92
|
+
* single-quoted literal is a SyntaxError, so a schema `default` or `enum` entry
|
|
93
|
+
* carrying one would print a chain that cannot even parse; a backtick would
|
|
94
|
+
* close the `` `return …;` `` span the example is printed in and truncate the
|
|
95
|
+
* call halfway. U+2028/U+2029 are the pair worth naming: line terminators to a
|
|
96
|
+
* JavaScript parser, invisible to everything else.
|
|
97
|
+
*/
|
|
98
|
+
function quote(text: string): string {
|
|
99
|
+
let out = '';
|
|
100
|
+
for (const ch of text) {
|
|
101
|
+
const cp = ch.codePointAt(0)!;
|
|
102
|
+
if (ch === '\\') out += '\\\\';
|
|
103
|
+
else if (ch === "'") out += "\\'";
|
|
104
|
+
else if (ch === '\n') out += '\\n';
|
|
105
|
+
else if (ch === '\r') out += '\\r';
|
|
106
|
+
else if (ch === '\t') out += '\\t';
|
|
107
|
+
else if (ch === '`') out += '\\x60';
|
|
108
|
+
else if (cp < 0x20 || cp === 0x7f) out += `\\x${cp.toString(16).padStart(2, '0')}`;
|
|
109
|
+
else if (cp === 0x2028 || cp === 0x2029) out += `\\u${cp.toString(16)}`;
|
|
110
|
+
else out += ch;
|
|
111
|
+
}
|
|
112
|
+
return `'${out}'`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Whether `value` is of JSON Schema `type`. An unrecognised type word matches nothing. */
|
|
116
|
+
function matchesJsonType(value: unknown, type: string): boolean {
|
|
117
|
+
switch (type) {
|
|
118
|
+
case 'string':
|
|
119
|
+
return typeof value === 'string';
|
|
120
|
+
case 'boolean':
|
|
121
|
+
return typeof value === 'boolean';
|
|
122
|
+
case 'integer':
|
|
123
|
+
return typeof value === 'number' && Number.isInteger(value);
|
|
124
|
+
case 'number':
|
|
125
|
+
return typeof value === 'number';
|
|
126
|
+
case 'null':
|
|
127
|
+
return value === null;
|
|
128
|
+
case 'array':
|
|
129
|
+
return Array.isArray(value);
|
|
130
|
+
case 'object':
|
|
131
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
132
|
+
default:
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Whether `value` satisfies the constraints declared BESIDE the `default` or
|
|
139
|
+
* `enum` it was read from.
|
|
140
|
+
*
|
|
141
|
+
* A schema is free to contradict itself — `enum: [1, 5]` with `minimum: 5`
|
|
142
|
+
* makes the first entry invalid — and the whole point of reading a value off
|
|
143
|
+
* the schema instead of inventing one is that the printed call works. So a
|
|
144
|
+
* candidate that fails any sibling bound is not used, and a keyword this does
|
|
145
|
+
* not know is not assumed to pass: an unrecognised `type` word, or a `pattern`
|
|
146
|
+
* this runtime cannot compile, rejects the candidate rather than advertising a
|
|
147
|
+
* call the server may refuse.
|
|
148
|
+
*/
|
|
149
|
+
function satisfiesConstraints(value: unknown, s: SchemaLike): boolean {
|
|
150
|
+
const declared = Array.isArray(s.type)
|
|
151
|
+
? s.type.filter((t): t is string => typeof t === 'string')
|
|
152
|
+
: typeof s.type === 'string'
|
|
153
|
+
? [s.type]
|
|
154
|
+
: [];
|
|
155
|
+
if (declared.length > 0 && !declared.some((t) => matchesJsonType(value, t))) return false;
|
|
156
|
+
if ('const' in s && s.const !== value) return false;
|
|
157
|
+
if (typeof value === 'number') {
|
|
158
|
+
if (typeof s.minimum === 'number' && value < s.minimum) return false;
|
|
159
|
+
if (typeof s.maximum === 'number' && value > s.maximum) return false;
|
|
160
|
+
if (typeof s.exclusiveMinimum === 'number' && value <= s.exclusiveMinimum) return false;
|
|
161
|
+
if (typeof s.exclusiveMaximum === 'number' && value >= s.exclusiveMaximum) return false;
|
|
162
|
+
if (typeof s.multipleOf === 'number' && s.multipleOf > 0 && !Number.isInteger(value / s.multipleOf)) return false;
|
|
163
|
+
}
|
|
164
|
+
if (typeof value === 'string') {
|
|
165
|
+
if (typeof s.minLength === 'number' && value.length < s.minLength) return false;
|
|
166
|
+
if (typeof s.maxLength === 'number' && value.length > s.maxLength) return false;
|
|
167
|
+
if (typeof s.pattern === 'string') {
|
|
168
|
+
try {
|
|
169
|
+
if (!new RegExp(s.pattern).test(value)) return false;
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** `value` as JavaScript source, or null when it is not a plain scalar. */
|
|
179
|
+
function scalarLiteral(value: unknown): string | null {
|
|
180
|
+
if (value === null) return 'null';
|
|
181
|
+
if (typeof value === 'string') return quote(value);
|
|
182
|
+
if (typeof value === 'boolean') return String(value);
|
|
183
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* An object key, bare when it is an identifier and quoted when it is not.
|
|
189
|
+
*
|
|
190
|
+
* `__proto__` is neither. In an object literal, `__proto__: v` does not create
|
|
191
|
+
* a property — it SETS THE PROTOTYPE, and quoting it (`'__proto__': v`) does
|
|
192
|
+
* exactly the same. So a copied example would send the tool an object without
|
|
193
|
+
* the argument its schema requires. Only the computed form is an ordinary own
|
|
194
|
+
* property.
|
|
195
|
+
*/
|
|
196
|
+
function propertyKey(key: string): string {
|
|
197
|
+
if (key === '__proto__') return `[${quote(key)}]`;
|
|
198
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) ? key : quote(key);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** A written value and how many scalars had to be written to get it. */
|
|
202
|
+
interface WrittenValue {
|
|
203
|
+
source: string;
|
|
204
|
+
/** Scalars in the value. Zero — `{ body: {} }` — is the simplest example. */
|
|
205
|
+
scalars: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The smallest value that satisfies `schema`, as JavaScript source, or null
|
|
210
|
+
* when the schema does not say enough to write one.
|
|
211
|
+
*
|
|
212
|
+
* Only the REQUIRED properties are written: an optional argument an agent did
|
|
213
|
+
* not ask for has no business in an example. A free-form required string,
|
|
214
|
+
* number or boolean returns null — the schema names a type, not a value, and
|
|
215
|
+
* inventing one is exactly how the example stopped working.
|
|
216
|
+
*/
|
|
217
|
+
function satisfyingValue(schema: unknown, depth: number): WrittenValue | null {
|
|
218
|
+
if (depth > MAX_DEPTH || !schema || typeof schema !== 'object' || Array.isArray(schema)) return null;
|
|
219
|
+
const s = schema as SchemaLike;
|
|
220
|
+
// A `default`, or a closed `enum`, is the schema itself naming a value —
|
|
221
|
+
// but only a value its OWN siblings accept (see `satisfiesConstraints`).
|
|
222
|
+
if ('default' in s) {
|
|
223
|
+
const literal = scalarLiteral(s.default);
|
|
224
|
+
if (literal !== null && satisfiesConstraints(s.default, s)) return { source: literal, scalars: 1 };
|
|
225
|
+
}
|
|
226
|
+
if (Array.isArray(s.enum) && s.enum.length > 0) {
|
|
227
|
+
// Every entry, not just the first: a schema may list one its own bounds
|
|
228
|
+
// forbid, and any entry that satisfies them is an equally good example.
|
|
229
|
+
for (const candidate of s.enum) {
|
|
230
|
+
const literal = scalarLiteral(candidate);
|
|
231
|
+
if (literal !== null && satisfiesConstraints(candidate, s)) return { source: literal, scalars: 1 };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const declared = Array.isArray(s.type) ? s.type.find((t) => typeof t === 'string') : s.type;
|
|
235
|
+
const type = typeof declared === 'string' ? declared : s.properties ? 'object' : undefined;
|
|
236
|
+
if (type === 'object') {
|
|
237
|
+
const properties = (s.properties ?? {}) as Record<string, unknown>;
|
|
238
|
+
const required = Array.isArray(s.required) ? s.required.filter((k): k is string => typeof k === 'string') : [];
|
|
239
|
+
const parts: string[] = [];
|
|
240
|
+
let scalars = 0;
|
|
241
|
+
for (const key of required) {
|
|
242
|
+
const child = satisfyingValue(properties[key], depth + 1);
|
|
243
|
+
// A required argument whose value the schema does not determine
|
|
244
|
+
// disqualifies the whole tool: a call missing it is a call that 400s.
|
|
245
|
+
if (!child) return null;
|
|
246
|
+
parts.push(`${propertyKey(key)}: ${child.source}`);
|
|
247
|
+
scalars += child.scalars;
|
|
248
|
+
}
|
|
249
|
+
// A schema may demand more properties than it names in `required` — an
|
|
250
|
+
// example satisfying only `required` would then be refused for being too
|
|
251
|
+
// thin, and which properties to add is not something the schema says.
|
|
252
|
+
if (typeof s.minProperties === 'number' && parts.length < s.minProperties) return null;
|
|
253
|
+
return { source: parts.length > 0 ? `{ ${parts.join(', ')} }` : '{}', scalars };
|
|
254
|
+
}
|
|
255
|
+
if (type === 'array') {
|
|
256
|
+
// An empty array satisfies an array that demands no minimum. One that does
|
|
257
|
+
// needs an element whose value the schema has not named.
|
|
258
|
+
const min = typeof s.minItems === 'number' ? s.minItems : 0;
|
|
259
|
+
return min === 0 ? { source: '[]', scalars: 0 } : null;
|
|
260
|
+
}
|
|
261
|
+
if (type === 'null') return { source: 'null', scalars: 1 };
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Where `name` sits in {@link PREFERRED_EXAMPLE_TOOLS}; past the end when absent. */
|
|
266
|
+
function preference(name: string): number {
|
|
267
|
+
const index = PREFERRED_EXAMPLE_TOOLS.findIndex((p) => name === p || name.endsWith(`.${p}`) || name.endsWith(`_${p}`));
|
|
268
|
+
return index === -1 ? PREFERRED_EXAMPLE_TOOLS.length : index;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The namespace, a callable name and a working call for this connection.
|
|
273
|
+
*
|
|
274
|
+
* This connection's OWN namespace is used when it has any tools: a third-party
|
|
275
|
+
* `.tool` is a worse example than a core tool, since what an agent most needs
|
|
276
|
+
* demonstrated is how to reach the knowledge base.
|
|
277
|
+
*/
|
|
278
|
+
export function chainExample(namespace: string, tools: readonly ChainExampleTool[]): ChainExample {
|
|
279
|
+
const ns = sanitizeIdentifier(namespace);
|
|
280
|
+
const callable = tools
|
|
281
|
+
.map((t) => ({ name: utcpNameToTsInterfaceName(t.utcpName), schema: t.inputSchema }))
|
|
282
|
+
// Sorted so that every tie below breaks the same way on every request —
|
|
283
|
+
// a description that changed between two listings of the same catalog
|
|
284
|
+
// would be its own small puzzle.
|
|
285
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
286
|
+
// A sanitized name that TWO catalog entries share is no use as an example:
|
|
287
|
+
// the runtime binds one of them and the description cannot say which, so a
|
|
288
|
+
// copied call might reach a tool whose arguments are not the schema the
|
|
289
|
+
// example was derived from. Both halves are drawn from the rest.
|
|
290
|
+
const occurrences = new Map<string, number>();
|
|
291
|
+
for (const t of callable) occurrences.set(t.name, (occurrences.get(t.name) ?? 0) + 1);
|
|
292
|
+
const unambiguous = callable.filter((t) => occurrences.get(t.name) === 1);
|
|
293
|
+
const own = unambiguous.filter((t) => t.name.startsWith(`${ns}.`));
|
|
294
|
+
const pool = own.length > 0 ? own : unambiguous;
|
|
295
|
+
// The NAME example. `read_file` is preferred because every surface has it
|
|
296
|
+
// and an agent reading the description recognises it; printed without
|
|
297
|
+
// arguments, so its required `branch` is not at stake here. From the pool or
|
|
298
|
+
// not at all: with nothing to draw on, `<namespace>.read_file` would be a
|
|
299
|
+
// name this surface invented.
|
|
300
|
+
const name = pool.find((t) => t.name.endsWith('read_file'))?.name ?? pool[0]?.name ?? null;
|
|
301
|
+
// The CALL example: the simplest call the catalog fully determines.
|
|
302
|
+
let best: { name: string; value: WrittenValue } | undefined;
|
|
303
|
+
for (const tool of pool) {
|
|
304
|
+
const value = satisfyingValue(tool.schema, 0);
|
|
305
|
+
if (!value) continue;
|
|
306
|
+
if (
|
|
307
|
+
!best ||
|
|
308
|
+
value.scalars < best.value.scalars ||
|
|
309
|
+
(value.scalars === best.value.scalars && preference(tool.name) < preference(best.name))
|
|
310
|
+
) {
|
|
311
|
+
best = { name: tool.name, value };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return { namespace: ns, name, call: best ? `${best.name}(${best.value.source})` : null };
|
|
315
|
+
}
|