@zackbart/connecta 0.24.2 → 0.24.3
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 +141 -0
- package/dist/auth/bearer.js +2 -0
- package/dist/auth/downstream-oauth.d.ts +12 -1
- package/dist/auth/downstream-oauth.js +147 -35
- package/dist/call-admission.d.ts +4 -0
- package/dist/call-admission.js +26 -0
- package/dist/catalog-drift.js +9 -4
- package/dist/catalog-service.d.ts +2 -0
- package/dist/catalog-service.js +25 -8
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +246 -121
- package/dist/connectors/api.js +11 -1
- package/dist/connectors/guarded-fetch.d.ts +1 -1
- package/dist/connectors/guarded-fetch.js +27 -20
- package/dist/connectors/remote-mcp.js +84 -53
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +58 -0
- package/dist/execute.js +85 -23
- package/dist/executor-result.js +3 -1
- package/dist/executors/quickjs-child.js +5 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -0
- package/dist/executors/quickjs-runtime.d.ts +1 -1
- package/dist/executors/quickjs-runtime.js +38 -21
- package/dist/executors/quickjs.js +68 -27
- package/dist/index.d.ts +14 -0
- package/dist/index.js +24 -3
- package/dist/invocation.js +134 -93
- package/dist/mcp-result.js +3 -2
- package/dist/meta-tools.js +118 -39
- package/dist/registry.d.ts +14 -2
- package/dist/registry.js +87 -13
- package/dist/routes/mcp.d.ts +4 -1
- package/dist/routes/mcp.js +84 -13
- package/dist/routes/oauth.js +4 -0
- package/dist/routes/shared.d.ts +1 -0
- package/dist/routes/shared.js +4 -4
- package/dist/server.js +15 -3
- package/dist/skills.js +6 -5
- package/dist/storage/file.d.ts +6 -2
- package/dist/storage/file.js +312 -34
- package/dist/storage/memory.js +12 -1
- package/dist/validate.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +22 -6
- package/documentation/auth.md +42 -9
- package/documentation/call-admission.md +24 -8
- package/documentation/code-mode.md +34 -22
- package/documentation/connectors.md +47 -5
- package/documentation/meta-tools.md +74 -6
- package/documentation/operations.md +19 -19
- package/documentation/provider-conventions.md +7 -0
- package/documentation/request-admission.md +38 -4
- package/documentation/storage-and-credentials.md +54 -1
- package/documentation/upgrading.md +18 -4
- package/package.json +1 -1
- package/templates/node/package.json +1 -1
package/dist/catalog.js
CHANGED
|
@@ -5,6 +5,53 @@ const MAX_COMPACT_DISCOVERY_ENUM_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
|
|
|
5
5
|
const MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
|
|
6
6
|
const schemaEncoder = new TextEncoder();
|
|
7
7
|
const COMPACT_DISCOVERY_TRUNCATION = " /* truncated */";
|
|
8
|
+
const MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES = 8_192;
|
|
9
|
+
const MAX_SCHEMA_WORK = 2_000;
|
|
10
|
+
const schemaWorkExceeded = Symbol("schema work budget exceeded");
|
|
11
|
+
const schemaSizeExceeded = Symbol("schema byte budget exceeded");
|
|
12
|
+
class SchemaWork {
|
|
13
|
+
byteLimit;
|
|
14
|
+
remaining = MAX_SCHEMA_WORK;
|
|
15
|
+
truncated = false;
|
|
16
|
+
refs = new Map();
|
|
17
|
+
constructor(byteLimit = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
|
|
18
|
+
this.byteLimit = byteLimit;
|
|
19
|
+
}
|
|
20
|
+
visit() {
|
|
21
|
+
if (this.remaining-- <= 0)
|
|
22
|
+
throw schemaWorkExceeded;
|
|
23
|
+
}
|
|
24
|
+
text(value) {
|
|
25
|
+
// Check code units first so encoding a hostile scalar is itself bounded.
|
|
26
|
+
if (value.length > this.byteLimit ||
|
|
27
|
+
schemaEncoder.encode(value).length > this.byteLimit) {
|
|
28
|
+
throw schemaSizeExceeded;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
json(value) {
|
|
33
|
+
// The raw-JSON fallback and const/enum values must spend the same work
|
|
34
|
+
// budget as schema nodes, including values nested inside unknown keywords.
|
|
35
|
+
const visit = this.visit.bind(this);
|
|
36
|
+
const text = this.text.bind(this);
|
|
37
|
+
const ancestors = [];
|
|
38
|
+
return this.text(JSON.stringify(value, function (key, item) {
|
|
39
|
+
visit();
|
|
40
|
+
text(key);
|
|
41
|
+
if (typeof item === "string")
|
|
42
|
+
text(item);
|
|
43
|
+
if (item !== null && typeof item === "object") {
|
|
44
|
+
while (ancestors.length && ancestors[ancestors.length - 1] !== this) {
|
|
45
|
+
ancestors.pop();
|
|
46
|
+
}
|
|
47
|
+
if (ancestors.length > 32)
|
|
48
|
+
throw schemaWorkExceeded;
|
|
49
|
+
ancestors.push(item);
|
|
50
|
+
}
|
|
51
|
+
return item;
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
8
55
|
export function summarizeDescription(text, full) {
|
|
9
56
|
return summarizeToLength(text, full, DEFAULT_DESCRIPTION_LENGTH);
|
|
10
57
|
}
|
|
@@ -315,6 +362,13 @@ function refName(ref) {
|
|
|
315
362
|
*/
|
|
316
363
|
function declaresShape(s) {
|
|
317
364
|
return (typeof s.$ref === "string" ||
|
|
365
|
+
typeof s.$dynamicRef === "string" ||
|
|
366
|
+
Array.isArray(s.allOf) ||
|
|
367
|
+
Array.isArray(s.prefixItems) ||
|
|
368
|
+
s.dependentSchemas !== undefined ||
|
|
369
|
+
s.if !== undefined ||
|
|
370
|
+
s.then !== undefined ||
|
|
371
|
+
s.else !== undefined ||
|
|
318
372
|
Array.isArray(s.oneOf) ||
|
|
319
373
|
Array.isArray(s.anyOf) ||
|
|
320
374
|
Array.isArray(s.enum) ||
|
|
@@ -342,30 +396,42 @@ function grouped(part) {
|
|
|
342
396
|
}
|
|
343
397
|
return part;
|
|
344
398
|
}
|
|
345
|
-
function renderEnum(values, byteLimit, onTruncated) {
|
|
399
|
+
function renderEnum(values, work, byteLimit, onTruncated) {
|
|
346
400
|
if (values.length === 0)
|
|
347
401
|
return "never";
|
|
348
|
-
const
|
|
349
|
-
const full = renderedValues.join(" | ");
|
|
350
|
-
if (byteLimit === undefined ||
|
|
351
|
-
schemaEncoder.encode(full).length <= byteLimit) {
|
|
352
|
-
return full;
|
|
353
|
-
}
|
|
354
|
-
onTruncated?.();
|
|
402
|
+
const limit = byteLimit ?? MAX_COMPACT_DISCOVERY_ENUM_BYTES;
|
|
355
403
|
const marker = (omitted) => `unknown /* ${omitted} enum ${omitted === 1 ? "value" : "values"} omitted */`;
|
|
356
404
|
let rendered = `(${marker(values.length)})`;
|
|
357
405
|
const prefix = [];
|
|
358
|
-
for (let index = 0; index <
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
406
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
407
|
+
let value;
|
|
408
|
+
try {
|
|
409
|
+
value = work.json(values[index]);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (error !== schemaSizeExceeded)
|
|
413
|
+
throw error;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
prefix.push(value);
|
|
417
|
+
const full = prefix.join(" | ");
|
|
418
|
+
if (schemaEncoder.encode(full).length > limit)
|
|
363
419
|
break;
|
|
364
|
-
|
|
420
|
+
if (index === values.length - 1)
|
|
421
|
+
return full;
|
|
422
|
+
const omitted = values.length - prefix.length;
|
|
423
|
+
const candidate = `(${full} | ${marker(omitted)})`;
|
|
424
|
+
if (schemaEncoder.encode(candidate).length <= limit)
|
|
425
|
+
rendered = candidate;
|
|
365
426
|
}
|
|
427
|
+
onTruncated?.();
|
|
366
428
|
return rendered;
|
|
367
429
|
}
|
|
368
430
|
function safeConstraintValue(value) {
|
|
431
|
+
if (value.length > MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES) {
|
|
432
|
+
// This placeholder only participates in the byte check and is dropped whole.
|
|
433
|
+
return "x".repeat(MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES + 1);
|
|
434
|
+
}
|
|
369
435
|
return JSON.stringify(value).replaceAll("*/", "*\\/");
|
|
370
436
|
}
|
|
371
437
|
function constraintEntries(schema) {
|
|
@@ -416,17 +482,36 @@ function renderConstraints(base, schema, byteLimit, onTruncated) {
|
|
|
416
482
|
: `${grouped(base)} /* ${kept.join("; ")} */`;
|
|
417
483
|
}
|
|
418
484
|
function renderSchema(schema, defs, seen, depth, options) {
|
|
485
|
+
options.work.visit();
|
|
486
|
+
return options.work.text(renderSchemaNode(schema, defs, seen, depth, options));
|
|
487
|
+
}
|
|
488
|
+
function renderSchemaNode(schema, defs, seen, depth, options) {
|
|
419
489
|
if (depth > 4)
|
|
420
490
|
return "…";
|
|
421
491
|
if (schema === null || typeof schema !== "object") {
|
|
422
|
-
return
|
|
492
|
+
return options.work.json(schema);
|
|
423
493
|
}
|
|
424
494
|
const s = schema;
|
|
425
495
|
const constrain = (rendered) => options.renderConstraints
|
|
426
496
|
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
427
497
|
: rendered;
|
|
498
|
+
// Conditions cannot be expressed by a single static shape. Preserve the
|
|
499
|
+
// base and send callers to the exact schema instead of hiding the rules.
|
|
500
|
+
if (s.dependentSchemas !== undefined || s.if !== undefined ||
|
|
501
|
+
s.then !== undefined || s.else !== undefined) {
|
|
502
|
+
options.work.truncated = true;
|
|
503
|
+
const base = Object.create(null);
|
|
504
|
+
for (const key of propertyNames(s, options.work)) {
|
|
505
|
+
if (!["dependentSchemas", "if", "then", "else"].includes(key))
|
|
506
|
+
base[key] = s[key];
|
|
507
|
+
}
|
|
508
|
+
const rendered = declaresShape(base)
|
|
509
|
+
? renderSchema(base, defs, seen, depth, options)
|
|
510
|
+
: "unknown";
|
|
511
|
+
return `${rendered} /* conditional */`;
|
|
512
|
+
}
|
|
428
513
|
// allOf composes rather than replaces: it is checked before every other
|
|
429
|
-
// keyword, and renders the schema's own shape alongside its members instead
|
|
514
|
+
// shape keyword, and renders the schema's own shape alongside its members instead
|
|
430
515
|
// of returning early. A schema carrying both allOf and properties (the usual
|
|
431
516
|
// OpenAPI-derived "extend this base" shape, and equally legal with $ref,
|
|
432
517
|
// enum, const, or items) would otherwise silently drop whichever half lost
|
|
@@ -434,7 +519,11 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
434
519
|
// specific half, and is rendered at the current depth because its members
|
|
435
520
|
// sit at this nesting level, not one below.
|
|
436
521
|
if (Array.isArray(s.allOf)) {
|
|
437
|
-
const
|
|
522
|
+
const own = Object.create(null);
|
|
523
|
+
for (const key of propertyNames(s, options.work)) {
|
|
524
|
+
if (key !== "allOf")
|
|
525
|
+
own[key] = s[key];
|
|
526
|
+
}
|
|
438
527
|
const parts = declaresShape(own)
|
|
439
528
|
? [renderSchema(own, defs, seen, depth, options)]
|
|
440
529
|
: [];
|
|
@@ -447,16 +536,27 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
447
536
|
return parts[0];
|
|
448
537
|
return parts.map(grouped).join(" & ");
|
|
449
538
|
}
|
|
450
|
-
|
|
451
|
-
|
|
539
|
+
const reference = s.$ref ?? s.$dynamicRef;
|
|
540
|
+
if (typeof reference === "string") {
|
|
541
|
+
const dynamic = s.$ref === undefined;
|
|
542
|
+
const rawName = refName(options.work.text(reference));
|
|
543
|
+
const name = dynamic ? rawName.replace(/^#/, "") : rawName;
|
|
452
544
|
if (seen.has(name))
|
|
453
545
|
return name;
|
|
454
|
-
const target = defs
|
|
455
|
-
if (target === undefined)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
546
|
+
const target = resolveDefinition(defs, name);
|
|
547
|
+
if (target === undefined) {
|
|
548
|
+
if (dynamic)
|
|
549
|
+
options.work.truncated = true;
|
|
550
|
+
return dynamic ? "unknown" : name;
|
|
551
|
+
}
|
|
552
|
+
const cacheKey = JSON.stringify([name, depth, [...seen]]);
|
|
553
|
+
let rendered = options.work.refs.get(cacheKey);
|
|
554
|
+
if (rendered === undefined) {
|
|
555
|
+
seen.add(name);
|
|
556
|
+
rendered = renderSchema(target, defs, seen, depth, options);
|
|
557
|
+
seen.delete(name);
|
|
558
|
+
options.work.refs.set(cacheKey, rendered);
|
|
559
|
+
}
|
|
460
560
|
return constrain(rendered);
|
|
461
561
|
}
|
|
462
562
|
const union = (s.oneOf ?? s.anyOf);
|
|
@@ -468,7 +568,7 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
468
568
|
return constrain(rendered);
|
|
469
569
|
}
|
|
470
570
|
if (Array.isArray(s.enum)) {
|
|
471
|
-
const rendered = renderEnum(s.enum, options.enumByteLimit, options.onEnumTruncated);
|
|
571
|
+
const rendered = renderEnum(s.enum, options.work, options.enumByteLimit, options.onEnumTruncated);
|
|
472
572
|
return constrain(rendered);
|
|
473
573
|
}
|
|
474
574
|
// Checked before type/properties so a discriminator like
|
|
@@ -476,10 +576,20 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
476
576
|
// JSON.stringify(undefined) returns undefined (not a string), so an explicit
|
|
477
577
|
// `const: undefined` must fall through to the regular type rendering.
|
|
478
578
|
if (s.const !== undefined) {
|
|
479
|
-
const rendered =
|
|
579
|
+
const rendered = options.work.json(s.const);
|
|
480
580
|
return constrain(rendered);
|
|
481
581
|
}
|
|
482
582
|
const type = s.type;
|
|
583
|
+
if (Array.isArray(s.prefixItems)) {
|
|
584
|
+
const parts = s.prefixItems.map((item) => renderSchema(item, defs, seen, depth + 1, options));
|
|
585
|
+
if (s.items !== false) {
|
|
586
|
+
const rest = s.items === undefined || s.items === true
|
|
587
|
+
? "unknown"
|
|
588
|
+
: renderSchema(s.items, defs, seen, depth + 1, options);
|
|
589
|
+
parts.push(`...${grouped(rest)}[]`);
|
|
590
|
+
}
|
|
591
|
+
return `[${parts.join(", ")}]`;
|
|
592
|
+
}
|
|
483
593
|
if (type === "array" || s.items) {
|
|
484
594
|
const items = s.items
|
|
485
595
|
? renderSchema(s.items, defs, seen, depth + 1, options)
|
|
@@ -488,8 +598,8 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
488
598
|
}
|
|
489
599
|
if (type === "object" || s.properties) {
|
|
490
600
|
const props = (s.properties ?? {});
|
|
491
|
-
const required = new Set((
|
|
492
|
-
const declaredKeys =
|
|
601
|
+
const required = new Set(schemaRequired(s, options.work));
|
|
602
|
+
const declaredKeys = propertyNames(props, options.work);
|
|
493
603
|
const keys = options.requiredFirst
|
|
494
604
|
? [
|
|
495
605
|
...declaredKeys.filter((key) => required.has(key)),
|
|
@@ -503,6 +613,10 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
503
613
|
const optional = required.has(key) ? "" : "?";
|
|
504
614
|
const rendered = renderSchema(props[key], defs, seen, depth + 1, options);
|
|
505
615
|
const description = props[key]?.description;
|
|
616
|
+
if (options.propertyDescriptions && typeof description === "string") {
|
|
617
|
+
options.work.text(description);
|
|
618
|
+
}
|
|
619
|
+
options.work.text(key);
|
|
506
620
|
const comment = options.propertyDescriptions && typeof description === "string"
|
|
507
621
|
? ` // ${description}`
|
|
508
622
|
: "";
|
|
@@ -511,43 +625,40 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
511
625
|
.join(", ")} }`;
|
|
512
626
|
}
|
|
513
627
|
if (typeof type === "string") {
|
|
514
|
-
return constrain(type);
|
|
628
|
+
return constrain(options.work.text(type));
|
|
515
629
|
}
|
|
516
630
|
if (Array.isArray(type)) {
|
|
517
|
-
const rendered = type.
|
|
631
|
+
const rendered = type.map((item) => {
|
|
632
|
+
options.work.visit();
|
|
633
|
+
return options.work.text(String(item));
|
|
634
|
+
}).join(" | ");
|
|
518
635
|
return constrain(rendered);
|
|
519
636
|
}
|
|
520
637
|
if (options.renderConstraints && constraintEntries(s).length > 0) {
|
|
521
638
|
return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
|
|
522
639
|
}
|
|
523
|
-
return
|
|
640
|
+
return options.work.json(schema);
|
|
524
641
|
}
|
|
525
642
|
const compactSchemas = new WeakMap();
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
643
|
+
function resolveDefinition(schema, name) {
|
|
644
|
+
const definitions = schema.definitions;
|
|
645
|
+
const defs = schema.$defs;
|
|
646
|
+
return definitions && Object.hasOwn(definitions, name)
|
|
647
|
+
? definitions[name]
|
|
648
|
+
: defs && Object.hasOwn(defs, name) ? defs[name] : undefined;
|
|
531
649
|
}
|
|
532
650
|
/** Render and cache a compact TypeScript-like representation of JSON Schema. */
|
|
533
651
|
export function compactSchema(schema) {
|
|
652
|
+
return compactDescriptionSchema(schema).text;
|
|
653
|
+
}
|
|
654
|
+
/** Describe allows 8 KiB for property prose, with the same work cap as search. */
|
|
655
|
+
export function compactDescriptionSchema(schema) {
|
|
534
656
|
const cached = compactSchemas.get(schema);
|
|
535
657
|
if (cached)
|
|
536
658
|
return cached;
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
541
|
-
propertyDescriptions: true,
|
|
542
|
-
requiredFirst: false,
|
|
543
|
-
renderConstraints: true,
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
catch {
|
|
547
|
-
rendered = JSON.stringify(schema);
|
|
548
|
-
}
|
|
549
|
-
compactSchemas.set(schema, rendered);
|
|
550
|
-
return rendered;
|
|
659
|
+
const result = boundedCompactSchema(schema, true);
|
|
660
|
+
compactSchemas.set(schema, result);
|
|
661
|
+
return result;
|
|
551
662
|
}
|
|
552
663
|
const compactDiscoverySchemas = new WeakMap();
|
|
553
664
|
/**
|
|
@@ -558,8 +669,12 @@ const compactDiscoverySchemas = new WeakMap();
|
|
|
558
669
|
* Types become `unknown`: pretending a severed nested type is exact would be
|
|
559
670
|
* worse than making the existing truncation flag's recovery route explicit.
|
|
560
671
|
*/
|
|
561
|
-
function truncatedDiscoverySchema(schema) {
|
|
562
|
-
|
|
672
|
+
function truncatedDiscoverySchema(schema, work) {
|
|
673
|
+
let keys;
|
|
674
|
+
try {
|
|
675
|
+
keys = objectKeys(schema, schema, new Set(), 0, work);
|
|
676
|
+
}
|
|
677
|
+
catch { /* An exhausted walk has no reliable key inventory. */ }
|
|
563
678
|
if (!keys)
|
|
564
679
|
return `unknown${COMPACT_DISCOVERY_TRUNCATION}`;
|
|
565
680
|
const required = new Set(keys.required);
|
|
@@ -594,63 +709,44 @@ export function compactDiscoverySchema(schema) {
|
|
|
594
709
|
const cached = compactDiscoverySchemas.get(schema);
|
|
595
710
|
if (cached)
|
|
596
711
|
return cached;
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
712
|
+
const result = boundedCompactSchema(schema, false);
|
|
713
|
+
compactDiscoverySchemas.set(schema, result);
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
function boundedCompactSchema(schema, description) {
|
|
717
|
+
const work = new SchemaWork(description ? MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES : MAX_COMPACT_DISCOVERY_SCHEMA_BYTES);
|
|
718
|
+
const options = {
|
|
719
|
+
work,
|
|
720
|
+
propertyDescriptions: description,
|
|
721
|
+
requiredFirst: !description,
|
|
722
|
+
enumByteLimit: description ? work.byteLimit : MAX_COMPACT_DISCOVERY_ENUM_BYTES,
|
|
723
|
+
onEnumTruncated: () => { work.truncated = true; },
|
|
724
|
+
renderConstraints: true,
|
|
725
|
+
constraintByteLimit: description ? work.byteLimit : MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES,
|
|
726
|
+
onConstraintTruncated: () => { work.truncated = true; },
|
|
608
727
|
};
|
|
609
728
|
try {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
629
|
-
...base,
|
|
630
|
-
renderConstraints: false,
|
|
631
|
-
});
|
|
632
|
-
constraintTruncated = true;
|
|
633
|
-
}
|
|
634
|
-
catch {
|
|
635
|
-
rendered = JSON.stringify(schema);
|
|
729
|
+
const text = renderSchema(schema, schema, new Set(), 0, options);
|
|
730
|
+
return { text, truncated: work.truncated };
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
// A constraint-free retry shares the original work budget. Repeated refs
|
|
734
|
+
// are memoized only within each pass because their text includes constraints.
|
|
735
|
+
if (error === schemaSizeExceeded && !description) {
|
|
736
|
+
work.refs.clear();
|
|
737
|
+
try {
|
|
738
|
+
return {
|
|
739
|
+
text: renderSchema(schema, schema, new Set(), 0, {
|
|
740
|
+
...options,
|
|
741
|
+
renderConstraints: false,
|
|
742
|
+
}),
|
|
743
|
+
truncated: true,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
catch { /* Fall through to a bounded key-only shape. */ }
|
|
636
747
|
}
|
|
748
|
+
return { text: truncatedDiscoverySchema(schema, work), truncated: true };
|
|
637
749
|
}
|
|
638
|
-
const bytes = schemaEncoder.encode(rendered);
|
|
639
|
-
let result;
|
|
640
|
-
if (bytes.length <= MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
|
|
641
|
-
result = {
|
|
642
|
-
text: rendered,
|
|
643
|
-
truncated: enumTruncated || constraintTruncated,
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
else {
|
|
647
|
-
result = {
|
|
648
|
-
text: truncatedDiscoverySchema(schema),
|
|
649
|
-
truncated: true,
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
compactDiscoverySchemas.set(schema, result);
|
|
653
|
-
return result;
|
|
654
750
|
}
|
|
655
751
|
/**
|
|
656
752
|
* Walk a schema the way renderSchema does — composing `allOf` and resolving
|
|
@@ -669,9 +765,8 @@ export function compactDiscoverySchema(schema) {
|
|
|
669
765
|
export function schemaObjectKeys(schema) {
|
|
670
766
|
if (!schema)
|
|
671
767
|
return undefined;
|
|
672
|
-
const defs = defsOf(schema);
|
|
673
768
|
try {
|
|
674
|
-
return objectKeys(schema,
|
|
769
|
+
return objectKeys(schema, schema, new Set(), 0, new SchemaWork());
|
|
675
770
|
}
|
|
676
771
|
catch {
|
|
677
772
|
return undefined;
|
|
@@ -687,19 +782,27 @@ function mergedKeys(parts) {
|
|
|
687
782
|
};
|
|
688
783
|
}
|
|
689
784
|
/** The key-collecting twin of renderSchema; the branch order must match it. */
|
|
690
|
-
function objectKeys(schema, defs, seen, depth) {
|
|
785
|
+
function objectKeys(schema, defs, seen, depth, work) {
|
|
786
|
+
work.visit();
|
|
691
787
|
if (depth > 4)
|
|
692
788
|
return undefined;
|
|
693
789
|
if (schema === null || typeof schema !== "object")
|
|
694
790
|
return undefined;
|
|
695
791
|
const s = schema;
|
|
696
792
|
if (Array.isArray(s.allOf)) {
|
|
697
|
-
const
|
|
793
|
+
const own = Object.create(null);
|
|
794
|
+
for (const key of propertyNames(s, work)) {
|
|
795
|
+
if (key !== "allOf")
|
|
796
|
+
own[key] = s[key];
|
|
797
|
+
}
|
|
698
798
|
const parts = declaresShape(own)
|
|
699
|
-
? [objectKeys(own, defs, seen, depth)]
|
|
799
|
+
? [objectKeys(own, defs, seen, depth, work)]
|
|
700
800
|
: [];
|
|
701
801
|
for (const member of s.allOf) {
|
|
702
|
-
|
|
802
|
+
const keys = objectKeys(member, defs, seen, depth + 1, work);
|
|
803
|
+
if (!keys)
|
|
804
|
+
return undefined;
|
|
805
|
+
parts.push(keys);
|
|
703
806
|
}
|
|
704
807
|
// An allOf whose members are not all object shapes renders as an
|
|
705
808
|
// intersection with a non-object half; no single key list describes it.
|
|
@@ -707,15 +810,17 @@ function objectKeys(schema, defs, seen, depth) {
|
|
|
707
810
|
? mergedKeys(parts)
|
|
708
811
|
: undefined;
|
|
709
812
|
}
|
|
710
|
-
|
|
711
|
-
|
|
813
|
+
const reference = s.$ref ?? s.$dynamicRef;
|
|
814
|
+
if (typeof reference === "string") {
|
|
815
|
+
const rawName = refName(work.text(reference));
|
|
816
|
+
const name = s.$ref === undefined ? rawName.replace(/^#/, "") : rawName;
|
|
712
817
|
if (seen.has(name))
|
|
713
818
|
return undefined;
|
|
714
|
-
const target = defs
|
|
819
|
+
const target = resolveDefinition(defs, name);
|
|
715
820
|
if (target === undefined)
|
|
716
821
|
return undefined;
|
|
717
822
|
seen.add(name);
|
|
718
|
-
const resolved = objectKeys(target, defs, seen, depth);
|
|
823
|
+
const resolved = objectKeys(target, defs, seen, depth, work);
|
|
719
824
|
seen.delete(name);
|
|
720
825
|
return resolved;
|
|
721
826
|
}
|
|
@@ -725,19 +830,39 @@ function objectKeys(schema, defs, seen, depth) {
|
|
|
725
830
|
return undefined;
|
|
726
831
|
if (s.const !== undefined)
|
|
727
832
|
return undefined;
|
|
728
|
-
if (s.type === "array" || s.items)
|
|
833
|
+
if (s.type === "array" || s.items || Array.isArray(s.prefixItems))
|
|
729
834
|
return undefined;
|
|
730
835
|
if (s.type === "object" || s.properties) {
|
|
731
836
|
const props = s.properties;
|
|
732
837
|
if (props === null || Array.isArray(props) || typeof props !== "object") {
|
|
733
838
|
return { properties: [], required: [] };
|
|
734
839
|
}
|
|
840
|
+
const properties = propertyNames(props, work);
|
|
841
|
+
const declared = new Set(properties);
|
|
735
842
|
return {
|
|
736
|
-
properties
|
|
737
|
-
required:
|
|
738
|
-
? s.required.filter((key) => typeof key === "string")
|
|
739
|
-
: [],
|
|
843
|
+
properties,
|
|
844
|
+
required: schemaRequired(s, work).filter((key) => declared.has(key)),
|
|
740
845
|
};
|
|
741
846
|
}
|
|
742
847
|
return undefined;
|
|
743
848
|
}
|
|
849
|
+
function propertyNames(props, work) {
|
|
850
|
+
const names = [];
|
|
851
|
+
for (const name in props) {
|
|
852
|
+
work.visit();
|
|
853
|
+
if (Object.hasOwn(props, name))
|
|
854
|
+
names.push(work.text(name));
|
|
855
|
+
}
|
|
856
|
+
return names;
|
|
857
|
+
}
|
|
858
|
+
function schemaRequired(schema, work) {
|
|
859
|
+
if (!Array.isArray(schema.required))
|
|
860
|
+
return [];
|
|
861
|
+
const names = [];
|
|
862
|
+
for (const key of schema.required) {
|
|
863
|
+
work.visit();
|
|
864
|
+
if (typeof key === "string")
|
|
865
|
+
names.push(work.text(key));
|
|
866
|
+
}
|
|
867
|
+
return names;
|
|
868
|
+
}
|
package/dist/connectors/api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectorCallError, networkErrorCode, unavailableCallError, } from "../errors.js";
|
|
1
2
|
import { compileValidator, validateToolInput } from "../validate.js";
|
|
2
3
|
export function defined(value) {
|
|
3
4
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
@@ -74,7 +75,16 @@ export function api(id, opts) {
|
|
|
74
75
|
// its first await never sits handler-less for the thenable-adoption
|
|
75
76
|
// microtask — workerd and vitest both report that gap as an unhandled
|
|
76
77
|
// rejection even though the caller catches the failure.
|
|
77
|
-
|
|
78
|
+
try {
|
|
79
|
+
return await tool.handler(input, ctx);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// A handler owns its destinations. ctx.baseUrl is Connecta's inbound
|
|
83
|
+
// URL, so it must never masquerade as the failed downstream host.
|
|
84
|
+
if (error instanceof ConnectorCallError || !networkErrorCode(error))
|
|
85
|
+
throw error;
|
|
86
|
+
throw unavailableCallError(error);
|
|
87
|
+
}
|
|
78
88
|
},
|
|
79
89
|
};
|
|
80
90
|
}
|
|
@@ -51,7 +51,7 @@ interface GuardedResponse {
|
|
|
51
51
|
parseError: unknown;
|
|
52
52
|
}>;
|
|
53
53
|
}
|
|
54
|
-
/** Parse
|
|
54
|
+
/** Parse delta-seconds or an HTTP-date into a non-negative wait window. */
|
|
55
55
|
export declare function retryAfterMs(headers: Headers): number | undefined;
|
|
56
56
|
/**
|
|
57
57
|
* Turn one response into the provider's own result, or throw the provider's
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/** See documentation/connectors.md#the-guarded-fetch-transport. Web APIs only. */
|
|
2
|
-
import { ConnectorCallError } from "../errors.js";
|
|
3
|
-
/** Parse
|
|
2
|
+
import { ConnectorCallError, unavailableCallError } from "../errors.js";
|
|
3
|
+
/** Parse delta-seconds or an HTTP-date into a non-negative wait window. */
|
|
4
4
|
export function retryAfterMs(headers) {
|
|
5
5
|
const raw = headers.get("retry-after");
|
|
6
6
|
if (!raw)
|
|
7
7
|
return undefined;
|
|
8
8
|
const seconds = Number(raw.trim());
|
|
9
|
-
if (
|
|
9
|
+
if (Number.isFinite(seconds)) {
|
|
10
|
+
return seconds < 0 ? undefined : Math.trunc(seconds * 1000);
|
|
11
|
+
}
|
|
12
|
+
// Require the HTTP-date shape, so Date.parse cannot reinterpret "-1" as
|
|
13
|
+
// a calendar date on runtimes that accept loose date strings.
|
|
14
|
+
if (!/^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(raw.trim()))
|
|
10
15
|
return undefined;
|
|
11
|
-
|
|
16
|
+
const date = Date.parse(raw);
|
|
17
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
|
|
12
18
|
}
|
|
13
19
|
/** Statuses that instruct a client to re-send somewhere else. Never followed. */
|
|
14
20
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
@@ -144,13 +150,20 @@ function boundedResponse(provider, response, limit) {
|
|
|
144
150
|
});
|
|
145
151
|
return read;
|
|
146
152
|
};
|
|
153
|
+
let readText;
|
|
154
|
+
const text = () => {
|
|
155
|
+
readText ??= stream
|
|
156
|
+
? bytes().then((body) => decoder.decode(body))
|
|
157
|
+
: response.text().then((body) => {
|
|
158
|
+
const size = encoder.encode(body).length;
|
|
159
|
+
if (size > limit)
|
|
160
|
+
throw oversized(provider, limit, `${size} bytes`);
|
|
161
|
+
return body;
|
|
162
|
+
});
|
|
163
|
+
return readText;
|
|
164
|
+
};
|
|
147
165
|
const json = async () => {
|
|
148
|
-
|
|
149
|
-
// directly is taken at its word, which is the one accessor on the one
|
|
150
|
-
// path where the ceiling cannot be applied.
|
|
151
|
-
if (!stream)
|
|
152
|
-
return await response.json();
|
|
153
|
-
const body = decoder.decode(await bytes());
|
|
166
|
+
const body = await text();
|
|
154
167
|
return body.trim() === "" ? undefined : JSON.parse(body);
|
|
155
168
|
};
|
|
156
169
|
return {
|
|
@@ -158,15 +171,7 @@ function boundedResponse(provider, response, limit) {
|
|
|
158
171
|
ok: response.ok,
|
|
159
172
|
headers: response.headers,
|
|
160
173
|
bytes,
|
|
161
|
-
|
|
162
|
-
if (stream)
|
|
163
|
-
return decoder.decode(await bytes());
|
|
164
|
-
const body = await response.text();
|
|
165
|
-
const size = encoder.encode(body).length;
|
|
166
|
-
if (size > limit)
|
|
167
|
-
throw oversized(provider, limit, `${size} bytes`);
|
|
168
|
-
return body;
|
|
169
|
-
},
|
|
174
|
+
text,
|
|
170
175
|
json,
|
|
171
176
|
jsonResult: () => jsonResult(json),
|
|
172
177
|
};
|
|
@@ -230,7 +235,9 @@ export function guardedFetch(options) {
|
|
|
230
235
|
});
|
|
231
236
|
}
|
|
232
237
|
catch (cause) {
|
|
233
|
-
|
|
238
|
+
if (cause instanceof ConnectorCallError)
|
|
239
|
+
throw cause;
|
|
240
|
+
throw unavailableCallError(cause, url.href, `Could not reach the ${provider} API.`);
|
|
234
241
|
}
|
|
235
242
|
if (REDIRECT_STATUSES.has(response.status)) {
|
|
236
243
|
await response.body?.cancel().catch(() => { });
|