@zackbart/connecta 0.24.4 → 0.26.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.
Files changed (155) hide show
  1. package/AGENTS.md +43 -12
  2. package/CHANGELOG.md +524 -0
  3. package/README.md +32 -15
  4. package/SECURITY.md +6 -5
  5. package/bin/connecta.mjs +5 -1
  6. package/dist/activity.d.ts +10 -3
  7. package/dist/activity.js +10 -1
  8. package/dist/advertised-schema.js +53 -0
  9. package/dist/artifacts/connector.d.ts +55 -0
  10. package/dist/artifacts/connector.js +724 -0
  11. package/dist/artifacts/document.js +76 -0
  12. package/dist/artifacts/guide.js +140 -0
  13. package/dist/artifacts/html-scan.js +329 -0
  14. package/dist/artifacts/json.js +68 -0
  15. package/dist/artifacts/kv-store.d.ts +20 -0
  16. package/dist/artifacts/kv-store.js +140 -0
  17. package/dist/artifacts/markdown.js +449 -0
  18. package/dist/artifacts/module.d.ts +37 -0
  19. package/dist/artifacts/module.js +60 -0
  20. package/dist/artifacts/operations.d.ts +195 -0
  21. package/dist/artifacts/operations.js +944 -0
  22. package/dist/artifacts/refresh.d.ts +53 -0
  23. package/dist/artifacts/refresh.js +235 -0
  24. package/dist/artifacts/routes.js +194 -0
  25. package/dist/artifacts/types.d.ts +214 -0
  26. package/dist/artifacts/types.js +22 -0
  27. package/dist/artifacts/validate.d.ts +62 -0
  28. package/dist/artifacts/validate.js +652 -0
  29. package/dist/artifacts.d.ts +6 -0
  30. package/dist/artifacts.js +9 -0
  31. package/dist/auth/downstream-oauth.js +490 -106
  32. package/dist/call-admission.js +139 -113
  33. package/dist/catalog-service.js +152 -106
  34. package/dist/catalog.js +297 -38
  35. package/dist/concurrency.js +24 -23
  36. package/dist/connector-access.d.ts +18 -4
  37. package/dist/connector-access.js +65 -20
  38. package/dist/connector-caller.d.ts +8 -0
  39. package/dist/connector-caller.js +19 -0
  40. package/dist/connector-scope.js +6 -37
  41. package/dist/connectors/api.d.ts +2 -2
  42. package/dist/connectors/remote-mcp.d.ts +6 -5
  43. package/dist/connectors/remote-mcp.js +330 -240
  44. package/dist/credential-contract.d.ts +9 -0
  45. package/dist/credential-rules.js +2 -2
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +45 -3
  48. package/dist/errors.d.ts +16 -1
  49. package/dist/errors.js +28 -0
  50. package/dist/execute.js +745 -282
  51. package/dist/executor-admission.d.ts +11 -1
  52. package/dist/executor-admission.js +145 -63
  53. package/dist/executors/quickjs-runtime.js +4 -2
  54. package/dist/executors/quickjs.js +255 -212
  55. package/dist/fix-prompt.js +37 -0
  56. package/dist/index.d.ts +72 -7
  57. package/dist/index.js +281 -26
  58. package/dist/invocation.js +371 -286
  59. package/dist/meta-tools.js +213 -94
  60. package/dist/module-contracts.d.ts +21 -1
  61. package/dist/oauth-callback-outcome.js +94 -0
  62. package/dist/oauth-sealing.js +29 -0
  63. package/dist/operator-ui/fix-prompts.js +87 -0
  64. package/dist/operator-ui/generated.js +2 -2
  65. package/dist/operator-ui/model.d.ts +103 -1
  66. package/dist/operator-ui/setup-commands.js +53 -0
  67. package/dist/operator-ui/view.js +128 -3
  68. package/dist/program-source.js +321 -0
  69. package/dist/providers/notion.js +2 -2
  70. package/dist/providers/revenuecat.js +32 -5
  71. package/dist/providers/stripe.d.ts +3 -2
  72. package/dist/providers/vercel.js +1 -1
  73. package/dist/registry.d.ts +43 -7
  74. package/dist/registry.js +434 -288
  75. package/dist/resumable.js +1291 -0
  76. package/dist/routes/activity.js +74 -154
  77. package/dist/routes/actor-labels.js +114 -0
  78. package/dist/routes/credentials.js +196 -163
  79. package/dist/routes/mcp.js +228 -112
  80. package/dist/routes/oauth-management.js +73 -71
  81. package/dist/routes/oauth.js +44 -16
  82. package/dist/routes/operator.js +83 -0
  83. package/dist/routes/shared.d.ts +25 -1
  84. package/dist/routes/shared.js +20 -2
  85. package/dist/routes/ui-data.js +211 -0
  86. package/dist/routes/ui.js +76 -35
  87. package/dist/run-journal.js +347 -0
  88. package/dist/runtime/admission.js +43 -0
  89. package/dist/runtime/call-admission.js +67 -0
  90. package/dist/runtime/connector-scope.js +72 -0
  91. package/dist/runtime/run.js +174 -0
  92. package/dist/runtime/services.js +130 -0
  93. package/dist/runtime/shared-read.js +106 -0
  94. package/dist/runtime/storage.js +66 -0
  95. package/dist/server.js +137 -89
  96. package/dist/skills.js +28 -9
  97. package/dist/storage/file.js +39 -2
  98. package/dist/storage/memory.js +28 -16
  99. package/dist/timeout.js +39 -43
  100. package/dist/tool-safety.d.ts +27 -1
  101. package/dist/tool-safety.js +31 -0
  102. package/dist/types.d.ts +45 -7
  103. package/dist/ui.d.ts +25 -4
  104. package/dist/ui.js +77 -167
  105. package/dist/url-safety.js +157 -0
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/documentation/architecture.md +367 -16
  109. package/documentation/auth.md +163 -2
  110. package/documentation/code-mode.md +274 -56
  111. package/documentation/meta-tools.md +200 -44
  112. package/ethos.md +46 -47
  113. package/examples/worker/AGENTS.md +6 -0
  114. package/examples/worker/README.md +224 -14
  115. package/examples/worker/src/d1-activity-row.ts +14 -2
  116. package/examples/worker/src/d1-activity.ts +4 -2
  117. package/examples/worker/src/d1-storage.ts +116 -0
  118. package/examples/worker/src/index.ts +42 -7
  119. package/examples/worker/src/r2-artifact-blobs.ts +29 -0
  120. package/examples/worker/wrangler.jsonc +21 -5
  121. package/package.json +16 -4
  122. package/templates/node/AGENTS.md +9 -2
  123. package/templates/node/README.md +31 -3
  124. package/templates/node/package.json +1 -1
  125. package/templates/node/src/index.ts +27 -0
  126. package/dist/auth/downstream-oauth.d.ts +0 -200
  127. package/dist/catalog-fingerprint.d.ts +0 -19
  128. package/dist/catalog-limits.d.ts +0 -9
  129. package/dist/catalog-service.d.ts +0 -263
  130. package/dist/catalog.d.ts +0 -87
  131. package/dist/concurrency.d.ts +0 -3
  132. package/dist/connectors/guarded-fetch.d.ts +0 -96
  133. package/dist/credential-rules.d.ts +0 -85
  134. package/dist/execute.d.ts +0 -154
  135. package/dist/executor-result.d.ts +0 -10
  136. package/dist/executors/quickjs-child.d.ts +0 -1
  137. package/dist/executors/quickjs-protocol.d.ts +0 -67
  138. package/dist/favicon.d.ts +0 -1
  139. package/dist/identity.d.ts +0 -4
  140. package/dist/invocation.d.ts +0 -74
  141. package/dist/mcp-result.d.ts +0 -8
  142. package/dist/meta-tools.d.ts +0 -129
  143. package/dist/operator-ui/generated.d.ts +0 -2
  144. package/dist/operator-ui/view.d.ts +0 -188
  145. package/dist/result-shapes.d.ts +0 -13
  146. package/dist/retired-toolkits.d.ts +0 -2
  147. package/dist/routes/activity.d.ts +0 -2
  148. package/dist/routes/credentials.d.ts +0 -2
  149. package/dist/routes/mcp.d.ts +0 -10
  150. package/dist/routes/oauth-management.d.ts +0 -2
  151. package/dist/routes/oauth.d.ts +0 -2
  152. package/dist/routes/ui.d.ts +0 -2
  153. package/dist/server.d.ts +0 -9
  154. package/dist/skills.d.ts +0 -44
  155. package/dist/timeout.d.ts +0 -21
package/dist/catalog.js CHANGED
@@ -14,6 +14,12 @@ class SchemaWork {
14
14
  remaining = MAX_SCHEMA_WORK;
15
15
  truncated = false;
16
16
  refs = new Map();
17
+ /**
18
+ * Renderings whose top level is a bare `|` or `&`, recorded as the walk
19
+ * produces them. Compact carries raw keys and `//` prose, so unlike the
20
+ * TypeScript dialect its text cannot be rescanned for a top-level operator.
21
+ */
22
+ ungrouped = new Set();
17
23
  constructor(byteLimit = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
18
24
  this.byteLimit = byteLimit;
19
25
  }
@@ -35,11 +41,29 @@ class SchemaWork {
35
41
  const visit = this.visit.bind(this);
36
42
  const text = this.text.bind(this);
37
43
  const ancestors = [];
44
+ let serializedBytes = 0;
45
+ const addSerialized = (value) => {
46
+ serializedBytes += schemaEncoder.encode(value).length;
47
+ if (serializedBytes > this.byteLimit)
48
+ throw schemaSizeExceeded;
49
+ };
38
50
  return this.text(JSON.stringify(value, function (key, item) {
39
51
  visit();
40
52
  text(key);
41
53
  if (typeof item === "string")
42
54
  text(item);
55
+ const omitted = item === undefined || typeof item === "function" || typeof item === "symbol";
56
+ const root = key === "" && ancestors.length === 0;
57
+ if (!omitted) {
58
+ // Array indexes are implicit in JSON. Object keys and primitive values
59
+ // are the useful lower bound; the final text check remains authoritative
60
+ // for braces, commas, and values whose encoding is larger than this bound.
61
+ if (!root && !Array.isArray(this))
62
+ addSerialized(JSON.stringify(key));
63
+ if (typeof item !== "object" || item === null) {
64
+ addSerialized(JSON.stringify(item));
65
+ }
66
+ }
43
67
  if (item !== null && typeof item === "object") {
44
68
  while (ancestors.length && ancestors[ancestors.length - 1] !== this) {
45
69
  ancestors.pop();
@@ -417,8 +441,11 @@ function renderEnum(values, work, byteLimit, onTruncated) {
417
441
  const full = prefix.join(" | ");
418
442
  if (schemaEncoder.encode(full).length > limit)
419
443
  break;
420
- if (index === values.length - 1)
444
+ if (index === values.length - 1) {
445
+ if (index > 0)
446
+ work.ungrouped.add(full);
421
447
  return full;
448
+ }
422
449
  const omitted = values.length - prefix.length;
423
450
  const candidate = `(${full} | ${marker(omitted)})`;
424
451
  if (schemaEncoder.encode(candidate).length <= limit)
@@ -481,20 +508,131 @@ function renderConstraints(base, schema, byteLimit, onTruncated) {
481
508
  ? base
482
509
  : `${grouped(base)} /* ${kept.join("; ")} */`;
483
510
  }
511
+ /**
512
+ * `grouped` for the TypeScript dialect, whose JSDoc prose and string literals
513
+ * may carry unbalanced brackets from a downstream description: skipping both
514
+ * keeps a stray `)` in prose from hiding a real top-level union.
515
+ */
516
+ function groupedType(part) {
517
+ let nesting = 0;
518
+ for (let i = 0; i < part.length; i += 1) {
519
+ const char = part[i];
520
+ if (part.startsWith("/*", i)) {
521
+ const end = part.indexOf("*/", i + 2);
522
+ if (end < 0)
523
+ break;
524
+ i = end + 1;
525
+ }
526
+ else if (char === '"') {
527
+ for (i += 1; i < part.length && part[i] !== '"'; i += 1) {
528
+ if (part[i] === "\\")
529
+ i += 1;
530
+ }
531
+ }
532
+ else if (char === "{" || char === "(" || char === "[" || char === "<") {
533
+ nesting += 1;
534
+ }
535
+ else if (char === "}" || char === ")" || char === "]" || char === ">") {
536
+ nesting -= 1;
537
+ }
538
+ else if (nesting === 0 &&
539
+ (part.startsWith(" | ", i) || part.startsWith(" & ", i))) {
540
+ return `(${part})`;
541
+ }
542
+ }
543
+ return part;
544
+ }
545
+ const TYPESCRIPT_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
546
+ function typescriptKey(key) {
547
+ return TYPESCRIPT_IDENTIFIER.test(key) ? key : JSON.stringify(key);
548
+ }
549
+ /** One-line JSDoc body; downstream prose can never close the comment early. */
550
+ function typescriptDoc(text) {
551
+ return text.replace(/\s+/g, " ").trim().replaceAll("*/", "*\\/");
552
+ }
553
+ function typescriptPrimitive(type) {
554
+ if (type === "integer")
555
+ return "number";
556
+ if (type === "array")
557
+ return "unknown[]";
558
+ if (type === "object")
559
+ return "Record<string, unknown>";
560
+ return ["string", "number", "boolean", "null"].includes(type)
561
+ ? type
562
+ : "unknown";
563
+ }
564
+ function boundedParts(work, separator, prefix, suffix) {
565
+ // Check each addition before joining so a rejected schema never creates a
566
+ // large intermediate string just to discover that the final result is over.
567
+ const parts = [];
568
+ let bytes = schemaEncoder.encode(prefix).length + schemaEncoder.encode(suffix).length;
569
+ const separatorBytes = schemaEncoder.encode(separator).length;
570
+ return {
571
+ get length() {
572
+ return parts.length;
573
+ },
574
+ add(part) {
575
+ const partBytes = schemaEncoder.encode(part).length;
576
+ const added = partBytes + (parts.length > 0 ? separatorBytes : 0);
577
+ if (bytes + added > work.byteLimit)
578
+ throw schemaSizeExceeded;
579
+ parts.push(part);
580
+ bytes += added;
581
+ },
582
+ finish() {
583
+ return `${prefix}${parts.join(separator)}${suffix}`;
584
+ },
585
+ };
586
+ }
484
587
  function renderSchema(schema, defs, seen, depth, options) {
485
588
  options.work.visit();
486
589
  return options.work.text(renderSchemaNode(schema, defs, seen, depth, options));
487
590
  }
488
591
  function renderSchemaNode(schema, defs, seen, depth, options) {
489
- if (depth > 4)
490
- return "…";
592
+ const ts = options.typescript === true;
593
+ const group = ts ? groupedType : grouped;
594
+ const ungrouped = options.work.ungrouped;
595
+ // A `[]` suffix binds tighter than `|` and `&`, so an element rendered at
596
+ // operator level needs parentheses or it reads as its last member's array.
597
+ const element = (rendered) => ts
598
+ ? groupedType(rendered)
599
+ : ungrouped.has(rendered) ? `(${rendered})` : rendered;
600
+ if (depth > 4) {
601
+ if (!ts)
602
+ return "…";
603
+ options.work.truncated = true;
604
+ return `unknown${COMPACT_DISCOVERY_TRUNCATION}`;
605
+ }
491
606
  if (schema === null || typeof schema !== "object") {
607
+ if (ts)
608
+ return schema === false ? "never" : "unknown";
492
609
  return options.work.json(schema);
493
610
  }
494
611
  const s = schema;
495
- const constrain = (rendered) => options.renderConstraints
496
- ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
497
- : rendered;
612
+ // OpenAPI 3.0's `nullable` has no JSON Schema meaning, so compact ignores
613
+ // it; a TypeScript reader writing `null` checks needs it in the type.
614
+ if (ts && s.nullable === true) {
615
+ const base = Object.create(null);
616
+ for (const key of propertyNames(s, options.work)) {
617
+ if (key !== "nullable")
618
+ base[key] = s[key];
619
+ }
620
+ const rendered = declaresShape(base)
621
+ ? renderSchema(base, defs, seen, depth, options)
622
+ : "unknown";
623
+ return rendered === "unknown" ? rendered : `${rendered} | null`;
624
+ }
625
+ const constrain = (rendered) => {
626
+ if (!options.renderConstraints)
627
+ return rendered;
628
+ const result = renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated);
629
+ // Constraints parenthesize a union before their comment but leave an
630
+ // intersection bare, and a comment does not group what precedes it.
631
+ if (ungrouped.has(rendered) && !result.startsWith(`(${rendered})`)) {
632
+ ungrouped.add(result);
633
+ }
634
+ return result;
635
+ };
498
636
  // Conditions cannot be expressed by a single static shape. Preserve the
499
637
  // base and send callers to the exact schema instead of hiding the rules.
500
638
  if (s.dependentSchemas !== undefined || s.if !== undefined ||
@@ -508,7 +646,10 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
508
646
  const rendered = declaresShape(base)
509
647
  ? renderSchema(base, defs, seen, depth, options)
510
648
  : "unknown";
511
- return `${rendered} /* conditional */`;
649
+ const result = `${rendered} /* conditional */`;
650
+ if (ungrouped.has(rendered))
651
+ ungrouped.add(result);
652
+ return result;
512
653
  }
513
654
  // allOf composes rather than replaces: it is checked before every other
514
655
  // shape keyword, and renders the schema's own shape alongside its members instead
@@ -524,29 +665,44 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
524
665
  if (key !== "allOf")
525
666
  own[key] = s[key];
526
667
  }
527
- const parts = declaresShape(own)
528
- ? [renderSchema(own, defs, seen, depth, options)]
529
- : [];
668
+ const parts = boundedParts(options.work, " & ", "", "");
669
+ const groupParts = declaresShape(own)
670
+ ? s.allOf.length > 0
671
+ : s.allOf.length > 1;
672
+ if (declaresShape(own)) {
673
+ const rendered = renderSchema(own, defs, seen, depth, options);
674
+ parts.add(groupParts ? group(rendered) : rendered);
675
+ }
530
676
  for (const member of s.allOf) {
531
- parts.push(renderSchema(member, defs, seen, depth + 1, options));
677
+ const rendered = renderSchema(member, defs, seen, depth + 1, options);
678
+ parts.add(groupParts ? group(rendered) : rendered);
532
679
  }
533
680
  if (parts.length === 0)
534
681
  return "unknown";
535
- if (parts.length === 1)
536
- return parts[0];
537
- return parts.map(grouped).join(" & ");
682
+ const rendered = parts.finish();
683
+ if (parts.length > 1)
684
+ ungrouped.add(rendered);
685
+ return rendered;
538
686
  }
539
687
  const reference = s.$ref ?? s.$dynamicRef;
540
688
  if (typeof reference === "string") {
541
689
  const dynamic = s.$ref === undefined;
542
690
  const rawName = refName(options.work.text(reference));
543
691
  const name = dynamic ? rawName.replace(/^#/, "") : rawName;
544
- if (seen.has(name))
545
- return name;
692
+ if (seen.has(name)) {
693
+ if (!ts)
694
+ return name;
695
+ // A cycle has no finite TypeScript expansion, and a bare definition
696
+ // name would be an undeclared type: degrade and send callers to JSON.
697
+ options.work.truncated = true;
698
+ return "unknown /* recursive */";
699
+ }
546
700
  const target = resolveDefinition(defs, name);
547
701
  if (target === undefined) {
548
- if (dynamic)
702
+ if (dynamic || ts)
549
703
  options.work.truncated = true;
704
+ if (ts)
705
+ return "unknown /* unresolved */";
550
706
  return dynamic ? "unknown" : name;
551
707
  }
552
708
  const cacheKey = JSON.stringify([name, depth, [...seen]]);
@@ -561,10 +717,13 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
561
717
  }
562
718
  const union = (s.oneOf ?? s.anyOf);
563
719
  if (Array.isArray(union)) {
564
- const rendered = union
565
- .map((u) => renderSchema(u, defs, seen, depth + 1, options))
566
- .join(" | ") ||
567
- "unknown";
720
+ const parts = boundedParts(options.work, " | ", "", "");
721
+ for (const member of union) {
722
+ parts.add(renderSchema(member, defs, seen, depth + 1, options));
723
+ }
724
+ const rendered = parts.finish() || "unknown";
725
+ if (parts.length > 1)
726
+ ungrouped.add(rendered);
568
727
  return constrain(rendered);
569
728
  }
570
729
  if (Array.isArray(s.enum)) {
@@ -580,21 +739,50 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
580
739
  return constrain(rendered);
581
740
  }
582
741
  const type = s.type;
742
+ // A type list renders member by member, so `["object", "null"]` beside
743
+ // `properties` keeps both its shape and its null rather than the object alone.
744
+ if (ts && Array.isArray(type) && type.length > 0) {
745
+ const members = new Set();
746
+ const parts = boundedParts(options.work, " | ", "", "");
747
+ for (const item of type) {
748
+ options.work.visit();
749
+ if (typeof item !== "string")
750
+ continue;
751
+ let rendered = "null";
752
+ if (item !== "null") {
753
+ const member = Object.create(null);
754
+ for (const key of propertyNames(s, options.work)) {
755
+ if (key !== "type")
756
+ member[key] = s[key];
757
+ }
758
+ member.type = item;
759
+ rendered = renderSchema(member, defs, seen, depth, options);
760
+ }
761
+ if (members.has(rendered))
762
+ continue;
763
+ members.add(rendered);
764
+ parts.add(rendered);
765
+ }
766
+ return parts.length === 0 ? "unknown" : parts.finish();
767
+ }
583
768
  if (Array.isArray(s.prefixItems)) {
584
- const parts = s.prefixItems.map((item) => renderSchema(item, defs, seen, depth + 1, options));
769
+ const parts = boundedParts(options.work, ", ", "[", "]");
770
+ for (const item of s.prefixItems) {
771
+ parts.add(renderSchema(item, defs, seen, depth + 1, options));
772
+ }
585
773
  if (s.items !== false) {
586
774
  const rest = s.items === undefined || s.items === true
587
775
  ? "unknown"
588
776
  : renderSchema(s.items, defs, seen, depth + 1, options);
589
- parts.push(`...${grouped(rest)}[]`);
777
+ parts.add(`...${element(rest)}[]`);
590
778
  }
591
- return `[${parts.join(", ")}]`;
779
+ return parts.finish();
592
780
  }
593
781
  if (type === "array" || s.items) {
594
782
  const items = s.items
595
783
  ? renderSchema(s.items, defs, seen, depth + 1, options)
596
784
  : "unknown";
597
- return `${items}[]`;
785
+ return `${element(items)}[]`;
598
786
  }
599
787
  if (type === "object" || s.properties) {
600
788
  const props = (s.properties ?? {});
@@ -606,10 +794,17 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
606
794
  ...declaredKeys.filter((key) => !required.has(key)),
607
795
  ]
608
796
  : declaredKeys;
609
- if (keys.length === 0)
610
- return "{}";
611
- return `{ ${keys
612
- .map((key) => {
797
+ const additional = s.additionalProperties;
798
+ const index = ts && additional !== null && typeof additional === "object"
799
+ ? renderSchema(additional, defs, seen, depth + 1, options)
800
+ : undefined;
801
+ if (keys.length === 0 && index === undefined) {
802
+ // In TypeScript `{}` reads as "empty"; an object with no declared
803
+ // properties is an open map unless it closes itself explicitly.
804
+ return ts && additional !== false ? "Record<string, unknown>" : "{}";
805
+ }
806
+ const parts = boundedParts(options.work, ts ? "; " : ", ", "{ ", " }");
807
+ for (const key of keys) {
613
808
  const optional = required.has(key) ? "" : "?";
614
809
  const rendered = renderSchema(props[key], defs, seen, depth + 1, options);
615
810
  const description = props[key]?.description;
@@ -617,27 +812,42 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
617
812
  options.work.text(description);
618
813
  }
619
814
  options.work.text(key);
815
+ if (ts) {
816
+ const doc = options.propertyDescriptions && typeof description === "string" &&
817
+ description.trim()
818
+ ? `/** ${typescriptDoc(description)} */ `
819
+ : "";
820
+ parts.add(`${doc}${typescriptKey(key)}${optional}: ${rendered}`);
821
+ continue;
822
+ }
620
823
  const comment = options.propertyDescriptions && typeof description === "string"
621
824
  ? ` // ${description}`
622
825
  : "";
623
- return `${key}${optional}: ${rendered}${comment}`;
624
- })
625
- .join(", ")} }`;
826
+ parts.add(`${key}${optional}: ${rendered}${comment}`);
827
+ }
828
+ if (index !== undefined)
829
+ parts.add(`[key: string]: ${index}`);
830
+ return parts.finish();
626
831
  }
627
832
  if (typeof type === "string") {
628
- return constrain(options.work.text(type));
833
+ const text = options.work.text(type);
834
+ return constrain(ts ? typescriptPrimitive(text) : text);
629
835
  }
630
836
  if (Array.isArray(type)) {
631
- const rendered = type.map((item) => {
837
+ const parts = boundedParts(options.work, " | ", "", "");
838
+ for (const item of type) {
632
839
  options.work.visit();
633
- return options.work.text(String(item));
634
- }).join(" | ");
840
+ parts.add(options.work.text(String(item)));
841
+ }
842
+ const rendered = parts.finish();
843
+ if (parts.length > 1)
844
+ ungrouped.add(rendered);
635
845
  return constrain(rendered);
636
846
  }
637
847
  if (options.renderConstraints && constraintEntries(s).length > 0) {
638
848
  return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
639
849
  }
640
- return options.work.json(schema);
850
+ return ts ? "unknown" : options.work.json(schema);
641
851
  }
642
852
  const compactSchemas = new WeakMap();
643
853
  function resolveDefinition(schema, name) {
@@ -713,10 +923,58 @@ export function compactDiscoverySchema(schema) {
713
923
  compactDiscoverySchemas.set(schema, result);
714
924
  return result;
715
925
  }
716
- function boundedCompactSchema(schema, description) {
926
+ const typescriptDiscoveryTypes = new WeakMap();
927
+ const typescriptDescriptionTypes = new WeakMap();
928
+ /**
929
+ * One schema as a TypeScript type, under exactly the compact budgets: search
930
+ * drops prose and orders required keys first within 1,024 bytes, describe
931
+ * keeps prose as JSDoc within 8,192, and both share the 2,000-visit walk.
932
+ */
933
+ function typescriptType(schema, description) {
934
+ const cache = description ? typescriptDescriptionTypes : typescriptDiscoveryTypes;
935
+ const cached = cache.get(schema);
936
+ if (cached)
937
+ return cached;
938
+ const result = boundedCompactSchema(schema, description, true);
939
+ cache.set(schema, result);
940
+ return result;
941
+ }
942
+ /** Marks runtime evidence inside the signature itself, not only beside it. */
943
+ const OBSERVED_OUTPUT_MARKER = "/* observed, not declared */ ";
944
+ /**
945
+ * Render a tool as the function `connecta.call(address, args)` resolves to,
946
+ * for an agent to read — never to execute: code mode still runs JavaScript.
947
+ *
948
+ * The input half comes from the declared input schema. The output half is the
949
+ * declared output schema, else a passively observed shape carrying a leading
950
+ * marker (runtime evidence is never presented as a contract), else `unknown`.
951
+ * A zero-field input renders as the optional `args?: {}` that call accepts:
952
+ * an MCP tool with no parameters rarely closes its schema, and reading its
953
+ * open `Record<string, unknown>` as "pass anything" would invite guessing.
954
+ */
955
+ export function typescriptSignature(input, output, options) {
956
+ const renderedInput = typescriptType(input, options.description);
957
+ const renderedOutput = output
958
+ ? typescriptType(output, options.description)
959
+ : undefined;
960
+ const args = renderedInput.text === "{}" ||
961
+ renderedInput.text === "Record<string, unknown>"
962
+ ? "args?: {}"
963
+ : `args: ${renderedInput.text}`;
964
+ const result = renderedOutput
965
+ ? `${options.observed ? OBSERVED_OUTPUT_MARKER : ""}${renderedOutput.text}`
966
+ : "unknown";
967
+ return {
968
+ text: `(${args}) => Promise<${result}>`,
969
+ inputTruncated: renderedInput.truncated,
970
+ outputTruncated: renderedOutput?.truncated === true,
971
+ };
972
+ }
973
+ function boundedCompactSchema(schema, description, typescript = false) {
717
974
  const work = new SchemaWork(description ? MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES : MAX_COMPACT_DISCOVERY_SCHEMA_BYTES);
718
975
  const options = {
719
976
  work,
977
+ typescript,
720
978
  propertyDescriptions: description,
721
979
  requiredFirst: !description,
722
980
  enumByteLimit: description ? work.byteLimit : MAX_COMPACT_DISCOVERY_ENUM_BYTES,
@@ -734,6 +992,7 @@ function boundedCompactSchema(schema, description) {
734
992
  // are memoized only within each pass because their text includes constraints.
735
993
  if (error === schemaSizeExceeded && !description) {
736
994
  work.refs.clear();
995
+ work.ungrouped.clear();
737
996
  try {
738
997
  return {
739
998
  text: renderSchema(schema, schema, new Set(), 0, {
@@ -1,30 +1,31 @@
1
+ import { Effect } from "effect";
2
+ import { runEdge } from "./runtime/run.js";
1
3
  const DEFAULT_DISCOVERY_CONCURRENCY = 4;
2
4
  export function resolveDiscoveryConcurrency(value) {
3
5
  return value !== undefined && Number.isInteger(value) && value > 0
4
6
  ? value
5
7
  : DEFAULT_DISCOVERY_CONCURRENCY;
6
8
  }
7
- /** Run `fn` over `items` with at most `limit` operations in flight. */
8
- export async function mapSettledWithConcurrency(items, limit, fn) {
9
- const settled = Array(items.length);
10
- const remaining = items.entries();
11
- const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
12
- for (;;) {
13
- const next = remaining.next();
14
- if (next.done)
15
- return;
16
- const [index, item] = next.value;
17
- try {
18
- settled[index] = {
19
- status: "fulfilled",
20
- value: await fn(item, index),
21
- };
22
- }
23
- catch (reason) {
24
- settled[index] = { status: "rejected", reason };
25
- }
26
- }
27
- });
28
- await Promise.all(workers);
29
- return settled;
9
+ /**
10
+ * Run `fn` over `items` with at most `limit` operations in flight, settling
11
+ * every one: a rejection lands in its slot instead of cancelling the rest.
12
+ * Results keep input order whatever order the work finishes in. A limit below
13
+ * one runs the items one at a time.
14
+ */
15
+ export function mapSettledWithConcurrency(items, limit, fn) {
16
+ return runEdge(Effect.forEach(items, (item, index) => Effect.tryPromise({
17
+ // Promise.resolve keeps a non-promise return from `fn` working, as
18
+ // `await` did; a synchronous throw is caught either way.
19
+ try: () => Promise.resolve(fn(item, index)),
20
+ catch: (reason) => reason,
21
+ }).pipe(Effect.match({
22
+ onSuccess: (value) => ({
23
+ status: "fulfilled",
24
+ value,
25
+ }),
26
+ onFailure: (reason) => ({
27
+ status: "rejected",
28
+ reason,
29
+ }),
30
+ })), { concurrency: limit >= 1 ? Math.floor(limit) : 1 }));
30
31
  }
@@ -9,20 +9,33 @@ export declare const POOL_NAME_RE: RegExp;
9
9
  /**
10
10
  * One derived view: which connectors, and for connectors granted by address
11
11
  * only, which tools. A connector absent from `toolAccess` is visible whole.
12
+ * `guardedToolAccess` is a subset of `toolAccess` checked against the loaded
13
+ * catalog each time tools are discovered or invoked.
12
14
  */
13
15
  export interface ConnectorAccess {
14
16
  connectorIds: "all" | readonly string[];
15
17
  toolAccess?: ToolAccess;
18
+ /** Exact tool grants that also require a current explicit read-only annotation. */
19
+ guardedToolAccess?: ToolAccess;
16
20
  }
21
+ interface ReadOnlyToolGrant {
22
+ tool: string;
23
+ requireReadOnly: true;
24
+ }
25
+ export type ConnectorGrant = string | ReadOnlyToolGrant;
17
26
  /**
18
27
  * Normalize a grant list. A bare connector id grants every tool on that
19
28
  * connector; a `connector.tool` address grants one tool. Grants are additive,
20
29
  * so a bare id beside addresses for the same connector means the whole
21
- * connector. Anything else — an unknown shape, an empty tool name, a
22
- * non-string — throws, and the caller decides whether that is a construction
23
- * failure or a 403: a grant that cannot be parsed must never fail open.
30
+ * connector. Identity resolution can opt into `{ tool, requireReadOnly: true }`
31
+ * for a fixed address that also needs a current explicit read-only annotation;
32
+ * pool declarations remain strings. Anything else throws, and the caller
33
+ * decides whether that is a construction failure or a 403: a grant that
34
+ * cannot be parsed must never fail open.
24
35
  */
25
- export declare function parseConnectorAccess(value: unknown): ConnectorAccess;
36
+ export declare function parseConnectorAccess(value: unknown, options?: {
37
+ allowReadOnly?: boolean;
38
+ }): ConnectorAccess;
26
39
  /**
27
40
  * The view a pool endpoint serves: the pool's grants, never wider than the
28
41
  * identity's own. A connector or tool outside either side is gone; a
@@ -30,3 +43,4 @@ export declare function parseConnectorAccess(value: unknown): ConnectorAccess;
30
43
  * only narrow what the identity resolver already allowed.
31
44
  */
32
45
  export declare function intersectAccess(ceiling: ConnectorAccess, pool: ConnectorAccess): ConnectorAccess;
46
+ export {};