@zackbart/connecta 0.9.0 → 0.9.1
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/CHANGELOG.md +49 -0
- package/README.md +9 -8
- package/dist/catalog-service.d.ts +8 -0
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +24 -2
- package/dist/catalog-service.js.map +1 -1
- package/dist/catalog.d.ts +34 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +264 -40
- package/dist/catalog.js.map +1 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +12 -6
- package/dist/execute.js.map +1 -1
- package/dist/meta-tools.js +2 -2
- package/dist/meta-tools.js.map +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +7 -7
- package/dist/skills.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/catalog-service.ts +43 -0
- package/src/catalog.ts +327 -33
- package/src/execute.ts +15 -6
- package/src/meta-tools.ts +2 -2
- package/src/skills.ts +7 -7
- package/src/version.ts +1 -1
package/src/catalog.ts
CHANGED
|
@@ -13,8 +13,19 @@ export function summarizeDescription(
|
|
|
13
13
|
return `${compact.slice(0, DEFAULT_DESCRIPTION_LENGTH - 1).trimEnd()}…`;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function lexicalTokens(text: string): string[] {
|
|
17
|
+
return text
|
|
18
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
19
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
22
|
+
.trim()
|
|
23
|
+
.split(/\s+/)
|
|
24
|
+
.filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
function normalized(text: string): string {
|
|
17
|
-
return text
|
|
28
|
+
return lexicalTokens(text).join(" ");
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
/**
|
|
@@ -75,7 +86,14 @@ export function lexicalSearchQuery(query: string): string {
|
|
|
75
86
|
interface SearchDocument {
|
|
76
87
|
tool: ToolDef;
|
|
77
88
|
name: string;
|
|
78
|
-
|
|
89
|
+
nameTokens: string[];
|
|
90
|
+
descriptionTokens: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface SearchIndex {
|
|
94
|
+
documents: SearchDocument[];
|
|
95
|
+
nameTokenDocuments: ReadonlyMap<string, readonly ToolDef[]>;
|
|
96
|
+
descriptionTokenDocuments: ReadonlyMap<string, readonly ToolDef[]>;
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
export type LexicalMatchMode = "all" | "partial";
|
|
@@ -86,19 +104,174 @@ export interface RankedTool {
|
|
|
86
104
|
order: number;
|
|
87
105
|
}
|
|
88
106
|
|
|
89
|
-
const
|
|
107
|
+
const searchIndexes = new WeakMap<ToolDef[], SearchIndex>();
|
|
108
|
+
|
|
109
|
+
function indexFor(tools: ToolDef[]): SearchIndex {
|
|
110
|
+
let index = searchIndexes.get(tools);
|
|
111
|
+
if (!index) {
|
|
112
|
+
const nameTokenDocuments = new Map<string, ToolDef[]>();
|
|
113
|
+
const descriptionTokenDocuments = new Map<string, ToolDef[]>();
|
|
114
|
+
const addTokens = (
|
|
115
|
+
target: Map<string, ToolDef[]>,
|
|
116
|
+
tokens: string[],
|
|
117
|
+
tool: ToolDef,
|
|
118
|
+
) => {
|
|
119
|
+
for (const token of new Set(tokens)) {
|
|
120
|
+
const documents = target.get(token) ?? [];
|
|
121
|
+
documents.push(tool);
|
|
122
|
+
target.set(token, documents);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const documents = tools.map((tool) => {
|
|
126
|
+
const nameTokens = lexicalTokens(tool.name);
|
|
127
|
+
const descriptionTokens = lexicalTokens(tool.description ?? "");
|
|
128
|
+
addTokens(nameTokenDocuments, nameTokens, tool);
|
|
129
|
+
addTokens(descriptionTokenDocuments, descriptionTokens, tool);
|
|
130
|
+
return {
|
|
131
|
+
tool,
|
|
132
|
+
name: nameTokens.join(" "),
|
|
133
|
+
nameTokens,
|
|
134
|
+
descriptionTokens,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
index = {
|
|
138
|
+
documents,
|
|
139
|
+
nameTokenDocuments,
|
|
140
|
+
descriptionTokenDocuments,
|
|
141
|
+
};
|
|
142
|
+
searchIndexes.set(tools, index);
|
|
143
|
+
}
|
|
144
|
+
return index;
|
|
145
|
+
}
|
|
90
146
|
|
|
91
147
|
function documentsFor(tools: ToolDef[]): SearchDocument[] {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
148
|
+
return indexFor(tools).documents;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Whole-token equality is the ordinary lexical match. A deliberately narrow
|
|
153
|
+
* inflection check retains useful singular/plural and past-tense recall
|
|
154
|
+
* without bringing back arbitrary substring matches (`list` must not match
|
|
155
|
+
* `enlist`, and `record` must not match the noun `recording`).
|
|
156
|
+
*/
|
|
157
|
+
function inflectionVariants(base: string): string[] {
|
|
158
|
+
return [
|
|
159
|
+
`${base}s`,
|
|
160
|
+
`${base}es`,
|
|
161
|
+
`${base}ed`,
|
|
162
|
+
...(base.endsWith("e") ? [`${base}d`] : []),
|
|
163
|
+
...(base.endsWith("y")
|
|
164
|
+
? [
|
|
165
|
+
`${base.slice(0, -1)}ies`,
|
|
166
|
+
`${base.slice(0, -1)}ied`,
|
|
167
|
+
]
|
|
168
|
+
: []),
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function matchingTokenCandidates(term: string): Set<string> {
|
|
173
|
+
const candidates = new Set([term, ...inflectionVariants(term)]);
|
|
174
|
+
const possibleBases = [
|
|
175
|
+
...(term.endsWith("s") ? [term.slice(0, -1)] : []),
|
|
176
|
+
...(term.endsWith("es") ? [term.slice(0, -2)] : []),
|
|
177
|
+
...(term.endsWith("ed") ? [term.slice(0, -2)] : []),
|
|
178
|
+
...(term.endsWith("d") ? [term.slice(0, -1)] : []),
|
|
179
|
+
...(term.endsWith("ies")
|
|
180
|
+
? [`${term.slice(0, -3)}y`]
|
|
181
|
+
: []),
|
|
182
|
+
...(term.endsWith("ied")
|
|
183
|
+
? [`${term.slice(0, -3)}y`]
|
|
184
|
+
: []),
|
|
185
|
+
];
|
|
186
|
+
for (const base of possibleBases) {
|
|
187
|
+
if (base && inflectionVariants(base).includes(term)) {
|
|
188
|
+
candidates.add(base);
|
|
189
|
+
}
|
|
100
190
|
}
|
|
101
|
-
return
|
|
191
|
+
return candidates;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface LexicalCorpusStatistics {
|
|
195
|
+
documentCount: number;
|
|
196
|
+
documentFrequency: ReadonlyMap<string, number>;
|
|
197
|
+
nameMatches: ReadonlyMap<string, ReadonlySet<ToolDef>>;
|
|
198
|
+
descriptionMatches: ReadonlyMap<string, ReadonlySet<ToolDef>>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Compute query-specific document frequencies across every available catalog.
|
|
203
|
+
* The caller does this once per search and shares the result with each
|
|
204
|
+
* connector rank, so ubiquitous words contribute less than discriminative
|
|
205
|
+
* ones without making any action word a stopword.
|
|
206
|
+
*/
|
|
207
|
+
export function lexicalCorpusStatistics(
|
|
208
|
+
toolSets: ToolDef[][],
|
|
209
|
+
query: string,
|
|
210
|
+
): LexicalCorpusStatistics {
|
|
211
|
+
const terms = [...new Set(lexicalTokens(query))];
|
|
212
|
+
if (terms.length === 0) {
|
|
213
|
+
return {
|
|
214
|
+
documentCount: toolSets.reduce(
|
|
215
|
+
(total, tools) => total + tools.length,
|
|
216
|
+
0,
|
|
217
|
+
),
|
|
218
|
+
documentFrequency: new Map(),
|
|
219
|
+
nameMatches: new Map(),
|
|
220
|
+
descriptionMatches: new Map(),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const nameMatches = new Map<string, Set<ToolDef>>();
|
|
224
|
+
const descriptionMatches = new Map<string, Set<ToolDef>>();
|
|
225
|
+
for (const term of terms) {
|
|
226
|
+
const termNameMatches = new Set<ToolDef>();
|
|
227
|
+
const termDescriptionMatches = new Set<ToolDef>();
|
|
228
|
+
for (const tools of toolSets) {
|
|
229
|
+
const index = indexFor(tools);
|
|
230
|
+
for (const candidate of matchingTokenCandidates(term)) {
|
|
231
|
+
for (const tool of index.nameTokenDocuments.get(candidate) ?? []) {
|
|
232
|
+
termNameMatches.add(tool);
|
|
233
|
+
}
|
|
234
|
+
for (
|
|
235
|
+
const tool of
|
|
236
|
+
index.descriptionTokenDocuments.get(candidate) ?? []
|
|
237
|
+
) {
|
|
238
|
+
termDescriptionMatches.add(tool);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
nameMatches.set(term, termNameMatches);
|
|
243
|
+
descriptionMatches.set(term, termDescriptionMatches);
|
|
244
|
+
}
|
|
245
|
+
const documentFrequency = new Map(
|
|
246
|
+
terms.map((term) => [
|
|
247
|
+
term,
|
|
248
|
+
new Set([
|
|
249
|
+
...(nameMatches.get(term) ?? []),
|
|
250
|
+
...(descriptionMatches.get(term) ?? []),
|
|
251
|
+
]).size,
|
|
252
|
+
]),
|
|
253
|
+
);
|
|
254
|
+
return {
|
|
255
|
+
documentCount: toolSets.reduce(
|
|
256
|
+
(total, tools) => total + tools.length,
|
|
257
|
+
0,
|
|
258
|
+
),
|
|
259
|
+
documentFrequency,
|
|
260
|
+
nameMatches,
|
|
261
|
+
descriptionMatches,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function inverseDocumentFrequency(
|
|
266
|
+
term: string,
|
|
267
|
+
statistics: LexicalCorpusStatistics,
|
|
268
|
+
): number {
|
|
269
|
+
const frequency = statistics.documentFrequency.get(term) ?? 0;
|
|
270
|
+
return Math.log(
|
|
271
|
+
1 +
|
|
272
|
+
(statistics.documentCount - frequency + 0.5) /
|
|
273
|
+
(frequency + 0.5),
|
|
274
|
+
);
|
|
102
275
|
}
|
|
103
276
|
|
|
104
277
|
function scoreDocument(
|
|
@@ -106,30 +279,45 @@ function scoreDocument(
|
|
|
106
279
|
phrase: string,
|
|
107
280
|
terms: string[],
|
|
108
281
|
mode: LexicalMatchMode,
|
|
282
|
+
statistics: LexicalCorpusStatistics,
|
|
109
283
|
): number | null {
|
|
110
284
|
if (!phrase) return 0;
|
|
111
|
-
const
|
|
112
|
-
|
|
285
|
+
const matchedTerms = terms.filter((term) =>
|
|
286
|
+
statistics.nameMatches.get(term)?.has(doc.tool) ||
|
|
287
|
+
statistics.descriptionMatches.get(term)?.has(doc.tool),
|
|
288
|
+
);
|
|
113
289
|
if (mode === "all" && matchedTerms.length !== terms.length) return null;
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
290
|
+
if (matchedTerms.length === 0) return null;
|
|
291
|
+
|
|
292
|
+
// Coverage remains meaningful in partial mode, but is IDF-weighted rather
|
|
293
|
+
// than a raw term count: one rare domain term can beat several ubiquitous
|
|
294
|
+
// catalog verbs.
|
|
295
|
+
let score = matchedTerms.reduce(
|
|
296
|
+
(total, term) =>
|
|
297
|
+
total + 4 * inverseDocumentFrequency(term, statistics),
|
|
298
|
+
0,
|
|
299
|
+
);
|
|
300
|
+
const phraseWeight = terms.reduce(
|
|
301
|
+
(total, term) =>
|
|
302
|
+
total + inverseDocumentFrequency(term, statistics),
|
|
303
|
+
0,
|
|
304
|
+
);
|
|
305
|
+
if (doc.name === phrase) score += 40 * phraseWeight;
|
|
306
|
+
else if (doc.name.startsWith(`${phrase} `)) score += 24 * phraseWeight;
|
|
307
|
+
else if (` ${doc.name} `.includes(` ${phrase} `)) {
|
|
308
|
+
score += 16 * phraseWeight;
|
|
122
309
|
}
|
|
123
310
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
else if (
|
|
132
|
-
|
|
311
|
+
for (const term of matchedTerms) {
|
|
312
|
+
const weight = inverseDocumentFrequency(term, statistics);
|
|
313
|
+
if (doc.nameTokens.includes(term)) score += 12 * weight;
|
|
314
|
+
else if (statistics.nameMatches.get(term)?.has(doc.tool)) {
|
|
315
|
+
score += 8 * weight;
|
|
316
|
+
}
|
|
317
|
+
if (doc.descriptionTokens.includes(term)) score += 3 * weight;
|
|
318
|
+
else if (statistics.descriptionMatches.get(term)?.has(doc.tool)) {
|
|
319
|
+
score += 1.5 * weight;
|
|
320
|
+
}
|
|
133
321
|
}
|
|
134
322
|
return score;
|
|
135
323
|
}
|
|
@@ -139,12 +327,16 @@ export function rankTools(
|
|
|
139
327
|
tools: ToolDef[],
|
|
140
328
|
query: string,
|
|
141
329
|
mode: LexicalMatchMode = "all",
|
|
330
|
+
statistics: LexicalCorpusStatistics = lexicalCorpusStatistics(
|
|
331
|
+
[tools],
|
|
332
|
+
query,
|
|
333
|
+
),
|
|
142
334
|
): RankedTool[] {
|
|
143
335
|
const phrase = normalized(query);
|
|
144
|
-
const terms = phrase.split(/\s+/).filter(Boolean);
|
|
336
|
+
const terms = [...new Set(phrase.split(/\s+/).filter(Boolean))];
|
|
145
337
|
const ranked: RankedTool[] = [];
|
|
146
338
|
documentsFor(tools).forEach((doc, order) => {
|
|
147
|
-
const score = scoreDocument(doc, phrase, terms, mode);
|
|
339
|
+
const score = scoreDocument(doc, phrase, terms, mode, statistics);
|
|
148
340
|
if (score !== null) ranked.push({ tool: doc.tool, score, order });
|
|
149
341
|
});
|
|
150
342
|
return ranked;
|
|
@@ -300,3 +492,105 @@ export function compactSchema(schema: JsonSchema): string {
|
|
|
300
492
|
compactSchemas.set(schema, rendered);
|
|
301
493
|
return rendered;
|
|
302
494
|
}
|
|
495
|
+
|
|
496
|
+
/** The property and required names a schema resolves to, or undefined. */
|
|
497
|
+
export interface SchemaObjectKeys {
|
|
498
|
+
properties: string[];
|
|
499
|
+
required: string[];
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Walk a schema the way renderSchema does — composing `allOf` and resolving
|
|
504
|
+
* `$ref` against the root's `$defs`/`definitions` — and collect the top-level
|
|
505
|
+
* property names it would render. A shallow `Object.keys(schema.properties)`
|
|
506
|
+
* disagrees with the rendered compact schema for exactly the shapes real
|
|
507
|
+
* connectors emit (a top-level `$ref` to a `$defs` entry, or the OpenAPI
|
|
508
|
+
* "extend this base" `allOf`), which is worse than no metadata at all: it
|
|
509
|
+
* reports an empty field list for a tool that plainly has fields.
|
|
510
|
+
*
|
|
511
|
+
* Returns undefined when the schema is not an object shape at all — a union,
|
|
512
|
+
* array, enum, or unresolvable `$ref`. Absent metadata tells a caller to read
|
|
513
|
+
* the rendered schema instead; an empty array would claim the tool takes no
|
|
514
|
+
* fields.
|
|
515
|
+
*/
|
|
516
|
+
export function schemaObjectKeys(
|
|
517
|
+
schema: JsonSchema | undefined,
|
|
518
|
+
): SchemaObjectKeys | undefined {
|
|
519
|
+
if (!schema) return undefined;
|
|
520
|
+
const defs = {
|
|
521
|
+
...(schema.$defs as Record<string, unknown>),
|
|
522
|
+
...(schema.definitions as Record<string, unknown>),
|
|
523
|
+
};
|
|
524
|
+
try {
|
|
525
|
+
return objectKeys(schema, defs, new Set(), 0);
|
|
526
|
+
} catch {
|
|
527
|
+
return undefined;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Merge in declaration order, first occurrence winning, as renderSchema renders. */
|
|
532
|
+
function mergedKeys(
|
|
533
|
+
parts: readonly SchemaObjectKeys[],
|
|
534
|
+
): SchemaObjectKeys | undefined {
|
|
535
|
+
if (parts.length === 0) return undefined;
|
|
536
|
+
return {
|
|
537
|
+
properties: [...new Set(parts.flatMap((part) => part.properties))],
|
|
538
|
+
required: [...new Set(parts.flatMap((part) => part.required))],
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** The key-collecting twin of renderSchema; the branch order must match it. */
|
|
543
|
+
function objectKeys(
|
|
544
|
+
schema: unknown,
|
|
545
|
+
defs: Record<string, unknown>,
|
|
546
|
+
seen: Set<string>,
|
|
547
|
+
depth: number,
|
|
548
|
+
): SchemaObjectKeys | undefined {
|
|
549
|
+
if (depth > 4) return undefined;
|
|
550
|
+
if (schema === null || typeof schema !== "object") return undefined;
|
|
551
|
+
const s = schema as Record<string, unknown>;
|
|
552
|
+
|
|
553
|
+
if (Array.isArray(s.allOf)) {
|
|
554
|
+
const { allOf: _members, ...own } = s;
|
|
555
|
+
const parts = declaresShape(own)
|
|
556
|
+
? [objectKeys(own, defs, seen, depth)]
|
|
557
|
+
: [];
|
|
558
|
+
for (const member of s.allOf) {
|
|
559
|
+
parts.push(objectKeys(member, defs, seen, depth + 1));
|
|
560
|
+
}
|
|
561
|
+
// An allOf whose members are not all object shapes renders as an
|
|
562
|
+
// intersection with a non-object half; no single key list describes it.
|
|
563
|
+
return parts.every((part) => part !== undefined)
|
|
564
|
+
? mergedKeys(parts as SchemaObjectKeys[])
|
|
565
|
+
: undefined;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (typeof s.$ref === "string") {
|
|
569
|
+
const name = refName(s.$ref);
|
|
570
|
+
if (seen.has(name)) return undefined;
|
|
571
|
+
const target = defs[name];
|
|
572
|
+
if (target === undefined) return undefined;
|
|
573
|
+
seen.add(name);
|
|
574
|
+
const resolved = objectKeys(target, defs, seen, depth);
|
|
575
|
+
seen.delete(name);
|
|
576
|
+
return resolved;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (Array.isArray(s.oneOf ?? s.anyOf)) return undefined;
|
|
580
|
+
if (Array.isArray(s.enum)) return undefined;
|
|
581
|
+
if (s.const !== undefined) return undefined;
|
|
582
|
+
if (s.type === "array" || s.items) return undefined;
|
|
583
|
+
if (s.type === "object" || s.properties) {
|
|
584
|
+
const props = s.properties;
|
|
585
|
+
if (props === null || Array.isArray(props) || typeof props !== "object") {
|
|
586
|
+
return { properties: [], required: [] };
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
properties: Object.keys(props as Record<string, unknown>),
|
|
590
|
+
required: Array.isArray(s.required)
|
|
591
|
+
? s.required.filter((key): key is string => typeof key === "string")
|
|
592
|
+
: [],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
return undefined;
|
|
596
|
+
}
|
package/src/execute.ts
CHANGED
|
@@ -279,11 +279,20 @@ export async function buildSandboxProviders(
|
|
|
279
279
|
offset?: number;
|
|
280
280
|
fullDescriptions?: boolean;
|
|
281
281
|
includeSchemas?: "compact" | "json";
|
|
282
|
+
includeSchemaKeys?: boolean;
|
|
282
283
|
};
|
|
283
|
-
const result = flatSearchResult(
|
|
284
|
+
const result = flatSearchResult(
|
|
285
|
+
await catalog.search({
|
|
286
|
+
...args,
|
|
287
|
+
// Key metadata rides along with schemas by default, since that is
|
|
288
|
+
// the whole point of it in code mode. It stays opt-out because it
|
|
289
|
+
// counts against the same hard discovery-byte ceiling.
|
|
290
|
+
includeSchemaKeys: args.includeSchemaKeys !== false,
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
284
293
|
boundedDiscoveryText(
|
|
285
294
|
result,
|
|
286
|
-
"Request a smaller limit, omit fullDescriptions,
|
|
295
|
+
"Request a smaller limit, omit fullDescriptions, use compact schemas, or pass includeSchemaKeys: false.",
|
|
287
296
|
);
|
|
288
297
|
return result;
|
|
289
298
|
},
|
|
@@ -442,18 +451,18 @@ export function createExecuteTool(
|
|
|
442
451
|
};
|
|
443
452
|
}
|
|
444
453
|
|
|
445
|
-
const EXECUTE_DESC = `Use for dependent multi-step calls, loops, joins, branching, or reducing large results in a sandbox.
|
|
454
|
+
const EXECUTE_DESC = `Use for dependent multi-step calls, loops, joins, branching, or reducing large results in a sandbox. Never use execute_code for search-only discovery or one downstream call: use search_tools, then call_tool when needed. For 2–10 independent calls use batch_call. Only tools explicitly annotated readOnlyHint: true are available. Each run is limited to ${EXECUTE_MAX_HOST_CALLS} host calls; connecta.batch accepts at most ${EXECUTE_MAX_BATCH_CALLS}; each host call has a ${EXECUTE_HOST_CALL_TIMEOUT_MS / 1_000}-second deadline.
|
|
446
455
|
|
|
447
456
|
Write an async arrow function. It runs with NO network, filesystem, timers, or imports — the only capabilities are:
|
|
448
457
|
- One global per connector: every address <connectorId>.<toolName> from search_tools is callable as <connectorId>.<toolName>(args) with a single args object matching the schema from describe_tools. Names are sanitized to JS identifiers: characters outside [A-Za-z0-9_$] become "_" (e.g. my-service.get.thing → my_service.get_thing), leading digits get "_" prefixed, reserved words get "_" appended.
|
|
449
458
|
- connecta.call(address, args) and connecta.batch(calls) — call raw addresses.
|
|
450
|
-
- connecta.search(args) and connecta.describe(args) — load and inspect request-local catalogs on demand.
|
|
459
|
+
- connecta.search(args) and connecta.describe(args) — load and inspect request-local catalogs on demand. Matches carrying schemas also list inputKeys, requiredInputKeys, and outputKeys — the same names the schema shows, ready to check against before building args. They are absent when a schema is not a plain object shape, so read the schema itself rather than assuming a missing list means no fields.
|
|
451
460
|
- console.log(...) — captured and returned alongside the result.
|
|
452
461
|
|
|
453
462
|
Tool calls return plain values (MCP text content is JSON-parsed when possible) and throw on downstream errors — use try/catch to handle them. Return a JSON-serializable value; large results are truncated, so reduce data in code instead of returning raw payloads.
|
|
454
463
|
|
|
455
|
-
|
|
456
|
-
|
|
464
|
+
Plain JavaScript only — no TypeScript syntax. For unknown-address dependent work, use one execute_code call: search inside it, read the compact schemas, and continue to the dependent calls; do not return search results for a second execute_code call. Compact schemas are TypeScript-like strings, not JSON Schema objects: write the property names they display, never a positional guess or an invented alias.
|
|
465
|
+
Dependent example (only when the second call requires a value returned by the first): async () => { const { tools } = await connecta.search({ query: "pipeline run job logs", includeSchemas: "compact" }); const pick = (suffix) => { const match = tools.find((tool) => tool.address.endsWith(suffix)); if (!match) throw new Error("no tool matching " + suffix); return match.address; }; const run = await connecta.call(pick(".get_run"), { runId: 42 }); const logs = await connecta.call(pick(".get_job_logs"), { jobId: run.failedJobId }); return [run, logs]; }`;
|
|
457
466
|
|
|
458
467
|
/** Register the execute_code meta-tool. Only called when an executor is configured. */
|
|
459
468
|
export function registerExecuteTool(
|
package/src/meta-tools.ts
CHANGED
|
@@ -1118,8 +1118,8 @@ export function createMetaTools(
|
|
|
1118
1118
|
|
|
1119
1119
|
const LIST_DESC =
|
|
1120
1120
|
"List connectors with status, cached tool count, and recent real-call health. Use probe=false for a fast inventory; use probe=true (default) only to diagnose live health or authorization.";
|
|
1121
|
-
const SEARCH_DESC = `
|
|
1122
|
-
const DESCRIBE_DESC = `
|
|
1121
|
+
const SEARCH_DESC = `Unknown address: use 2–4 distinctive action/object terms, not the full request; omit limit initially (default ${DEFAULT_SEARCH_LIMIT}) and page only if needed, up to ${MAX_SEARCH_LIMIT}. includeSchemas="compact" adds the input and any declared output shape; matches also carry declared annotations. Call directly when sufficient. Empty query browses all.`;
|
|
1122
|
+
const DESCRIBE_DESC = `Only when search_tools omitted schemas, a compact shape is ambiguous, or exact JSON constraints are needed. Inspects up to ${MAX_DESCRIBE_ADDRESSES} addresses with schemas and annotations; "compact" is default, while "json" preserves exact constraints.`;
|
|
1123
1123
|
const CALL_DESC =
|
|
1124
1124
|
'Use for one tool explicitly annotated readOnlyHint: true. For 2–10 independent read-only calls use batch_call; for dependent steps or data reduction use execute_code when available. Unannotated, write-capable, and destructive tools are refused and require call_destructive_tool. fields selects JSON dot-paths, resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
|
|
1125
1125
|
const CALL_DESTRUCTIVE_DESC =
|
package/src/skills.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { Connector } from "./types.js";
|
|
2
2
|
|
|
3
3
|
export const CONNECTA_INSTRUCTIONS =
|
|
4
|
-
'Connecta exposes
|
|
4
|
+
'Connecta exposes integrations behind meta-tools. Unknown address: use search_tools with 2–4 distinctive action/object terms, no initial limit, and includeSchemas="compact"; describe_tools only if that shape is ambiguous or exact JSON constraints are needed. Use call_tool for one explicitly read-only call, batch_call for 2–10 independent read-only calls, and execute_code (when available) only for dependencies, loops, joins, or substantial reduction — searching inside that one run rather than searching first. Use call_destructive_tool individually for unannotated, write-capable, or destructive tools. authorize_connector follows auth_required; get_result follows truncation. If this routing is unfamiliar, fetch skills({ name: "usage" }).';
|
|
5
5
|
|
|
6
6
|
export const USAGE_SKILL = `# Connecta usage
|
|
7
7
|
|
|
8
8
|
## Choose the smallest execution tool
|
|
9
9
|
|
|
10
|
-
Use exact addresses returned by discovery; never invent one.
|
|
10
|
+
Use exact addresses returned by discovery; never invent one. Search with 2–4 distinctive action/object terms rather than the full request, and omit \`limit\` initially so the default page stays small.
|
|
11
11
|
|
|
12
|
-
- Unknown address: \`search_tools({ query, includeSchemas: "compact" })
|
|
13
|
-
-
|
|
12
|
+
- Unknown address: \`search_tools({ query, includeSchemas: "compact" })\`; every match then includes its input shape plus any declared output shape and annotations.
|
|
13
|
+
- Compact shape still ambiguous: \`describe_tools({ addresses: [...] })\`; use \`format: "json"\` only for exact constraints.
|
|
14
14
|
- One explicitly read-only call: \`call_tool\`.
|
|
15
15
|
- Two to ten independent explicitly read-only calls: \`batch_call\`.
|
|
16
16
|
- Dependent read-only calls, loops, joins, branching, or large-result reduction: \`execute_code\` when available.
|
|
@@ -18,15 +18,15 @@ Use exact addresses returned by discovery; never invent one.
|
|
|
18
18
|
- Truncated result: retry with \`fields\` when possible; otherwise page it with \`get_result\`.
|
|
19
19
|
- \`auth_required\`: use \`authorize_connector\`, give its recovery handoff to the operator, then retry the original call.
|
|
20
20
|
|
|
21
|
-
Use \`list_connectors({ probe: false })\` for a fast
|
|
21
|
+
Use \`list_connectors({ probe: false })\` for a fast observed-health inventory; use \`probe: true\` only to diagnose live health or authorization.
|
|
22
22
|
|
|
23
23
|
## Code mode
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Unknown addresses plus dependent calls: search inside the run, not in an outer \`search_tools\`. Parallelize independent calls with \`Promise.all\` or \`connecta.batch\`.
|
|
26
26
|
|
|
27
27
|
Connector namespace calls and \`connecta.call\` use the same read-only gate and throw on downstream errors. Catch only failures the workflow can handle; let authorization failures return to the agent for recovery.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Skip code mode for one call, calls suited to \`batch_call\`, or tools lacking \`readOnlyHint: true\`. Return only the needed reduction.
|
|
30
30
|
`;
|
|
31
31
|
|
|
32
32
|
/**
|
package/src/version.ts
CHANGED