@zackbart/connecta 0.18.3 → 0.20.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/CHANGELOG.md +131 -4
- package/dist/apps-shell.d.ts +10 -12
- package/dist/apps-shell.js +29 -220
- package/dist/catalog-service.d.ts +16 -13
- package/dist/catalog-service.js +106 -115
- package/dist/catalog.js +29 -46
- package/dist/connector-scope.js +2 -7
- package/dist/connectors/api.d.ts +4 -16
- package/dist/connectors/api.js +19 -46
- package/dist/connectors/guarded-fetch.d.ts +9 -23
- package/dist/connectors/guarded-fetch.js +38 -76
- package/dist/connectors/remote-mcp.js +36 -79
- package/dist/errors.d.ts +6 -27
- package/dist/errors.js +8 -5
- package/dist/execute.d.ts +23 -28
- package/dist/execute.js +101 -257
- package/dist/executor-result.d.ts +1 -0
- package/dist/executor-result.js +4 -11
- package/dist/executors/quickjs-child.js +1 -3
- package/dist/executors/quickjs-runtime.js +1 -3
- package/dist/executors/quickjs.js +1 -3
- package/dist/index.js +134 -123
- package/dist/invocation.d.ts +1 -1
- package/dist/invocation.js +113 -183
- package/dist/meta-tools.d.ts +15 -29
- package/dist/meta-tools.js +41 -582
- package/dist/operator-ui/generated.d.ts +2 -2
- package/dist/providers/cloudflare.d.ts +2 -18
- package/dist/providers/cloudflare.js +1460 -2451
- package/dist/providers/linear.d.ts +4 -41
- package/dist/providers/linear.js +8 -39
- package/dist/providers/mixpanel.d.ts +3 -25
- package/dist/providers/mixpanel.js +7 -22
- package/dist/providers/notion.d.ts +1 -15
- package/dist/providers/notion.js +44 -173
- package/dist/providers/revenuecat.d.ts +4 -57
- package/dist/providers/revenuecat.js +10 -93
- package/dist/providers/stripe.d.ts +1 -12
- package/dist/providers/stripe.js +7 -45
- package/dist/registry.d.ts +9 -34
- package/dist/registry.js +9 -103
- package/dist/routes/mcp.js +1 -1
- package/dist/routes/oauth.js +3 -3
- package/dist/routes/shared.d.ts +15 -15
- package/dist/routes/shared.js +1 -3
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -5
- package/dist/timeout.d.ts +8 -7
- package/dist/timeout.js +47 -38
- package/dist/types.d.ts +3 -3
- package/dist/ui.d.ts +1 -25
- package/dist/ui.js +18 -45
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/call-admission.md +1 -1
- package/documentation/cloudflare.md +1 -1
- package/documentation/code-mode.md +25 -25
- package/documentation/connectors.md +24 -1
- package/documentation/linear.md +1 -1
- package/documentation/meta-tools.md +4 -30
- package/documentation/mixpanel.md +1 -1
- package/documentation/notion.md +1 -1
- package/documentation/operations.md +29 -25
- package/documentation/provider-conventions.md +4 -5
- package/documentation/revenuecat.md +1 -1
- package/documentation/stripe.md +1 -1
- package/documentation/upgrading.md +43 -8
- package/ethos.md +75 -121
- package/package.json +3 -4
- package/templates/node/package.json +1 -1
- package/documentation/code-first-exploration.md +0 -292
- package/documentation/mcp-2026-07-28.md +0 -46
- package/documentation/mcp-ui-design.md +0 -382
- package/documentation/program-ui-read-calls.md +0 -213
- package/documentation/provider-audit.md +0 -198
- package/documentation/rich-output-design.md +0 -211
package/dist/catalog-service.js
CHANGED
|
@@ -2,7 +2,7 @@ import { compactDiscoverySchema, compactSchema, lexicalCorpusStatistics, lexical
|
|
|
2
2
|
import { mapSettledWithConcurrency, resolveDiscoveryConcurrency, } from "./concurrency.js";
|
|
3
3
|
import { boundedEchoText, classifyCallError, framingError, } from "./errors.js";
|
|
4
4
|
import { connectorGuide, connectorGuideRequired, connectorGuideSummary, connectorSkillName, } from "./skills.js";
|
|
5
|
-
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs,
|
|
5
|
+
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, withDeadline, } from "./timeout.js";
|
|
6
6
|
import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
7
7
|
export const DEFAULT_SEARCH_LIMIT = 8;
|
|
8
8
|
export const MAX_SEARCH_LIMIT = 100;
|
|
@@ -261,10 +261,13 @@ export class CatalogService {
|
|
|
261
261
|
return loading;
|
|
262
262
|
}
|
|
263
263
|
loadForDiscovery(id, label) {
|
|
264
|
-
return
|
|
264
|
+
return withDeadline((signal) => this.loadConnector(id, {
|
|
265
265
|
signal,
|
|
266
266
|
timeoutMs: this.probeTimeoutMs,
|
|
267
|
-
}),
|
|
267
|
+
}), {
|
|
268
|
+
timeoutMs: this.probeTimeoutMs,
|
|
269
|
+
timeoutError: new Error(`${label} timed out after ${this.probeTimeoutMs}ms`),
|
|
270
|
+
});
|
|
268
271
|
}
|
|
269
272
|
outputSchema(connectorId, tool) {
|
|
270
273
|
if (tool.outputSchema)
|
|
@@ -274,17 +277,42 @@ export class CatalogService {
|
|
|
274
277
|
? { schema: observed, source: "observed" }
|
|
275
278
|
: {};
|
|
276
279
|
}
|
|
280
|
+
unknownAddressFailure(address, query) {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
error: {
|
|
284
|
+
...framingError("unknown_address", `Unknown address "${boundedEchoText(address)}"`),
|
|
285
|
+
nextAction: this.searchRecovery({ query: recoveryQuery(query) }, "Find the configured canonical address before retrying."),
|
|
286
|
+
},
|
|
287
|
+
catalogMs: 0,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
catalogLoadFailure(cause, started, connector, toolName) {
|
|
291
|
+
return {
|
|
292
|
+
ok: false,
|
|
293
|
+
error: classifyCallError(cause, "catalog_lookup_failed"),
|
|
294
|
+
catalogMs: Date.now() - started,
|
|
295
|
+
connector,
|
|
296
|
+
toolName,
|
|
297
|
+
cause,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
unknownToolFailure(toolName, connector, started) {
|
|
301
|
+
return {
|
|
302
|
+
ok: false,
|
|
303
|
+
error: {
|
|
304
|
+
...framingError("unknown_tool", `Unknown tool "${boundedEchoText(toolName)}" on connector "${connector.id}"`),
|
|
305
|
+
nextAction: this.searchRecovery({ query: recoveryQuery(toolName), connector: connector.id }, "Find the connector's current canonical tool address."),
|
|
306
|
+
},
|
|
307
|
+
catalogMs: Date.now() - started,
|
|
308
|
+
connector,
|
|
309
|
+
toolName,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
277
312
|
async resolveTool(address, callOptions = {}) {
|
|
278
313
|
const resolved = this.registry.resolveAddress(address);
|
|
279
314
|
if (!resolved) {
|
|
280
|
-
return
|
|
281
|
-
ok: false,
|
|
282
|
-
error: {
|
|
283
|
-
...framingError("unknown_address", `Unknown address "${boundedEchoText(address)}"`),
|
|
284
|
-
nextAction: this.searchRecovery({ query: recoveryQuery(address) }, "Find the configured canonical address before retrying."),
|
|
285
|
-
},
|
|
286
|
-
catalogMs: 0,
|
|
287
|
-
};
|
|
315
|
+
return this.unknownAddressFailure(address, address);
|
|
288
316
|
}
|
|
289
317
|
const started = Date.now();
|
|
290
318
|
let tools;
|
|
@@ -292,30 +320,11 @@ export class CatalogService {
|
|
|
292
320
|
tools = await this.loadConnector(resolved.connector.id, callOptions);
|
|
293
321
|
}
|
|
294
322
|
catch (cause) {
|
|
295
|
-
return
|
|
296
|
-
ok: false,
|
|
297
|
-
error: classifyCallError(cause, "catalog_lookup_failed"),
|
|
298
|
-
catalogMs: Date.now() - started,
|
|
299
|
-
connector: resolved.connector,
|
|
300
|
-
toolName: resolved.toolName,
|
|
301
|
-
cause,
|
|
302
|
-
};
|
|
323
|
+
return this.catalogLoadFailure(cause, started, resolved.connector, resolved.toolName);
|
|
303
324
|
}
|
|
304
325
|
const definition = tools.find((tool) => tool.name === resolved.toolName);
|
|
305
326
|
if (!definition) {
|
|
306
|
-
return
|
|
307
|
-
ok: false,
|
|
308
|
-
error: {
|
|
309
|
-
...framingError("unknown_tool", `Unknown tool "${boundedEchoText(resolved.toolName)}" on connector "${resolved.connector.id}"`),
|
|
310
|
-
nextAction: this.searchRecovery({
|
|
311
|
-
query: recoveryQuery(resolved.toolName),
|
|
312
|
-
connector: resolved.connector.id,
|
|
313
|
-
}, "Find the connector's current canonical tool address."),
|
|
314
|
-
},
|
|
315
|
-
catalogMs: Date.now() - started,
|
|
316
|
-
connector: resolved.connector,
|
|
317
|
-
toolName: resolved.toolName,
|
|
318
|
-
};
|
|
327
|
+
return this.unknownToolFailure(resolved.toolName, resolved.connector, started);
|
|
319
328
|
}
|
|
320
329
|
return {
|
|
321
330
|
ok: true,
|
|
@@ -335,14 +344,7 @@ export class CatalogService {
|
|
|
335
344
|
async resolveToolAlias(connectorId, alias, aliasFor, callOptions = {}) {
|
|
336
345
|
const connector = this.registry.getConnector(connectorId);
|
|
337
346
|
if (!connector) {
|
|
338
|
-
return {
|
|
339
|
-
ok: false,
|
|
340
|
-
error: {
|
|
341
|
-
...framingError("unknown_address", `Unknown address "${boundedEchoText(`${connectorId}.${alias}`)}"`),
|
|
342
|
-
nextAction: this.searchRecovery({ query: recoveryQuery(alias) }, "Find the configured canonical address before retrying."),
|
|
343
|
-
},
|
|
344
|
-
catalogMs: 0,
|
|
345
|
-
};
|
|
347
|
+
return this.unknownAddressFailure(`${connectorId}.${alias}`, alias);
|
|
346
348
|
}
|
|
347
349
|
const started = Date.now();
|
|
348
350
|
let tools;
|
|
@@ -350,27 +352,11 @@ export class CatalogService {
|
|
|
350
352
|
tools = await this.loadConnector(connector.id, callOptions);
|
|
351
353
|
}
|
|
352
354
|
catch (cause) {
|
|
353
|
-
return
|
|
354
|
-
ok: false,
|
|
355
|
-
error: classifyCallError(cause, "catalog_lookup_failed"),
|
|
356
|
-
catalogMs: Date.now() - started,
|
|
357
|
-
connector,
|
|
358
|
-
toolName: alias,
|
|
359
|
-
cause,
|
|
360
|
-
};
|
|
355
|
+
return this.catalogLoadFailure(cause, started, connector, alias);
|
|
361
356
|
}
|
|
362
357
|
const [definition, ...collisions] = tools.filter((tool) => aliasFor(tool.name) === alias);
|
|
363
358
|
if (!definition) {
|
|
364
|
-
return
|
|
365
|
-
ok: false,
|
|
366
|
-
error: {
|
|
367
|
-
...framingError("unknown_tool", `Unknown tool "${boundedEchoText(alias)}" on connector "${connector.id}"`),
|
|
368
|
-
nextAction: this.searchRecovery({ query: recoveryQuery(alias), connector: connector.id }, "Find the connector's current canonical tool address."),
|
|
369
|
-
},
|
|
370
|
-
catalogMs: Date.now() - started,
|
|
371
|
-
connector,
|
|
372
|
-
toolName: alias,
|
|
373
|
-
};
|
|
359
|
+
return this.unknownToolFailure(alias, connector, started);
|
|
374
360
|
}
|
|
375
361
|
if (collisions.length > 0) {
|
|
376
362
|
const names = [definition, ...collisions]
|
|
@@ -494,18 +480,19 @@ export class CatalogService {
|
|
|
494
480
|
a.order - b.order);
|
|
495
481
|
});
|
|
496
482
|
const pageMatches = matches.slice(offset, offset + limit);
|
|
483
|
+
const withSchemas = args.includeSchemas;
|
|
497
484
|
const entries = pageMatches.map((match) => {
|
|
498
|
-
const output =
|
|
485
|
+
const output = withSchemas
|
|
499
486
|
? this.outputSchema(match.connector.id, match.tool)
|
|
500
487
|
: {};
|
|
501
488
|
const input = match.tool.inputSchema ?? { type: "object" };
|
|
502
|
-
const renderedInput =
|
|
503
|
-
? renderSearchSchema(input,
|
|
489
|
+
const renderedInput = withSchemas
|
|
490
|
+
? renderSearchSchema(input, withSchemas)
|
|
504
491
|
: undefined;
|
|
505
|
-
const renderedOutput =
|
|
506
|
-
? renderSearchSchema(output.schema,
|
|
492
|
+
const renderedOutput = withSchemas && output.schema
|
|
493
|
+
? renderSearchSchema(output.schema, withSchemas)
|
|
507
494
|
: undefined;
|
|
508
|
-
const schemaKeys =
|
|
495
|
+
const schemaKeys = withSchemas && args.includeSchemaKeys
|
|
509
496
|
? schemaKeyMetadata(input, output.schema)
|
|
510
497
|
: undefined;
|
|
511
498
|
const description = summarizeDiscoveryDescription(match.tool.description, args.fullDescriptions === true);
|
|
@@ -523,7 +510,7 @@ export class CatalogService {
|
|
|
523
510
|
name: match.tool.name,
|
|
524
511
|
address: `${match.connector.id}.${match.tool.name}`,
|
|
525
512
|
...(description !== undefined ? { description } : {}),
|
|
526
|
-
...(
|
|
513
|
+
...(withSchemas
|
|
527
514
|
? {
|
|
528
515
|
inputSchema: renderedInput?.schema,
|
|
529
516
|
}
|
|
@@ -531,12 +518,12 @@ export class CatalogService {
|
|
|
531
518
|
...(renderedInput?.truncated
|
|
532
519
|
? { inputSchemaTruncated: true }
|
|
533
520
|
: {}),
|
|
534
|
-
...(
|
|
521
|
+
...(withSchemas && output.schema
|
|
535
522
|
? {
|
|
536
523
|
outputSchema: renderedOutput?.schema,
|
|
537
524
|
}
|
|
538
525
|
: {}),
|
|
539
|
-
...(
|
|
526
|
+
...(withSchemas && output.source
|
|
540
527
|
? { outputSchemaSource: output.source }
|
|
541
528
|
: {}),
|
|
542
529
|
...(renderedOutput?.truncated
|
|
@@ -671,41 +658,46 @@ export class CatalogService {
|
|
|
671
658
|
(queryTerms.length > 0
|
|
672
659
|
? matchMode === "partial"
|
|
673
660
|
: unknownConnectorGuidance !== undefined || unavailableCatalogs > 0);
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
661
|
+
const searchGuidance = () => {
|
|
662
|
+
if (unknownConnectorGuidance)
|
|
663
|
+
return unknownConnectorGuidance;
|
|
664
|
+
if (unsearchableQuery) {
|
|
665
|
+
return scopedConnector && unavailableCatalogs > 0
|
|
677
666
|
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
678
|
-
: "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query."
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
667
|
+
: "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query.";
|
|
668
|
+
}
|
|
669
|
+
if (queryTerms.length === 0) {
|
|
670
|
+
if (unavailableCatalogs === 0)
|
|
671
|
+
return undefined;
|
|
672
|
+
return scopedConnector
|
|
673
|
+
? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
674
|
+
: `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Scope by connector to see the typed reason.`;
|
|
675
|
+
}
|
|
676
|
+
if (matches.length === 0) {
|
|
677
|
+
if (scopedConnector) {
|
|
678
|
+
if (unavailableCatalogs > 0) {
|
|
679
|
+
return `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`;
|
|
680
|
+
}
|
|
681
|
+
return scopedGuide?.required
|
|
682
|
+
? `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Fetch queryAnalysis.guide before calling, then refine terms or browse with an empty query.${filterRecovery}`
|
|
683
|
+
: `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`;
|
|
684
|
+
}
|
|
685
|
+
if (identityGuidance)
|
|
686
|
+
return identityGuidance;
|
|
687
|
+
return unavailableCatalogs === 0
|
|
688
|
+
? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
|
|
689
|
+
: `No matching ${safetyLabel}capability was found in the catalogs that answered; ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`;
|
|
690
|
+
}
|
|
691
|
+
if (matchMode !== "partial")
|
|
692
|
+
return undefined;
|
|
693
|
+
if (scopedConnector) {
|
|
694
|
+
return `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`;
|
|
695
|
+
}
|
|
696
|
+
return unavailableCatalogs === 0
|
|
697
|
+
? "No single tool matched every term. Split distinct intents into separate searches."
|
|
698
|
+
: "No single tool matched every term in the catalogs that answered. Split distinct intents into separate searches.";
|
|
699
|
+
};
|
|
700
|
+
const guidance = searchGuidance();
|
|
709
701
|
return {
|
|
710
702
|
entries,
|
|
711
703
|
total: matches.length,
|
|
@@ -859,6 +851,17 @@ export class CatalogService {
|
|
|
859
851
|
});
|
|
860
852
|
}
|
|
861
853
|
}
|
|
854
|
+
function pageTail(page) {
|
|
855
|
+
return {
|
|
856
|
+
total: page.total,
|
|
857
|
+
offset: page.offset,
|
|
858
|
+
limit: page.limit,
|
|
859
|
+
hasMore: page.hasMore,
|
|
860
|
+
...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
|
|
861
|
+
...(page.matchMode ? { matchMode: page.matchMode } : {}),
|
|
862
|
+
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
863
|
+
};
|
|
864
|
+
}
|
|
862
865
|
export function groupedSearchResult(page) {
|
|
863
866
|
const groups = [];
|
|
864
867
|
const byConnector = new Map();
|
|
@@ -884,13 +887,7 @@ export function groupedSearchResult(page) {
|
|
|
884
887
|
}
|
|
885
888
|
return {
|
|
886
889
|
connectors: groups,
|
|
887
|
-
|
|
888
|
-
offset: page.offset,
|
|
889
|
-
limit: page.limit,
|
|
890
|
-
hasMore: page.hasMore,
|
|
891
|
-
...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
|
|
892
|
-
...(page.matchMode ? { matchMode: page.matchMode } : {}),
|
|
893
|
-
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
890
|
+
...pageTail(page),
|
|
894
891
|
};
|
|
895
892
|
}
|
|
896
893
|
export function flatSearchResult(page) {
|
|
@@ -902,12 +899,6 @@ export function flatSearchResult(page) {
|
|
|
902
899
|
? { guideSummary: entry.guideSummary }
|
|
903
900
|
: {}),
|
|
904
901
|
})),
|
|
905
|
-
|
|
906
|
-
offset: page.offset,
|
|
907
|
-
limit: page.limit,
|
|
908
|
-
hasMore: page.hasMore,
|
|
909
|
-
...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
|
|
910
|
-
...(page.matchMode ? { matchMode: page.matchMode } : {}),
|
|
911
|
-
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
902
|
+
...pageTail(page),
|
|
912
903
|
};
|
|
913
904
|
}
|
package/dist/catalog.js
CHANGED
|
@@ -123,9 +123,6 @@ function indexFor(tools) {
|
|
|
123
123
|
}
|
|
124
124
|
return index;
|
|
125
125
|
}
|
|
126
|
-
function documentsFor(tools) {
|
|
127
|
-
return indexFor(tools).documents;
|
|
128
|
-
}
|
|
129
126
|
/**
|
|
130
127
|
* Whole-token equality is the ordinary lexical match. A deliberately narrow
|
|
131
128
|
* inflection check retains useful singular/plural and past-tense recall
|
|
@@ -293,7 +290,7 @@ export function rankTools(tools, query, mode = "all", statistics = lexicalCorpus
|
|
|
293
290
|
const exactNamePhrase = normalized(exactNameQuery);
|
|
294
291
|
const terms = [...new Set(phrase.split(/\s+/).filter(Boolean))];
|
|
295
292
|
const ranked = [];
|
|
296
|
-
|
|
293
|
+
indexFor(tools).documents.forEach((doc, order) => {
|
|
297
294
|
const scored = scoreDocument(doc, phrase, terms, mode, statistics);
|
|
298
295
|
if (scored !== null) {
|
|
299
296
|
ranked.push({
|
|
@@ -425,6 +422,9 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
425
422
|
return JSON.stringify(schema);
|
|
426
423
|
}
|
|
427
424
|
const s = schema;
|
|
425
|
+
const constrain = (rendered) => options.renderConstraints
|
|
426
|
+
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
427
|
+
: rendered;
|
|
428
428
|
// allOf composes rather than replaces: it is checked before every other
|
|
429
429
|
// keyword, and renders the schema's own shape alongside its members instead
|
|
430
430
|
// of returning early. A schema carrying both allOf and properties (the usual
|
|
@@ -457,9 +457,7 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
457
457
|
seen.add(name);
|
|
458
458
|
const rendered = renderSchema(target, defs, seen, depth, options);
|
|
459
459
|
seen.delete(name);
|
|
460
|
-
return
|
|
461
|
-
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
462
|
-
: rendered;
|
|
460
|
+
return constrain(rendered);
|
|
463
461
|
}
|
|
464
462
|
const union = (s.oneOf ?? s.anyOf);
|
|
465
463
|
if (Array.isArray(union)) {
|
|
@@ -467,15 +465,11 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
467
465
|
.map((u) => renderSchema(u, defs, seen, depth + 1, options))
|
|
468
466
|
.join(" | ") ||
|
|
469
467
|
"unknown";
|
|
470
|
-
return
|
|
471
|
-
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
472
|
-
: rendered;
|
|
468
|
+
return constrain(rendered);
|
|
473
469
|
}
|
|
474
470
|
if (Array.isArray(s.enum)) {
|
|
475
471
|
const rendered = renderEnum(s.enum, options.enumByteLimit, options.onEnumTruncated);
|
|
476
|
-
return
|
|
477
|
-
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
478
|
-
: rendered;
|
|
472
|
+
return constrain(rendered);
|
|
479
473
|
}
|
|
480
474
|
// Checked before type/properties so a discriminator like
|
|
481
475
|
// { type: "string", const: "emoji" } renders as "emoji" rather than string.
|
|
@@ -483,9 +477,7 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
483
477
|
// `const: undefined` must fall through to the regular type rendering.
|
|
484
478
|
if (s.const !== undefined) {
|
|
485
479
|
const rendered = JSON.stringify(s.const);
|
|
486
|
-
return
|
|
487
|
-
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
488
|
-
: rendered;
|
|
480
|
+
return constrain(rendered);
|
|
489
481
|
}
|
|
490
482
|
const type = s.type;
|
|
491
483
|
if (type === "array" || s.items) {
|
|
@@ -519,15 +511,11 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
519
511
|
.join(", ")} }`;
|
|
520
512
|
}
|
|
521
513
|
if (typeof type === "string") {
|
|
522
|
-
return
|
|
523
|
-
? renderConstraints(type, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
524
|
-
: type;
|
|
514
|
+
return constrain(type);
|
|
525
515
|
}
|
|
526
516
|
if (Array.isArray(type)) {
|
|
527
517
|
const rendered = type.join(" | ");
|
|
528
|
-
return
|
|
529
|
-
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
530
|
-
: rendered;
|
|
518
|
+
return constrain(rendered);
|
|
531
519
|
}
|
|
532
520
|
if (options.renderConstraints && constraintEntries(s).length > 0) {
|
|
533
521
|
return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
|
|
@@ -535,15 +523,18 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
535
523
|
return JSON.stringify(schema);
|
|
536
524
|
}
|
|
537
525
|
const compactSchemas = new WeakMap();
|
|
526
|
+
function defsOf(schema) {
|
|
527
|
+
return {
|
|
528
|
+
...schema.$defs,
|
|
529
|
+
...schema.definitions,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
538
532
|
/** Render and cache a compact TypeScript-like representation of JSON Schema. */
|
|
539
533
|
export function compactSchema(schema) {
|
|
540
534
|
const cached = compactSchemas.get(schema);
|
|
541
535
|
if (cached)
|
|
542
536
|
return cached;
|
|
543
|
-
const defs =
|
|
544
|
-
...schema.$defs,
|
|
545
|
-
...schema.definitions,
|
|
546
|
-
};
|
|
537
|
+
const defs = defsOf(schema);
|
|
547
538
|
let rendered;
|
|
548
539
|
try {
|
|
549
540
|
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
@@ -603,24 +594,24 @@ export function compactDiscoverySchema(schema) {
|
|
|
603
594
|
const cached = compactDiscoverySchemas.get(schema);
|
|
604
595
|
if (cached)
|
|
605
596
|
return cached;
|
|
606
|
-
const defs =
|
|
607
|
-
...schema.$defs,
|
|
608
|
-
...schema.definitions,
|
|
609
|
-
};
|
|
597
|
+
const defs = defsOf(schema);
|
|
610
598
|
let rendered;
|
|
611
599
|
let enumTruncated = false;
|
|
612
600
|
let constraintTruncated = false;
|
|
601
|
+
const base = {
|
|
602
|
+
propertyDescriptions: false,
|
|
603
|
+
requiredFirst: true,
|
|
604
|
+
enumByteLimit: MAX_COMPACT_DISCOVERY_ENUM_BYTES,
|
|
605
|
+
onEnumTruncated: () => {
|
|
606
|
+
enumTruncated = true;
|
|
607
|
+
},
|
|
608
|
+
};
|
|
613
609
|
try {
|
|
614
610
|
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
615
|
-
|
|
616
|
-
requiredFirst: true,
|
|
611
|
+
...base,
|
|
617
612
|
// Three near-cap enums spend about three quarters of the complete shape
|
|
618
613
|
// budget, leaving the final quarter for surrounding syntax before the
|
|
619
614
|
// unchanged global fallback applies. Whole values keep this UTF-8 safe.
|
|
620
|
-
enumByteLimit: MAX_COMPACT_DISCOVERY_ENUM_BYTES,
|
|
621
|
-
onEnumTruncated: () => {
|
|
622
|
-
enumTruncated = true;
|
|
623
|
-
},
|
|
624
615
|
renderConstraints: true,
|
|
625
616
|
constraintByteLimit: MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES,
|
|
626
617
|
onConstraintTruncated: () => {
|
|
@@ -635,12 +626,7 @@ export function compactDiscoverySchema(schema) {
|
|
|
635
626
|
MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
|
|
636
627
|
try {
|
|
637
628
|
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
638
|
-
|
|
639
|
-
requiredFirst: true,
|
|
640
|
-
enumByteLimit: MAX_COMPACT_DISCOVERY_ENUM_BYTES,
|
|
641
|
-
onEnumTruncated: () => {
|
|
642
|
-
enumTruncated = true;
|
|
643
|
-
},
|
|
629
|
+
...base,
|
|
644
630
|
renderConstraints: false,
|
|
645
631
|
});
|
|
646
632
|
constraintTruncated = true;
|
|
@@ -683,10 +669,7 @@ export function compactDiscoverySchema(schema) {
|
|
|
683
669
|
export function schemaObjectKeys(schema) {
|
|
684
670
|
if (!schema)
|
|
685
671
|
return undefined;
|
|
686
|
-
const defs =
|
|
687
|
-
...schema.$defs,
|
|
688
|
-
...schema.definitions,
|
|
689
|
-
};
|
|
672
|
+
const defs = defsOf(schema);
|
|
690
673
|
try {
|
|
691
674
|
return objectKeys(schema, defs, new Set(), 0);
|
|
692
675
|
}
|
package/dist/connector-scope.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sleep } from "./timeout.js";
|
|
1
2
|
/** Enough for local transport abort/close without letting cleanup own latency. */
|
|
2
3
|
const CONNECTOR_SCOPE_CLOSE_BUDGET_MS = 100;
|
|
3
4
|
/**
|
|
@@ -11,13 +12,7 @@ const CONNECTOR_SCOPE_CLOSE_BUDGET_MS = 100;
|
|
|
11
12
|
const CONNECTOR_SCOPE_DEFER_BUDGET_MS = 2_000;
|
|
12
13
|
/** Resolve when `work` settles or `budgetMs` expires; never reject. */
|
|
13
14
|
function waitAtMost(work, budgetMs) {
|
|
14
|
-
return
|
|
15
|
-
const timer = setTimeout(resolve, budgetMs);
|
|
16
|
-
work.then(() => {
|
|
17
|
-
clearTimeout(timer);
|
|
18
|
-
resolve();
|
|
19
|
-
});
|
|
20
|
-
});
|
|
15
|
+
return Promise.race([work, sleep(budgetMs)]).then(() => { });
|
|
21
16
|
}
|
|
22
17
|
/**
|
|
23
18
|
* Tell a connector that a scope owned by the core has ended.
|
package/dist/connectors/api.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Connector, ConnectorCallAdmissionPolicy, ConnectorCredentialConfig, ConnectorCredentialValues, ConnectorContext, ConnectorUsageGuide, CredentialTestResult, JsonSchema, ToolAnnotations } from "../types.js";
|
|
2
|
+
export declare function defined<T extends object>(value: T): {
|
|
3
|
+
[K in keyof T]?: Exclude<T[K], undefined>;
|
|
4
|
+
};
|
|
2
5
|
export interface ApiTool {
|
|
3
6
|
name: string;
|
|
4
7
|
/**
|
|
@@ -63,20 +66,5 @@ export interface ApiOptions {
|
|
|
63
66
|
validateArgs?: boolean;
|
|
64
67
|
tools: ApiTool[];
|
|
65
68
|
}
|
|
66
|
-
/**
|
|
67
|
-
* A connector defined entirely in code: static tool defs + fetch handlers.
|
|
68
|
-
* Tool inputs are plain JSON Schema objects (bring your own zod-to-json-schema
|
|
69
|
-
* conversion if you prefer zod). call_tool JSON-wraps the handler's return.
|
|
70
|
-
*
|
|
71
|
-
* Every tool declares a description and an explicit `annotations.readOnlyHint`,
|
|
72
|
-
* and any `inputSchema` it carries must compile — a tool that fails the
|
|
73
|
-
* contract throws here rather than reaching a catalog. Arguments are then
|
|
74
|
-
* validated against `inputSchema` before the handler runs (disable with
|
|
75
|
-
* `validateArgs: false`, which opts out of enforcement, not out of the schema
|
|
76
|
-
* being real), and a schema that only reveals itself as unenforceable on first
|
|
77
|
-
* use — an unresolvable `$ref`, say — fails the call rather than passing raw
|
|
78
|
-
* arguments through. Remote MCP inputs are also validated, but in the shared
|
|
79
|
-
* invocation path against the request-local downstream catalog, where a
|
|
80
|
-
* downstream's schema is its own affair and stays fail-open.
|
|
81
|
-
*/
|
|
69
|
+
/** A static connector; see provider conventions' two construction-time checks. */
|
|
82
70
|
export declare function api(id: string, opts: ApiOptions): Connector;
|
package/dist/connectors/api.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { compileValidator, validateToolInput } from "../validate.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* guess: what the tool does, and whether calling it needs a human's blessing.
|
|
7
|
-
* Guessing either one is how a deployment boots into the wrong shape, so this
|
|
8
|
-
* throws instead. Note what it does *not* do — it never reads a name, verb, or
|
|
9
|
-
* HTTP method to infer a safety class. An unclassified tool is a bug in the
|
|
10
|
-
* deployment, not a puzzle for connecta to solve.
|
|
11
|
-
*/
|
|
2
|
+
export function defined(value) {
|
|
3
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
4
|
+
}
|
|
5
|
+
/** Enforce provider conventions' two construction-time checks. */
|
|
12
6
|
function checkToolContract(id, tool) {
|
|
13
7
|
const address = `${id}.${tool.name}`;
|
|
14
8
|
if (typeof tool.description !== "string" || tool.description.trim() === "") {
|
|
@@ -25,55 +19,34 @@ function checkToolContract(id, tool) {
|
|
|
25
19
|
if (tool.inputSchema)
|
|
26
20
|
compileValidator(tool.inputSchema, { address });
|
|
27
21
|
}
|
|
28
|
-
/**
|
|
29
|
-
* A connector defined entirely in code: static tool defs + fetch handlers.
|
|
30
|
-
* Tool inputs are plain JSON Schema objects (bring your own zod-to-json-schema
|
|
31
|
-
* conversion if you prefer zod). call_tool JSON-wraps the handler's return.
|
|
32
|
-
*
|
|
33
|
-
* Every tool declares a description and an explicit `annotations.readOnlyHint`,
|
|
34
|
-
* and any `inputSchema` it carries must compile — a tool that fails the
|
|
35
|
-
* contract throws here rather than reaching a catalog. Arguments are then
|
|
36
|
-
* validated against `inputSchema` before the handler runs (disable with
|
|
37
|
-
* `validateArgs: false`, which opts out of enforcement, not out of the schema
|
|
38
|
-
* being real), and a schema that only reveals itself as unenforceable on first
|
|
39
|
-
* use — an unresolvable `$ref`, say — fails the call rather than passing raw
|
|
40
|
-
* arguments through. Remote MCP inputs are also validated, but in the shared
|
|
41
|
-
* invocation path against the request-local downstream catalog, where a
|
|
42
|
-
* downstream's schema is its own affair and stays fail-open.
|
|
43
|
-
*/
|
|
22
|
+
/** A static connector; see provider conventions' two construction-time checks. */
|
|
44
23
|
export function api(id, opts) {
|
|
45
24
|
for (const t of opts.tools)
|
|
46
25
|
checkToolContract(id, t);
|
|
47
26
|
const defs = opts.tools.map((t) => ({
|
|
48
27
|
name: t.name,
|
|
49
28
|
description: t.description,
|
|
50
|
-
...(
|
|
51
|
-
|
|
29
|
+
...defined({
|
|
30
|
+
inputSchema: t.inputSchema,
|
|
31
|
+
outputSchema: t.outputSchema,
|
|
32
|
+
}),
|
|
52
33
|
annotations: t.annotations,
|
|
53
34
|
}));
|
|
54
35
|
const byName = new Map(opts.tools.map((t) => [t.name, t]));
|
|
55
36
|
const validateArgs = opts.validateArgs ?? true;
|
|
56
37
|
return {
|
|
57
38
|
id,
|
|
58
|
-
...(
|
|
39
|
+
...defined({ title: opts.title }),
|
|
59
40
|
kind: "api",
|
|
60
|
-
...(
|
|
61
|
-
|
|
62
|
-
:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
...(opts.usageGuide !== undefined ? { usageGuide: opts.usageGuide } : {}),
|
|
70
|
-
...(opts.credential !== undefined ? { credential: opts.credential } : {}),
|
|
71
|
-
...(opts.testCredential !== undefined
|
|
72
|
-
? { testCredential: opts.testCredential }
|
|
73
|
-
: {}),
|
|
74
|
-
...(opts.testCredentials !== undefined
|
|
75
|
-
? { testCredentials: opts.testCredentials }
|
|
76
|
-
: {}),
|
|
41
|
+
...defined({
|
|
42
|
+
description: opts.description,
|
|
43
|
+
maxResultBytes: opts.maxResultBytes,
|
|
44
|
+
callAdmission: opts.callAdmission,
|
|
45
|
+
usageGuide: opts.usageGuide,
|
|
46
|
+
credential: opts.credential,
|
|
47
|
+
testCredential: opts.testCredential,
|
|
48
|
+
testCredentials: opts.testCredentials,
|
|
49
|
+
}),
|
|
77
50
|
staticTools: defs,
|
|
78
51
|
async listTools() {
|
|
79
52
|
return defs;
|