@zackbart/connecta 0.24.4 → 0.25.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 (107) hide show
  1. package/AGENTS.md +40 -10
  2. package/CHANGELOG.md +253 -0
  3. package/dist/advertised-schema.js +53 -0
  4. package/dist/auth/downstream-oauth.js +490 -106
  5. package/dist/call-admission.js +139 -113
  6. package/dist/catalog-service.js +141 -105
  7. package/dist/catalog.js +258 -33
  8. package/dist/concurrency.js +24 -23
  9. package/dist/connector-scope.js +6 -37
  10. package/dist/connectors/api.d.ts +2 -2
  11. package/dist/connectors/remote-mcp.d.ts +6 -5
  12. package/dist/connectors/remote-mcp.js +317 -241
  13. package/dist/credential-contract.d.ts +9 -0
  14. package/dist/credential-rules.js +2 -2
  15. package/dist/credentials.d.ts +6 -1
  16. package/dist/credentials.js +45 -3
  17. package/dist/execute.js +363 -257
  18. package/dist/executor-admission.js +67 -50
  19. package/dist/executors/quickjs-runtime.js +4 -2
  20. package/dist/executors/quickjs.js +251 -210
  21. package/dist/fix-prompt.js +37 -0
  22. package/dist/index.d.ts +10 -1
  23. package/dist/index.js +51 -20
  24. package/dist/invocation.js +324 -285
  25. package/dist/meta-tools.js +206 -92
  26. package/dist/oauth-callback-outcome.js +94 -0
  27. package/dist/oauth-sealing.js +29 -0
  28. package/dist/operator-ui/fix-prompts.js +87 -0
  29. package/dist/operator-ui/generated.js +2 -2
  30. package/dist/operator-ui/model.d.ts +46 -1
  31. package/dist/operator-ui/setup-commands.js +53 -0
  32. package/dist/operator-ui/view.js +38 -2
  33. package/dist/program-source.js +205 -0
  34. package/dist/providers/notion.js +2 -2
  35. package/dist/providers/revenuecat.js +32 -5
  36. package/dist/providers/stripe.d.ts +3 -2
  37. package/dist/providers/vercel.js +1 -1
  38. package/dist/registry.d.ts +1 -1
  39. package/dist/registry.js +305 -260
  40. package/dist/routes/activity.js +120 -137
  41. package/dist/routes/credentials.js +174 -163
  42. package/dist/routes/mcp.js +129 -70
  43. package/dist/routes/oauth-management.js +60 -71
  44. package/dist/routes/oauth.js +44 -16
  45. package/dist/routes/operator.js +82 -0
  46. package/dist/routes/shared.d.ts +2 -0
  47. package/dist/routes/ui-data.js +210 -0
  48. package/dist/routes/ui.js +64 -32
  49. package/dist/runtime/admission.js +43 -0
  50. package/dist/runtime/call-admission.js +67 -0
  51. package/dist/runtime/connector-scope.js +72 -0
  52. package/dist/runtime/run.js +174 -0
  53. package/dist/runtime/services.js +130 -0
  54. package/dist/runtime/storage.js +66 -0
  55. package/dist/server.js +111 -89
  56. package/dist/skills.js +1 -1
  57. package/dist/storage/file.js +39 -2
  58. package/dist/storage/memory.js +28 -16
  59. package/dist/timeout.js +17 -47
  60. package/dist/types.d.ts +27 -7
  61. package/dist/ui.d.ts +16 -2
  62. package/dist/ui.js +50 -166
  63. package/dist/url-safety.js +157 -0
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/documentation/architecture.md +184 -10
  67. package/documentation/auth.md +80 -0
  68. package/documentation/code-mode.md +69 -24
  69. package/documentation/meta-tools.md +164 -27
  70. package/ethos.md +44 -47
  71. package/examples/worker/README.md +51 -5
  72. package/examples/worker/src/d1-storage.ts +116 -0
  73. package/examples/worker/src/index.ts +6 -6
  74. package/examples/worker/wrangler.jsonc +16 -4
  75. package/package.json +11 -3
  76. package/templates/node/package.json +1 -1
  77. package/dist/auth/downstream-oauth.d.ts +0 -200
  78. package/dist/catalog-fingerprint.d.ts +0 -19
  79. package/dist/catalog-limits.d.ts +0 -9
  80. package/dist/catalog-service.d.ts +0 -263
  81. package/dist/catalog.d.ts +0 -87
  82. package/dist/concurrency.d.ts +0 -3
  83. package/dist/connectors/guarded-fetch.d.ts +0 -96
  84. package/dist/credential-rules.d.ts +0 -85
  85. package/dist/execute.d.ts +0 -154
  86. package/dist/executor-result.d.ts +0 -10
  87. package/dist/executors/quickjs-child.d.ts +0 -1
  88. package/dist/executors/quickjs-protocol.d.ts +0 -67
  89. package/dist/favicon.d.ts +0 -1
  90. package/dist/identity.d.ts +0 -4
  91. package/dist/invocation.d.ts +0 -74
  92. package/dist/mcp-result.d.ts +0 -8
  93. package/dist/meta-tools.d.ts +0 -129
  94. package/dist/operator-ui/generated.d.ts +0 -2
  95. package/dist/operator-ui/view.d.ts +0 -188
  96. package/dist/result-shapes.d.ts +0 -13
  97. package/dist/retired-toolkits.d.ts +0 -2
  98. package/dist/routes/activity.d.ts +0 -2
  99. package/dist/routes/credentials.d.ts +0 -2
  100. package/dist/routes/mcp.d.ts +0 -10
  101. package/dist/routes/oauth-management.d.ts +0 -2
  102. package/dist/routes/oauth.d.ts +0 -2
  103. package/dist/routes/ui.d.ts +0 -2
  104. package/dist/server.d.ts +0 -9
  105. package/dist/skills.d.ts +0 -44
  106. package/dist/timeout.d.ts +0 -21
  107. package/dist/tool-safety.d.ts +0 -9
package/dist/catalog.js CHANGED
@@ -35,11 +35,29 @@ class SchemaWork {
35
35
  const visit = this.visit.bind(this);
36
36
  const text = this.text.bind(this);
37
37
  const ancestors = [];
38
+ let serializedBytes = 0;
39
+ const addSerialized = (value) => {
40
+ serializedBytes += schemaEncoder.encode(value).length;
41
+ if (serializedBytes > this.byteLimit)
42
+ throw schemaSizeExceeded;
43
+ };
38
44
  return this.text(JSON.stringify(value, function (key, item) {
39
45
  visit();
40
46
  text(key);
41
47
  if (typeof item === "string")
42
48
  text(item);
49
+ const omitted = item === undefined || typeof item === "function" || typeof item === "symbol";
50
+ const root = key === "" && ancestors.length === 0;
51
+ if (!omitted) {
52
+ // Array indexes are implicit in JSON. Object keys and primitive values
53
+ // are the useful lower bound; the final text check remains authoritative
54
+ // for braces, commas, and values whose encoding is larger than this bound.
55
+ if (!root && !Array.isArray(this))
56
+ addSerialized(JSON.stringify(key));
57
+ if (typeof item !== "object" || item === null) {
58
+ addSerialized(JSON.stringify(item));
59
+ }
60
+ }
43
61
  if (item !== null && typeof item === "object") {
44
62
  while (ancestors.length && ancestors[ancestors.length - 1] !== this) {
45
63
  ancestors.pop();
@@ -481,17 +499,114 @@ function renderConstraints(base, schema, byteLimit, onTruncated) {
481
499
  ? base
482
500
  : `${grouped(base)} /* ${kept.join("; ")} */`;
483
501
  }
502
+ /**
503
+ * `grouped` for the TypeScript dialect, whose JSDoc prose and string literals
504
+ * may carry unbalanced brackets from a downstream description: skipping both
505
+ * keeps a stray `)` in prose from hiding a real top-level union.
506
+ */
507
+ function groupedType(part) {
508
+ let nesting = 0;
509
+ for (let i = 0; i < part.length; i += 1) {
510
+ const char = part[i];
511
+ if (part.startsWith("/*", i)) {
512
+ const end = part.indexOf("*/", i + 2);
513
+ if (end < 0)
514
+ break;
515
+ i = end + 1;
516
+ }
517
+ else if (char === '"') {
518
+ for (i += 1; i < part.length && part[i] !== '"'; i += 1) {
519
+ if (part[i] === "\\")
520
+ i += 1;
521
+ }
522
+ }
523
+ else if (char === "{" || char === "(" || char === "[" || char === "<") {
524
+ nesting += 1;
525
+ }
526
+ else if (char === "}" || char === ")" || char === "]" || char === ">") {
527
+ nesting -= 1;
528
+ }
529
+ else if (nesting === 0 &&
530
+ (part.startsWith(" | ", i) || part.startsWith(" & ", i))) {
531
+ return `(${part})`;
532
+ }
533
+ }
534
+ return part;
535
+ }
536
+ const TYPESCRIPT_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
537
+ function typescriptKey(key) {
538
+ return TYPESCRIPT_IDENTIFIER.test(key) ? key : JSON.stringify(key);
539
+ }
540
+ /** One-line JSDoc body; downstream prose can never close the comment early. */
541
+ function typescriptDoc(text) {
542
+ return text.replace(/\s+/g, " ").trim().replaceAll("*/", "*\\/");
543
+ }
544
+ function typescriptPrimitive(type) {
545
+ if (type === "integer")
546
+ return "number";
547
+ if (type === "array")
548
+ return "unknown[]";
549
+ if (type === "object")
550
+ return "Record<string, unknown>";
551
+ return ["string", "number", "boolean", "null"].includes(type)
552
+ ? type
553
+ : "unknown";
554
+ }
555
+ function boundedParts(work, separator, prefix, suffix) {
556
+ // Check each addition before joining so a rejected schema never creates a
557
+ // large intermediate string just to discover that the final result is over.
558
+ const parts = [];
559
+ let bytes = schemaEncoder.encode(prefix).length + schemaEncoder.encode(suffix).length;
560
+ const separatorBytes = schemaEncoder.encode(separator).length;
561
+ return {
562
+ get length() {
563
+ return parts.length;
564
+ },
565
+ add(part) {
566
+ const partBytes = schemaEncoder.encode(part).length;
567
+ const added = partBytes + (parts.length > 0 ? separatorBytes : 0);
568
+ if (bytes + added > work.byteLimit)
569
+ throw schemaSizeExceeded;
570
+ parts.push(part);
571
+ bytes += added;
572
+ },
573
+ finish() {
574
+ return `${prefix}${parts.join(separator)}${suffix}`;
575
+ },
576
+ };
577
+ }
484
578
  function renderSchema(schema, defs, seen, depth, options) {
485
579
  options.work.visit();
486
580
  return options.work.text(renderSchemaNode(schema, defs, seen, depth, options));
487
581
  }
488
582
  function renderSchemaNode(schema, defs, seen, depth, options) {
489
- if (depth > 4)
490
- return "…";
583
+ const ts = options.typescript === true;
584
+ const group = ts ? groupedType : grouped;
585
+ if (depth > 4) {
586
+ if (!ts)
587
+ return "…";
588
+ options.work.truncated = true;
589
+ return `unknown${COMPACT_DISCOVERY_TRUNCATION}`;
590
+ }
491
591
  if (schema === null || typeof schema !== "object") {
592
+ if (ts)
593
+ return schema === false ? "never" : "unknown";
492
594
  return options.work.json(schema);
493
595
  }
494
596
  const s = schema;
597
+ // OpenAPI 3.0's `nullable` has no JSON Schema meaning, so compact ignores
598
+ // it; a TypeScript reader writing `null` checks needs it in the type.
599
+ if (ts && s.nullable === true) {
600
+ const base = Object.create(null);
601
+ for (const key of propertyNames(s, options.work)) {
602
+ if (key !== "nullable")
603
+ base[key] = s[key];
604
+ }
605
+ const rendered = declaresShape(base)
606
+ ? renderSchema(base, defs, seen, depth, options)
607
+ : "unknown";
608
+ return rendered === "unknown" ? rendered : `${rendered} | null`;
609
+ }
495
610
  const constrain = (rendered) => options.renderConstraints
496
611
  ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
497
612
  : rendered;
@@ -524,29 +639,41 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
524
639
  if (key !== "allOf")
525
640
  own[key] = s[key];
526
641
  }
527
- const parts = declaresShape(own)
528
- ? [renderSchema(own, defs, seen, depth, options)]
529
- : [];
642
+ const parts = boundedParts(options.work, " & ", "", "");
643
+ const groupParts = declaresShape(own)
644
+ ? s.allOf.length > 0
645
+ : s.allOf.length > 1;
646
+ if (declaresShape(own)) {
647
+ const rendered = renderSchema(own, defs, seen, depth, options);
648
+ parts.add(groupParts ? group(rendered) : rendered);
649
+ }
530
650
  for (const member of s.allOf) {
531
- parts.push(renderSchema(member, defs, seen, depth + 1, options));
651
+ const rendered = renderSchema(member, defs, seen, depth + 1, options);
652
+ parts.add(groupParts ? group(rendered) : rendered);
532
653
  }
533
654
  if (parts.length === 0)
534
655
  return "unknown";
535
- if (parts.length === 1)
536
- return parts[0];
537
- return parts.map(grouped).join(" & ");
656
+ return parts.finish();
538
657
  }
539
658
  const reference = s.$ref ?? s.$dynamicRef;
540
659
  if (typeof reference === "string") {
541
660
  const dynamic = s.$ref === undefined;
542
661
  const rawName = refName(options.work.text(reference));
543
662
  const name = dynamic ? rawName.replace(/^#/, "") : rawName;
544
- if (seen.has(name))
545
- return name;
663
+ if (seen.has(name)) {
664
+ if (!ts)
665
+ return name;
666
+ // A cycle has no finite TypeScript expansion, and a bare definition
667
+ // name would be an undeclared type: degrade and send callers to JSON.
668
+ options.work.truncated = true;
669
+ return "unknown /* recursive */";
670
+ }
546
671
  const target = resolveDefinition(defs, name);
547
672
  if (target === undefined) {
548
- if (dynamic)
673
+ if (dynamic || ts)
549
674
  options.work.truncated = true;
675
+ if (ts)
676
+ return "unknown /* unresolved */";
550
677
  return dynamic ? "unknown" : name;
551
678
  }
552
679
  const cacheKey = JSON.stringify([name, depth, [...seen]]);
@@ -561,10 +688,11 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
561
688
  }
562
689
  const union = (s.oneOf ?? s.anyOf);
563
690
  if (Array.isArray(union)) {
564
- const rendered = union
565
- .map((u) => renderSchema(u, defs, seen, depth + 1, options))
566
- .join(" | ") ||
567
- "unknown";
691
+ const parts = boundedParts(options.work, " | ", "", "");
692
+ for (const member of union) {
693
+ parts.add(renderSchema(member, defs, seen, depth + 1, options));
694
+ }
695
+ const rendered = parts.finish() || "unknown";
568
696
  return constrain(rendered);
569
697
  }
570
698
  if (Array.isArray(s.enum)) {
@@ -580,21 +708,50 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
580
708
  return constrain(rendered);
581
709
  }
582
710
  const type = s.type;
711
+ // A type list renders member by member, so `["object", "null"]` beside
712
+ // `properties` keeps both its shape and its null rather than the object alone.
713
+ if (ts && Array.isArray(type) && type.length > 0) {
714
+ const members = new Set();
715
+ const parts = boundedParts(options.work, " | ", "", "");
716
+ for (const item of type) {
717
+ options.work.visit();
718
+ if (typeof item !== "string")
719
+ continue;
720
+ let rendered = "null";
721
+ if (item !== "null") {
722
+ const member = Object.create(null);
723
+ for (const key of propertyNames(s, options.work)) {
724
+ if (key !== "type")
725
+ member[key] = s[key];
726
+ }
727
+ member.type = item;
728
+ rendered = renderSchema(member, defs, seen, depth, options);
729
+ }
730
+ if (members.has(rendered))
731
+ continue;
732
+ members.add(rendered);
733
+ parts.add(rendered);
734
+ }
735
+ return parts.length === 0 ? "unknown" : parts.finish();
736
+ }
583
737
  if (Array.isArray(s.prefixItems)) {
584
- const parts = s.prefixItems.map((item) => renderSchema(item, defs, seen, depth + 1, options));
738
+ const parts = boundedParts(options.work, ", ", "[", "]");
739
+ for (const item of s.prefixItems) {
740
+ parts.add(renderSchema(item, defs, seen, depth + 1, options));
741
+ }
585
742
  if (s.items !== false) {
586
743
  const rest = s.items === undefined || s.items === true
587
744
  ? "unknown"
588
745
  : renderSchema(s.items, defs, seen, depth + 1, options);
589
- parts.push(`...${grouped(rest)}[]`);
746
+ parts.add(`...${group(rest)}[]`);
590
747
  }
591
- return `[${parts.join(", ")}]`;
748
+ return parts.finish();
592
749
  }
593
750
  if (type === "array" || s.items) {
594
751
  const items = s.items
595
752
  ? renderSchema(s.items, defs, seen, depth + 1, options)
596
753
  : "unknown";
597
- return `${items}[]`;
754
+ return ts ? `${groupedType(items)}[]` : `${items}[]`;
598
755
  }
599
756
  if (type === "object" || s.properties) {
600
757
  const props = (s.properties ?? {});
@@ -606,10 +763,17 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
606
763
  ...declaredKeys.filter((key) => !required.has(key)),
607
764
  ]
608
765
  : declaredKeys;
609
- if (keys.length === 0)
610
- return "{}";
611
- return `{ ${keys
612
- .map((key) => {
766
+ const additional = s.additionalProperties;
767
+ const index = ts && additional !== null && typeof additional === "object"
768
+ ? renderSchema(additional, defs, seen, depth + 1, options)
769
+ : undefined;
770
+ if (keys.length === 0 && index === undefined) {
771
+ // In TypeScript `{}` reads as "empty"; an object with no declared
772
+ // properties is an open map unless it closes itself explicitly.
773
+ return ts && additional !== false ? "Record<string, unknown>" : "{}";
774
+ }
775
+ const parts = boundedParts(options.work, ts ? "; " : ", ", "{ ", " }");
776
+ for (const key of keys) {
613
777
  const optional = required.has(key) ? "" : "?";
614
778
  const rendered = renderSchema(props[key], defs, seen, depth + 1, options);
615
779
  const description = props[key]?.description;
@@ -617,27 +781,40 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
617
781
  options.work.text(description);
618
782
  }
619
783
  options.work.text(key);
784
+ if (ts) {
785
+ const doc = options.propertyDescriptions && typeof description === "string" &&
786
+ description.trim()
787
+ ? `/** ${typescriptDoc(description)} */ `
788
+ : "";
789
+ parts.add(`${doc}${typescriptKey(key)}${optional}: ${rendered}`);
790
+ continue;
791
+ }
620
792
  const comment = options.propertyDescriptions && typeof description === "string"
621
793
  ? ` // ${description}`
622
794
  : "";
623
- return `${key}${optional}: ${rendered}${comment}`;
624
- })
625
- .join(", ")} }`;
795
+ parts.add(`${key}${optional}: ${rendered}${comment}`);
796
+ }
797
+ if (index !== undefined)
798
+ parts.add(`[key: string]: ${index}`);
799
+ return parts.finish();
626
800
  }
627
801
  if (typeof type === "string") {
628
- return constrain(options.work.text(type));
802
+ const text = options.work.text(type);
803
+ return constrain(ts ? typescriptPrimitive(text) : text);
629
804
  }
630
805
  if (Array.isArray(type)) {
631
- const rendered = type.map((item) => {
806
+ const parts = boundedParts(options.work, " | ", "", "");
807
+ for (const item of type) {
632
808
  options.work.visit();
633
- return options.work.text(String(item));
634
- }).join(" | ");
809
+ parts.add(options.work.text(String(item)));
810
+ }
811
+ const rendered = parts.finish();
635
812
  return constrain(rendered);
636
813
  }
637
814
  if (options.renderConstraints && constraintEntries(s).length > 0) {
638
815
  return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
639
816
  }
640
- return options.work.json(schema);
817
+ return ts ? "unknown" : options.work.json(schema);
641
818
  }
642
819
  const compactSchemas = new WeakMap();
643
820
  function resolveDefinition(schema, name) {
@@ -713,10 +890,58 @@ export function compactDiscoverySchema(schema) {
713
890
  compactDiscoverySchemas.set(schema, result);
714
891
  return result;
715
892
  }
716
- function boundedCompactSchema(schema, description) {
893
+ const typescriptDiscoveryTypes = new WeakMap();
894
+ const typescriptDescriptionTypes = new WeakMap();
895
+ /**
896
+ * One schema as a TypeScript type, under exactly the compact budgets: search
897
+ * drops prose and orders required keys first within 1,024 bytes, describe
898
+ * keeps prose as JSDoc within 8,192, and both share the 2,000-visit walk.
899
+ */
900
+ function typescriptType(schema, description) {
901
+ const cache = description ? typescriptDescriptionTypes : typescriptDiscoveryTypes;
902
+ const cached = cache.get(schema);
903
+ if (cached)
904
+ return cached;
905
+ const result = boundedCompactSchema(schema, description, true);
906
+ cache.set(schema, result);
907
+ return result;
908
+ }
909
+ /** Marks runtime evidence inside the signature itself, not only beside it. */
910
+ const OBSERVED_OUTPUT_MARKER = "/* observed, not declared */ ";
911
+ /**
912
+ * Render a tool as the function `connecta.call(address, args)` resolves to,
913
+ * for an agent to read — never to execute: code mode still runs JavaScript.
914
+ *
915
+ * The input half comes from the declared input schema. The output half is the
916
+ * declared output schema, else a passively observed shape carrying a leading
917
+ * marker (runtime evidence is never presented as a contract), else `unknown`.
918
+ * A zero-field input renders as the optional `args?: {}` that call accepts:
919
+ * an MCP tool with no parameters rarely closes its schema, and reading its
920
+ * open `Record<string, unknown>` as "pass anything" would invite guessing.
921
+ */
922
+ export function typescriptSignature(input, output, options) {
923
+ const renderedInput = typescriptType(input, options.description);
924
+ const renderedOutput = output
925
+ ? typescriptType(output, options.description)
926
+ : undefined;
927
+ const args = renderedInput.text === "{}" ||
928
+ renderedInput.text === "Record<string, unknown>"
929
+ ? "args?: {}"
930
+ : `args: ${renderedInput.text}`;
931
+ const result = renderedOutput
932
+ ? `${options.observed ? OBSERVED_OUTPUT_MARKER : ""}${renderedOutput.text}`
933
+ : "unknown";
934
+ return {
935
+ text: `(${args}) => Promise<${result}>`,
936
+ inputTruncated: renderedInput.truncated,
937
+ outputTruncated: renderedOutput?.truncated === true,
938
+ };
939
+ }
940
+ function boundedCompactSchema(schema, description, typescript = false) {
717
941
  const work = new SchemaWork(description ? MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES : MAX_COMPACT_DISCOVERY_SCHEMA_BYTES);
718
942
  const options = {
719
943
  work,
944
+ typescript,
720
945
  propertyDescriptions: description,
721
946
  requiredFirst: !description,
722
947
  enumByteLimit: description ? work.byteLimit : MAX_COMPACT_DISCOVERY_ENUM_BYTES,
@@ -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
  }
@@ -1,19 +1,5 @@
1
- import { sleep } from "./timeout.js";
2
- /** Enough for local transport abort/close without letting cleanup own latency. */
3
- const CONNECTOR_SCOPE_CLOSE_BUDGET_MS = 100;
4
- /**
5
- * Bound on cleanup continued after the caller-facing window expires.
6
- *
7
- * `remoteMcp` spends at most one second asking the downstream to terminate its
8
- * session, leaving another second for the local close. Custom hooks still get a
9
- * finite background window: handing a never-settling promise to a Worker's
10
- * `waitUntil` would otherwise keep the invocation alive until the platform cap.
11
- */
12
- const CONNECTOR_SCOPE_DEFER_BUDGET_MS = 2_000;
13
- /** Resolve when `work` settles or `budgetMs` expires; never reject. */
14
- function waitAtMost(work, budgetMs) {
15
- return Promise.race([work, sleep(budgetMs)]).then(() => { });
16
- }
1
+ import { closeScope } from "./runtime/connector-scope.js";
2
+ import { runEdge } from "./runtime/run.js";
17
3
  /**
18
4
  * Tell a connector that a scope owned by the core has ended.
19
5
  *
@@ -25,25 +11,8 @@ function waitAtMost(work, budgetMs) {
25
11
  * window. Callers own the at-most-once guarantee and must not use the scope
26
12
  * again after this returns.
27
13
  */
28
- export async function closeConnectorScope(connector, ctx, defer) {
29
- try {
30
- const closing = connector.closeScope?.(ctx);
31
- if (!closing)
32
- return;
33
- // Attach both handlers before either timer can win, so a late rejection is
34
- // consumed rather than becoming an unhandled rejection.
35
- const settled = closing.then(() => { }, () => { });
36
- if (defer) {
37
- try {
38
- defer(waitAtMost(settled, CONNECTOR_SCOPE_DEFER_BUDGET_MS));
39
- }
40
- catch {
41
- // A runtime hook is best-effort too; the caller cap still applies.
42
- }
43
- }
44
- await waitAtMost(settled, CONNECTOR_SCOPE_CLOSE_BUDGET_MS);
45
- }
46
- catch {
47
- // The scope is over whether or not the connector managed to clean it up.
48
- }
14
+ export function closeConnectorScope(connector, ctx, defer) {
15
+ // The Promise face of closeScope (src/runtime/connector-scope.ts), which
16
+ // owns the budgets; an Effect caller registers it as a Scope finalizer.
17
+ return runEdge(closeScope(connector, ctx, defer));
49
18
  }
@@ -52,9 +52,9 @@ export interface ApiOptions {
52
52
  * discovery metadata. See `Connector.usageGuide`.
53
53
  */
54
54
  usageGuide?: string | ConnectorUsageGuide;
55
- /** Optional operator-managed credential exposed through ctx.credential and /credentials. */
55
+ /** Optional operator-managed credential exposed through ctx.credential and the connection in the operator UI. */
56
56
  credential?: ConnectorCredentialConfig;
57
- /** Optional validation behind /credentials' Test action. */
57
+ /** Optional validation behind the connection's Test action in the operator UI. */
58
58
  testCredential?: (value: string, ctx: ConnectorContext) => Promise<CredentialTestResult>;
59
59
  /** Optional validation for named multi-field credentials. */
60
60
  testCredentials?: (values: ConnectorCredentialValues, ctx: ConnectorContext) => Promise<CredentialTestResult>;
@@ -2,8 +2,8 @@ import type { FetchLike, Transport } from "@modelcontextprotocol/client";
2
2
  import { ConnectorCallError } from "../errors.js";
3
3
  import type { Connector, ConnectorCallAdmissionPolicy, ConnectorContext, ConnectorCredentialConfig, ConnectorUsageGuide, Logger } from "../types.js";
4
4
  /**
5
- * A static downstream credential the operator supplies at `/credentials`
6
- * rather than the deployment baking into its source.
5
+ * A static downstream credential supplied through the connection in the
6
+ * operator UI rather than baked into deployment source.
7
7
  *
8
8
  * The connector, its endpoint, and the credential *slot* stay declared in
9
9
  * code; only the secret arrives through the operator route, exactly as for
@@ -15,8 +15,8 @@ import type { Connector, ConnectorCallAdmissionPolicy, ConnectorContext, Connect
15
15
  interface RemoteMcpCredentialAuth {
16
16
  type: "credential";
17
17
  /**
18
- * Operator-facing slot description rendered on `/credentials`. Defaults to
19
- * `{ label: "API key" }`; a maintained provider passes the name the provider
18
+ * Slot description rendered in the connection in the operator UI. Defaults
19
+ * to `{ label: "API key" }`; a maintained provider passes the name the provider
20
20
  * itself uses. Named `fields` are refused — this shape reads the reserved
21
21
  * `value` field only.
22
22
  */
@@ -122,7 +122,8 @@ export declare class RemoteMcpRedirectError extends ConnectorCallError {
122
122
  * Wrap fetch with explicit, bounded redirect handling.
123
123
  *
124
124
  * The starting URL of each fetch call is trusted by its caller (the configured
125
- * MCP endpoint, or an OAuth URL discovered by the pinned SDK). Only Location
125
+ * MCP endpoint, or an OAuth URL the pinned SDK discovered and
126
+ * `learnedUrlSafeFetch` already admitted). Only Location
126
127
  * values are policy-controlled here. No rejected target is ever fetched, so
127
128
  * arbitrary static header names receive the same protection as Authorization.
128
129
  */