@zackbart/connecta 0.24.2 → 0.24.4

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.
Files changed (97) hide show
  1. package/AGENTS.md +18 -20
  2. package/CHANGELOG.md +205 -1
  3. package/README.md +5 -6
  4. package/dist/auth/bearer.js +2 -0
  5. package/dist/auth/downstream-oauth.d.ts +12 -1
  6. package/dist/auth/downstream-oauth.js +147 -35
  7. package/dist/branding.d.ts +31 -2
  8. package/dist/branding.js +116 -8
  9. package/dist/call-admission.d.ts +4 -0
  10. package/dist/call-admission.js +26 -0
  11. package/dist/catalog-drift.js +9 -4
  12. package/dist/catalog-service.d.ts +2 -0
  13. package/dist/catalog-service.js +25 -8
  14. package/dist/catalog.d.ts +2 -0
  15. package/dist/catalog.js +246 -121
  16. package/dist/connectors/api.d.ts +1 -1
  17. package/dist/connectors/api.js +21 -3
  18. package/dist/connectors/guarded-fetch.d.ts +6 -2
  19. package/dist/connectors/guarded-fetch.js +61 -24
  20. package/dist/connectors/remote-mcp.js +92 -57
  21. package/dist/errors.d.ts +28 -3
  22. package/dist/errors.js +60 -1
  23. package/dist/execute.js +85 -23
  24. package/dist/executor-result.js +3 -1
  25. package/dist/executors/quickjs-child.js +5 -1
  26. package/dist/executors/quickjs-protocol.d.ts +4 -0
  27. package/dist/executors/quickjs-runtime.d.ts +1 -1
  28. package/dist/executors/quickjs-runtime.js +38 -21
  29. package/dist/executors/quickjs.js +68 -27
  30. package/dist/index.d.ts +15 -1
  31. package/dist/index.js +36 -4
  32. package/dist/invocation.js +134 -93
  33. package/dist/mcp-result.js +3 -2
  34. package/dist/meta-tools.js +191 -36
  35. package/dist/operator-ui/generated.js +2 -2
  36. package/dist/operator-ui/view.d.ts +38 -1
  37. package/dist/operator-ui/view.js +71 -0
  38. package/dist/providers/cloudflare.d.ts +14 -2
  39. package/dist/providers/cloudflare.js +107 -16
  40. package/dist/providers/linear.d.ts +26 -4
  41. package/dist/providers/linear.js +19 -4
  42. package/dist/providers/mixpanel.d.ts +16 -3
  43. package/dist/providers/mixpanel.js +13 -2
  44. package/dist/providers/notion.d.ts +8 -1
  45. package/dist/providers/notion.js +83 -10
  46. package/dist/providers/revenuecat.d.ts +30 -4
  47. package/dist/providers/revenuecat.js +42 -4
  48. package/dist/providers/stripe.d.ts +7 -1
  49. package/dist/providers/stripe.js +30 -4
  50. package/dist/providers/vercel.js +11 -1
  51. package/dist/registry.d.ts +22 -2
  52. package/dist/registry.js +101 -13
  53. package/dist/routes/mcp.d.ts +4 -1
  54. package/dist/routes/mcp.js +84 -13
  55. package/dist/routes/oauth.js +4 -0
  56. package/dist/routes/shared.d.ts +1 -0
  57. package/dist/routes/shared.js +4 -4
  58. package/dist/server.js +15 -3
  59. package/dist/skills.js +6 -5
  60. package/dist/storage/file.d.ts +6 -2
  61. package/dist/storage/file.js +312 -34
  62. package/dist/storage/memory.js +12 -1
  63. package/dist/types.d.ts +37 -0
  64. package/dist/ui.js +18 -10
  65. package/dist/validate.js +3 -3
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/documentation/architecture.md +193 -165
  69. package/documentation/auth.md +199 -145
  70. package/documentation/code-mode.md +433 -316
  71. package/documentation/meta-tools.md +363 -355
  72. package/examples/worker/AGENTS.md +2 -1
  73. package/examples/worker/README.md +12 -10
  74. package/examples/worker/src/index.ts +12 -15
  75. package/package.json +1 -2
  76. package/templates/node/.env.example +3 -3
  77. package/templates/node/AGENTS.md +5 -4
  78. package/templates/node/README.md +2 -1
  79. package/templates/node/package.json +1 -1
  80. package/templates/node/src/index.ts +23 -22
  81. package/documentation/call-admission.md +0 -142
  82. package/documentation/cloudflare.md +0 -471
  83. package/documentation/connector-guides.md +0 -176
  84. package/documentation/connectors.md +0 -389
  85. package/documentation/linear.md +0 -193
  86. package/documentation/mixpanel.md +0 -160
  87. package/documentation/notion.md +0 -308
  88. package/documentation/operations.md +0 -359
  89. package/documentation/operator-ui.md +0 -135
  90. package/documentation/optional-modules-upgrade.md +0 -243
  91. package/documentation/provider-conventions.md +0 -722
  92. package/documentation/request-admission.md +0 -170
  93. package/documentation/revenuecat.md +0 -305
  94. package/documentation/storage-and-credentials.md +0 -201
  95. package/documentation/stripe.md +0 -262
  96. package/documentation/upgrading.md +0 -754
  97. package/documentation/vercel.md +0 -241
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 renderedValues = values.map((value) => JSON.stringify(value));
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 < renderedValues.length - 1; index += 1) {
359
- prefix.push(renderedValues[index]);
360
- const omitted = renderedValues.length - prefix.length;
361
- const candidate = `(${prefix.join(" | ")} | ${marker(omitted)})`;
362
- if (schemaEncoder.encode(candidate).length > byteLimit)
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
- rendered = candidate;
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 JSON.stringify(schema);
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 { allOf: _members, ...own } = s;
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
- if (typeof s.$ref === "string") {
451
- const name = refName(s.$ref);
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[name];
455
- if (target === undefined)
456
- return name;
457
- seen.add(name);
458
- const rendered = renderSchema(target, defs, seen, depth, options);
459
- seen.delete(name);
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 = JSON.stringify(s.const);
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((Array.isArray(s.required) ? s.required : []));
492
- const declaredKeys = Object.keys(props);
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.join(" | ");
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 JSON.stringify(schema);
640
+ return options.work.json(schema);
524
641
  }
525
642
  const compactSchemas = new WeakMap();
526
- function defsOf(schema) {
527
- return {
528
- ...schema.$defs,
529
- ...schema.definitions,
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 defs = defsOf(schema);
538
- let rendered;
539
- try {
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
- const keys = schemaObjectKeys(schema);
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 defs = defsOf(schema);
598
- let rendered;
599
- let enumTruncated = false;
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
- },
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
- rendered = renderSchema(schema, defs, new Set(), 0, {
611
- ...base,
612
- // Three near-cap enums spend about three quarters of the complete shape
613
- // budget, leaving the final quarter for surrounding syntax before the
614
- // unchanged global fallback applies. Whole values keep this UTF-8 safe.
615
- renderConstraints: true,
616
- constraintByteLimit: MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES,
617
- onConstraintTruncated: () => {
618
- constraintTruncated = true;
619
- },
620
- });
621
- }
622
- catch {
623
- rendered = JSON.stringify(schema);
624
- }
625
- if (schemaEncoder.encode(rendered).length >
626
- MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
627
- try {
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, defs, new Set(), 0);
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 { allOf: _members, ...own } = s;
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
- parts.push(objectKeys(member, defs, seen, depth + 1));
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
- if (typeof s.$ref === "string") {
711
- const name = refName(s.$ref);
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[name];
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: Object.keys(props),
737
- required: Array.isArray(s.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
+ }
@@ -68,5 +68,5 @@ export interface ApiOptions {
68
68
  validateArgs?: boolean;
69
69
  tools: ApiTool[];
70
70
  }
71
- /** A static connector; see provider conventions' two construction-time checks. */
71
+ /** A static connector; every tool passes {@link checkToolContract} first. */
72
72
  export declare function api(id: string, opts: ApiOptions): Connector;
@@ -1,8 +1,17 @@
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));
4
5
  }
5
- /** Enforce provider conventions' two construction-time checks. */
6
+ /**
7
+ * Three things a hand-written surface is refused for at construction rather
8
+ * than in production (#340): no description (discovery has nothing to route on
9
+ * and a guess costs a call), no explicit `readOnlyHint` (connecta never infers
10
+ * the safety class, so an unclassified tool is a deployment bug), and an
11
+ * `inputSchema` the validator cannot compile (declaring one is optional;
12
+ * declaring an unenforceable one is not). None of this reaches a proxied
13
+ * catalog — the contract binds the surfaces we write, not the ones we relay.
14
+ */
6
15
  function checkToolContract(id, tool) {
7
16
  const address = `${id}.${tool.name}`;
8
17
  if (typeof tool.description !== "string" || tool.description.trim() === "") {
@@ -19,7 +28,7 @@ function checkToolContract(id, tool) {
19
28
  if (tool.inputSchema)
20
29
  compileValidator(tool.inputSchema, { address });
21
30
  }
22
- /** A static connector; see provider conventions' two construction-time checks. */
31
+ /** A static connector; every tool passes {@link checkToolContract} first. */
23
32
  export function api(id, opts) {
24
33
  for (const t of opts.tools)
25
34
  checkToolContract(id, t);
@@ -74,7 +83,16 @@ export function api(id, opts) {
74
83
  // its first await never sits handler-less for the thenable-adoption
75
84
  // microtask — workerd and vitest both report that gap as an unhandled
76
85
  // rejection even though the caller catches the failure.
77
- return await tool.handler(input, ctx);
86
+ try {
87
+ return await tool.handler(input, ctx);
88
+ }
89
+ catch (error) {
90
+ // A handler owns its destinations. ctx.baseUrl is Connecta's inbound
91
+ // URL, so it must never masquerade as the failed downstream host.
92
+ if (error instanceof ConnectorCallError || !networkErrorCode(error))
93
+ throw error;
94
+ throw unavailableCallError(error);
95
+ }
78
96
  },
79
97
  };
80
98
  }
@@ -51,7 +51,7 @@ interface GuardedResponse {
51
51
  parseError: unknown;
52
52
  }>;
53
53
  }
54
- /** Parse a decimal `Retry-After` header in seconds into milliseconds. */
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
@@ -87,6 +87,10 @@ export interface GuardedFetchOptions {
87
87
  }
88
88
  /** Send one guarded request and map its response with provider knowledge. */
89
89
  export type GuardedTransport = <T>(request: GuardedRequest, ctx: ConnectorContext, map: GuardedResponseMapper<T>) => Promise<T>;
90
- /** Build the guarded transport described in documentation/connectors.md. */
90
+ /**
91
+ * Build the transport a connector sends every request through. `maxResponseBytes`
92
+ * is required rather than defaulted: what counts as an absurd response is a fact
93
+ * about the API, not about HTTP.
94
+ */
91
95
  export declare function guardedFetch(options: GuardedFetchOptions): GuardedTransport;
92
96
  export {};