@zackbart/connecta 0.18.2 → 0.19.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 (75) hide show
  1. package/CHANGELOG.md +100 -4
  2. package/README.md +4 -0
  3. package/dist/catalog-service.d.ts +20 -13
  4. package/dist/catalog-service.js +123 -116
  5. package/dist/catalog.js +29 -46
  6. package/dist/connector-scope.js +2 -7
  7. package/dist/connectors/api.d.ts +4 -16
  8. package/dist/connectors/api.js +19 -46
  9. package/dist/connectors/guarded-fetch.d.ts +9 -23
  10. package/dist/connectors/guarded-fetch.js +38 -76
  11. package/dist/connectors/remote-mcp.js +36 -79
  12. package/dist/errors.d.ts +6 -27
  13. package/dist/errors.js +8 -5
  14. package/dist/execute.d.ts +24 -22
  15. package/dist/execute.js +98 -145
  16. package/dist/executor-result.d.ts +1 -0
  17. package/dist/executor-result.js +4 -11
  18. package/dist/executors/quickjs-child.js +1 -3
  19. package/dist/executors/quickjs-runtime.js +1 -3
  20. package/dist/executors/quickjs.js +1 -3
  21. package/dist/index.js +27 -57
  22. package/dist/invocation.js +114 -178
  23. package/dist/meta-tools.d.ts +15 -28
  24. package/dist/meta-tools.js +33 -89
  25. package/dist/providers/cloudflare.d.ts +2 -18
  26. package/dist/providers/cloudflare.js +1460 -2451
  27. package/dist/providers/linear.d.ts +4 -41
  28. package/dist/providers/linear.js +8 -39
  29. package/dist/providers/mixpanel.d.ts +3 -25
  30. package/dist/providers/mixpanel.js +7 -22
  31. package/dist/providers/notion.d.ts +1 -15
  32. package/dist/providers/notion.js +44 -173
  33. package/dist/providers/revenuecat.d.ts +4 -57
  34. package/dist/providers/revenuecat.js +10 -93
  35. package/dist/providers/stripe.d.ts +1 -12
  36. package/dist/providers/stripe.js +7 -45
  37. package/dist/registry.d.ts +16 -34
  38. package/dist/registry.js +18 -103
  39. package/dist/result-shapes.d.ts +13 -0
  40. package/dist/result-shapes.js +331 -0
  41. package/dist/routes/mcp.js +1 -1
  42. package/dist/routes/oauth.js +3 -3
  43. package/dist/routes/shared.d.ts +15 -15
  44. package/dist/routes/shared.js +1 -3
  45. package/dist/skills.js +3 -3
  46. package/dist/timeout.d.ts +8 -7
  47. package/dist/timeout.js +47 -38
  48. package/dist/types.d.ts +3 -3
  49. package/dist/ui.d.ts +1 -25
  50. package/dist/ui.js +18 -45
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/documentation/architecture.md +5 -2
  54. package/documentation/call-admission.md +1 -1
  55. package/documentation/cloudflare.md +1 -1
  56. package/documentation/code-mode.md +13 -13
  57. package/documentation/connectors.md +34 -1
  58. package/documentation/linear.md +1 -1
  59. package/documentation/meta-tools.md +17 -3
  60. package/documentation/mixpanel.md +1 -1
  61. package/documentation/notion.md +1 -1
  62. package/documentation/operations.md +20 -15
  63. package/documentation/provider-conventions.md +1 -1
  64. package/documentation/revenuecat.md +1 -1
  65. package/documentation/stripe.md +1 -1
  66. package/documentation/upgrading.md +24 -4
  67. package/ethos.md +74 -120
  68. package/package.json +3 -4
  69. package/templates/node/package.json +1 -1
  70. package/documentation/code-first-exploration.md +0 -292
  71. package/documentation/mcp-2026-07-28.md +0 -46
  72. package/documentation/mcp-ui-design.md +0 -382
  73. package/documentation/program-ui-read-calls.md +0 -213
  74. package/documentation/provider-audit.md +0 -198
  75. package/documentation/rich-output-design.md +0 -211
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { boundedDiscoveryText, CatalogService, DEFAULT_SEARCH_LIMIT, DiscoveryPolicyError, groupedSearchResult, MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, } from "./catalog-service.js";
3
3
  import { resolveDiscoveryConcurrency } from "./concurrency.js";
4
+ import { msg } from "./errors.js";
5
+ import { serializeResultText } from "./executor-result.js";
4
6
  import { InvocationService, MAX_RETRY_BACKOFF_MS, retryBackoffMs, } from "./invocation.js";
5
7
  import { isValidMaxResultBytes, MIN_MAX_RESULT_BYTES, resolveMaxResultBytes, } from "./registry.js";
6
8
  import { hasConnectorGuides, listSkills, resolveSkill, } from "./skills.js";
@@ -9,9 +11,9 @@ export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_RETRY_BACKOFF_M
9
11
  const RESULT_TTL_SECONDS = 900;
10
12
  const enc = new TextEncoder();
11
13
  const dec = new TextDecoder();
12
- export function jsonResult(obj) {
14
+ export function jsonResult(obj, text = JSON.stringify(obj)) {
13
15
  return {
14
- content: [{ type: "text", text: JSON.stringify(obj) }],
16
+ content: [{ type: "text", text }],
15
17
  ...(obj !== null && typeof obj === "object" && !Array.isArray(obj)
16
18
  ? { structuredContent: obj }
17
19
  : {}),
@@ -20,9 +22,6 @@ export function jsonResult(obj) {
20
22
  export function errorResult(message) {
21
23
  return { content: [{ type: "text", text: message }], isError: true };
22
24
  }
23
- function msg(err) {
24
- return err instanceof Error ? err.message : String(err);
25
- }
26
25
  function discoveryErrorResult(error) {
27
26
  const result = jsonResult({
28
27
  error: {
@@ -38,12 +37,7 @@ async function discoveryResult(operation, hint) {
38
37
  try {
39
38
  const value = await operation();
40
39
  const text = boundedDiscoveryText(value, hint);
41
- return {
42
- content: [{ type: "text", text }],
43
- ...(value !== null && typeof value === "object" && !Array.isArray(value)
44
- ? { structuredContent: value }
45
- : {}),
46
- };
40
+ return jsonResult(value, text);
47
41
  }
48
42
  catch (err) {
49
43
  if (err instanceof DiscoveryPolicyError) {
@@ -58,20 +52,8 @@ function isContinuationByte(b) {
58
52
  }
59
53
  /** Smallest accepted `get_result` byte offset. */
60
54
  const MIN_RESULT_OFFSET = 0;
61
- /**
62
- * The one definition of a usable `get_result` offset: a whole number of bytes
63
- * at or past {@link MIN_RESULT_OFFSET}. Shared by the registered zod schema and
64
- * the handler's own check, the way `isValidMaxResultBytes` is shared across the
65
- * cap's intake points (issue #32) — so a value valid at the wire is valid in
66
- * process, and the two cannot drift.
67
- *
68
- * Everything else is rejected rather than coerced, because coercion is how an
69
- * out-of-domain offset used to void a result silently: `Math.max(0, NaN)` is
70
- * `NaN`, which slices to nothing, serializes as `"offset": null`, and reports
71
- * no `nextOffset` — a caller sees a successful, empty result instead of an
72
- * error. An offset past the end of the payload stays legal: it is a whole
73
- * number of bytes, and it answers with an empty final page.
74
- */
55
+ /** Whole-byte offset accepted by the result representation documented in
56
+ * documentation/meta-tools.md#result-representation. */
75
57
  function isValidResultOffset(value) {
76
58
  return Number.isInteger(value) && value >= MIN_RESULT_OFFSET;
77
59
  }
@@ -93,21 +75,8 @@ export function alignStartToCharBoundary(bytes, offset) {
93
75
  o--;
94
76
  return o;
95
77
  }
96
- /**
97
- * Move a byte `end` back to the nearest UTF-8 codepoint boundary in
98
- * `(offset, total]`, so decoding `bytes[offset, end)` never splits a codepoint
99
- * (which would emit U+FFFD and break byte-exact reassembly). If backing up
100
- * would make no progress — a single codepoint wider than the window — extend
101
- * forward to the end of that codepoint instead so paging always advances.
102
- * Assumes `offset` is itself a codepoint boundary (offsets are the prior
103
- * `nextOffset`, which this function guarantees, and 0 is always a boundary).
104
- *
105
- * The return is always `> offset` while `offset < total`, whatever `end` is
106
- * asked for. That is the belt-and-braces half of issue #32: cap validation
107
- * keeps an empty window from arising in the first place, and this keeps an
108
- * empty window from turning into a `nextOffset === offset` paging loop if one
109
- * ever does. Exported for direct testing of that invariant.
110
- */
78
+ /** End boundary for UTF-8-safe, forward-progressing result pages. See
79
+ * documentation/meta-tools.md#result-representation. */
111
80
  export function alignEndToCharBoundary(bytes, offset, end, total) {
112
81
  if (end >= total)
113
82
  return total;
@@ -615,10 +584,6 @@ function applyFieldsToContent(content, fields, outputSchema) {
615
584
  * the three give one answer to the same question. A value JSON cannot serialize
616
585
  * at all (a BigInt) still throws, as before, and is reported as a failure.
617
586
  */
618
- function serializeResultText(value) {
619
- const serialized = JSON.stringify(value);
620
- return serialized === undefined ? String(value) : serialized;
621
- }
622
587
  /**
623
588
  * Stash `text` under `result:<uuid>` (ttl 900s) and describe it as the
624
589
  * truncation notice every over-cap path hands back.
@@ -761,7 +726,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
761
726
  requestScope,
762
727
  probeTimeoutMs,
763
728
  concurrency: discoveryConcurrency,
764
- ...(opts.defer ? { defer: opts.defer } : {}),
729
+ defer: opts.defer,
765
730
  // searchRoute keeps its top-level default. In-program callers use a
766
731
  // separate CatalogService configured for connecta.search.
767
732
  });
@@ -792,19 +757,18 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
792
757
  // override the registry already warned about at startup is dropped
793
758
  // here, so the connector simply inherits `globalCap`.
794
759
  const cap = resolveMaxResultBytes(resolved.connector.maxResultBytes, globalCap);
760
+ const processed = (toolResult, truncated, value) => ({
761
+ toolResult,
762
+ ...value,
763
+ ...(truncated ? { friction: "result_too_large" } : {}),
764
+ });
795
765
  if (call.resultMode === "value") {
796
766
  let value = fields
797
767
  ? projectionValue(result, fields, resolved.definition.outputSchema)
798
768
  : result;
799
769
  const guarded = await guardValue(value, results, cap);
800
770
  value = guarded.result;
801
- return {
802
- toolResult: jsonResult({ ok: true, data: value }),
803
- value,
804
- ...(guarded.truncated
805
- ? { friction: "result_too_large" }
806
- : {}),
807
- };
771
+ return processed(jsonResult({ ok: true, data: value }), guarded.truncated, { value });
808
772
  }
809
773
  if (resolved.connector.kind === "mcp") {
810
774
  const mcpResult = result;
@@ -813,33 +777,25 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
813
777
  content = applyFieldsToContent(content, fields, resolved.definition.outputSchema);
814
778
  }
815
779
  const guarded = await guardContent(content, results, cap);
816
- return {
817
- toolResult: guarded.result,
818
- ...(guarded.truncated
819
- ? { friction: "result_too_large" }
820
- : {}),
821
- };
780
+ return processed(guarded.result, guarded.truncated);
822
781
  }
823
782
  const value = fields
824
783
  ? projectionValue(result, fields, resolved.definition.outputSchema)
825
784
  : result;
826
785
  const guarded = await guardText(serializeResultText(value), results, cap);
827
- return {
828
- toolResult: guarded.result,
829
- value,
830
- ...(guarded.truncated
831
- ? { friction: "result_too_large" }
832
- : {}),
833
- };
786
+ return processed(guarded.result, guarded.truncated, { value });
834
787
  },
835
788
  activityFriction: (processed) => processed.friction,
836
789
  });
837
790
  if (!outcome.ok) {
838
791
  const structuredRecovery = outcome.error.nextAction !== undefined;
839
- const failedResult = structuredRecovery ||
840
- outcome.error.code === "auth_required" ||
841
- outcome.error.code === "invalid_args" ||
842
- outcome.error.code === "input_required_unsupported" ||
792
+ const recoveryRequired = structuredRecovery ||
793
+ [
794
+ "auth_required",
795
+ "invalid_args",
796
+ "input_required_unsupported",
797
+ ].includes(outcome.error.code);
798
+ const failedResult = recoveryRequired ||
843
799
  call.resultMode === "value"
844
800
  ? jsonResult({
845
801
  ok: false,
@@ -849,10 +805,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
849
805
  ...(call.diagnostics ? { timing: outcome.timing } : {}),
850
806
  })
851
807
  : errorResult(outcome.error.message);
852
- if (structuredRecovery ||
853
- outcome.error.code === "auth_required" ||
854
- outcome.error.code === "invalid_args" ||
855
- outcome.error.code === "input_required_unsupported") {
808
+ if (recoveryRequired) {
856
809
  failedResult.isError = true;
857
810
  }
858
811
  return {
@@ -1050,7 +1003,6 @@ const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHi
1050
1003
  const GET_RESULT_DESC = "Page a truncated direct-call result by id and byte offset. A program result is never paged; reduce it inside execute_code. Returns text, offset, nextOffset when more remains, and totalBytes.";
1051
1004
  const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or reports required deployment configuration. force=true restarts OAuth only; this tool never accepts credentials.";
1052
1005
  const SKILLS_DESC = 'List or fetch on-demand guidance. Fetch usage once per task for program syntax, selection, repair, examples, and runtime details.';
1053
- const SEARCH_WITH_DESCRIBE_DESC = SEARCH_DESC;
1054
1006
  /**
1055
1007
  * Sentences appended to a meta-tool description only when this connection
1056
1008
  * actually has connector guides. Tool descriptions are always-loaded context,
@@ -1107,20 +1059,12 @@ const CALL_INPUT_SCHEMA = {
1107
1059
  */
1108
1060
  export function registerMetaTools(server, registry, ctx) {
1109
1061
  const mt = createMetaTools(registry, ctx.baseUrl, {
1110
- ...(ctx.defaultToolTimeoutMs !== undefined
1111
- ? { defaultToolTimeoutMs: ctx.defaultToolTimeoutMs }
1112
- : {}),
1113
- ...(ctx.probeTimeoutMs !== undefined
1114
- ? { probeTimeoutMs: ctx.probeTimeoutMs }
1115
- : {}),
1116
- ...(ctx.discoveryConcurrency !== undefined
1117
- ? { discoveryConcurrency: ctx.discoveryConcurrency }
1118
- : {}),
1119
- ...(ctx.activity !== undefined ? { activity: ctx.activity } : {}),
1120
- ...(ctx.requestSignal !== undefined
1121
- ? { requestSignal: ctx.requestSignal }
1122
- : {}),
1123
- ...(ctx.defer !== undefined ? { defer: ctx.defer } : {}),
1062
+ defaultToolTimeoutMs: ctx.defaultToolTimeoutMs,
1063
+ probeTimeoutMs: ctx.probeTimeoutMs,
1064
+ discoveryConcurrency: ctx.discoveryConcurrency,
1065
+ activity: ctx.activity,
1066
+ requestSignal: ctx.requestSignal,
1067
+ defer: ctx.defer,
1124
1068
  });
1125
1069
  server.registerTool("skills", {
1126
1070
  description: describedFor(registry, SKILLS_DESC, "skills"),
@@ -1129,7 +1073,7 @@ export function registerMetaTools(server, registry, ctx) {
1129
1073
  _meta: { ui: { visibility: ["model"] } },
1130
1074
  }, async (args) => mt.skills(args));
1131
1075
  server.registerTool("search_tools", {
1132
- description: describedFor(registry, SEARCH_WITH_DESCRIBE_DESC, "search"),
1076
+ description: describedFor(registry, SEARCH_DESC, "search"),
1133
1077
  inputSchema: z.object({
1134
1078
  query: z.string().optional(),
1135
1079
  connector: z.string().optional(),
@@ -3,25 +3,9 @@ import type { Connector, ConnectorCredentialConfig } from "../types.js";
3
3
  export declare const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
4
4
  /** Authentication schemes accepted by Cloudflare's v4 API. */
5
5
  export type CloudflareAuthentication = "apiToken" | "globalApiKey";
6
- /**
7
- * Every DNS record type the records API accepts, for filtering a list.
8
- * Enumerated in the schema so an agent picks a legal type without reading
9
- * Cloudflare's documentation.
10
- */
6
+ /** See documentation/cloudflare.md#dns-record-types. */
11
7
  export declare const CLOUDFLARE_DNS_RECORD_TYPES: readonly ["A", "AAAA", "CAA", "CERT", "CNAME", "DNSKEY", "DS", "HTTPS", "LOC", "MX", "NAPTR", "NS", "OPENPGPKEY", "PTR", "SMIMEA", "SRV", "SSHFP", "SVCB", "TLSA", "TXT", "URI"];
12
- /**
13
- * The record types whose value is a single `content` string — the eight this
14
- * connection can create and update.
15
- *
16
- * The other thirteen (CAA, CERT, DNSKEY, DS, HTTPS, LOC, NAPTR, SMIMEA, SRV,
17
- * SSHFP, SVCB, TLSA, URI) carry a per-type structured `data` object instead,
18
- * each with its own field set. Accepting them here would mean either a
19
- * free-form `data` passthrough — exactly the untyped `{}` this connection
20
- * exists to avoid — or thirteen more hand-written schemas for record types
21
- * that are rare in the day-to-day work this surface is for. They remain fully
22
- * readable and filterable; only the named create/update tools omit them. The
23
- * guarded raw mutation tool remains available for their documented bodies.
24
- */
8
+ /** Content-valued types only; see documentation/cloudflare.md#dns-record-types. */
25
9
  export declare const CLOUDFLARE_CONTENT_DNS_RECORD_TYPES: readonly ["A", "AAAA", "CNAME", "MX", "NS", "OPENPGPKEY", "PTR", "TXT"];
26
10
  export interface CloudflareOptions {
27
11
  /** Human-readable display name; defaults to "Cloudflare". */