@zackbart/connecta 0.37.1 → 0.38.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 (168) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +2 -2
  3. package/bin/connecta.mjs +1 -39
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +2 -4
  6. package/dist/agent-identity.js +1 -1
  7. package/dist/auth/clerk-sdk/generated.js +2 -1
  8. package/dist/auth/downstream-oauth.js +19 -153
  9. package/dist/auth/downstream-revocation.js +1 -1
  10. package/dist/auth/oauth-grant.js +11 -0
  11. package/dist/auth/oauth-token-response.js +1 -1
  12. package/dist/auth/vercel-agent.js +2 -1
  13. package/dist/auth-elicitation.js +2 -2
  14. package/dist/branding.d.ts +1 -15
  15. package/dist/branding.js +2 -2
  16. package/dist/bytes.js +42 -0
  17. package/dist/call-admission-context.js +2 -5
  18. package/dist/call-recovery.js +0 -3
  19. package/dist/catalog-fingerprint.js +2 -18
  20. package/dist/catalog-service.js +1 -1
  21. package/dist/config-defaults.d.ts +1 -0
  22. package/dist/config-defaults.js +1 -0
  23. package/dist/config-schema.d.ts +1 -1
  24. package/dist/config-schema.js +6 -2
  25. package/dist/config.d.ts +41 -14
  26. package/dist/config.js +21 -71
  27. package/dist/connectors/api-connector.js +4 -3
  28. package/dist/connectors/guarded-fetch.d.ts +0 -6
  29. package/dist/connectors/guarded-fetch.js +1 -11
  30. package/dist/connectors/remote-mcp.js +10 -43
  31. package/dist/connectors/resource-uri.js +0 -4
  32. package/dist/d1.d.ts +0 -89
  33. package/dist/d1.js +0 -34
  34. package/dist/downstream-input.js +2 -2
  35. package/dist/execute.js +22 -13
  36. package/dist/executor-result.js +2 -1
  37. package/dist/executors/quickjs-runtime.js +2 -25
  38. package/dist/executors/quickjs.js +1 -1
  39. package/dist/guest-runtime.js +5 -3
  40. package/dist/guest-types.d.ts +5 -5
  41. package/dist/http/client.js +1 -1
  42. package/dist/http/errors.js +2 -2
  43. package/dist/http/json-schema.js +1 -1
  44. package/dist/http/node-transport.js +0 -1
  45. package/dist/http/oauth.js +1 -1
  46. package/dist/http/paging.js +6 -9
  47. package/dist/http/provider.js +1 -1
  48. package/dist/http/safety.js +1 -1
  49. package/dist/http/services.js +1 -8
  50. package/dist/http/tools.js +1 -7
  51. package/dist/http/vendor.js +7 -7
  52. package/dist/identity.js +2 -6
  53. package/dist/invocation-auth.js +2 -1
  54. package/dist/meta-tools.js +27 -27
  55. package/dist/native-tools.js +2 -1
  56. package/dist/oauth-handoff.js +2 -2
  57. package/dist/operation-vocabulary.js +1 -1
  58. package/dist/operator-record.js +1 -49
  59. package/dist/operator-ui/contract.d.ts +2 -2
  60. package/dist/operator-ui/generated.js +3 -3
  61. package/dist/program-writes.js +3 -8
  62. package/dist/provider-call.js +0 -7
  63. package/dist/provider.d.ts +1 -1
  64. package/dist/providers/_shared/google/kit.js +1 -6
  65. package/dist/providers/_shared/google/service-account.js +1 -3
  66. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  67. package/dist/providers/_shared/guide.js +3 -0
  68. package/dist/providers/_shared/json.js +12 -0
  69. package/dist/providers/_shared/rest/compact-index.js +1 -1
  70. package/dist/providers/_shared/rest/kit.js +1 -3
  71. package/dist/providers/_shared/rest/operation-index.js +1 -3
  72. package/dist/providers/_shared/rest/value-safety.js +26 -28
  73. package/dist/providers/breeze/api.js +2 -7
  74. package/dist/providers/breeze/index.js +4 -2
  75. package/dist/providers/breeze/tools.js +3 -4
  76. package/dist/providers/ccb/api.js +1 -6
  77. package/dist/providers/ccb/index.js +2 -1
  78. package/dist/providers/ccb/tools.js +1 -6
  79. package/dist/providers/cloudflare/api.js +2 -2
  80. package/dist/providers/cloudflare/index.js +2 -1
  81. package/dist/providers/cloudflare/safety.js +3 -3
  82. package/dist/providers/docs/tools.js +2 -10
  83. package/dist/providers/drive/tools.js +12 -25
  84. package/dist/providers/forms/tools.js +1 -9
  85. package/dist/providers/gmail/tools.js +5 -21
  86. package/dist/providers/overflow/api.js +1 -3
  87. package/dist/providers/overflow/index.js +2 -1
  88. package/dist/providers/overflow/tools.js +3 -7
  89. package/dist/providers/planning-center/api.js +2 -1
  90. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  91. package/dist/providers/planning-center/lib/json-api.js +1 -9
  92. package/dist/providers/planning-center/tools.js +2 -1
  93. package/dist/providers/sheets/tools.js +1 -9
  94. package/dist/providers/slides/tools.js +4 -13
  95. package/dist/providers/stripe/index.js +2 -1
  96. package/dist/providers/stripe/safety.js +2 -2
  97. package/dist/providers/tithely/api.js +2 -7
  98. package/dist/providers/tithely/index.js +3 -2
  99. package/dist/providers/tithely/tools.js +7 -5
  100. package/dist/providers/vercel/api.js +1 -6
  101. package/dist/providers/vercel/lib/pin.js +1 -1
  102. package/dist/providers/vercel/safety.js +1 -1
  103. package/dist/providers/vercel/tools.js +2 -1
  104. package/dist/redaction-marker.js +2 -2
  105. package/dist/registry.d.ts +1 -1
  106. package/dist/registry.js +5 -7
  107. package/dist/request-principal.js +1 -1
  108. package/dist/request-state.js +2 -2
  109. package/dist/routes/access-tokens.js +1 -1
  110. package/dist/routes/activity-disclosure.js +1 -1
  111. package/dist/routes/connect.js +2 -2
  112. package/dist/routes/credentials.js +1 -1
  113. package/dist/routes/mcp.js +4 -3
  114. package/dist/routes/oauth-management.js +1 -1
  115. package/dist/routes/operator.js +4 -3
  116. package/dist/routes/shared.d.ts +8 -8
  117. package/dist/routes/shared.js +9 -9
  118. package/dist/routes/ui-config.js +1 -1
  119. package/dist/routes/ui.js +1 -1
  120. package/dist/runtime/call-admission.js +1 -1
  121. package/dist/runtime/call.js +1 -1
  122. package/dist/runtime/settlement.js +1 -1
  123. package/dist/runtime/shared-read.js +2 -15
  124. package/dist/sent-secrets.d.ts +1 -2
  125. package/dist/sent-secrets.js +3 -8
  126. package/dist/server.js +4 -4
  127. package/dist/skills.js +8 -35
  128. package/dist/sqlite.d.ts +0 -19
  129. package/dist/sqlite.js +2 -83
  130. package/dist/storage/keys.js +16 -125
  131. package/dist/storage/sql-native.js +0 -101
  132. package/dist/storage/sql.d.ts +1 -24
  133. package/dist/storage/sql.js +1 -12
  134. package/dist/tool-error.js +4 -0
  135. package/dist/url-safety.js +0 -4
  136. package/dist/usage-guide.js +2 -2
  137. package/dist/version.d.ts +1 -1
  138. package/dist/version.js +1 -1
  139. package/dist/worker.js +1 -1
  140. package/documentation/README.md +6 -42
  141. package/documentation/architecture.md +86 -496
  142. package/documentation/auth.md +12 -25
  143. package/documentation/code-mode.md +90 -66
  144. package/documentation/deploying.md +19 -40
  145. package/documentation/integrating.md +4 -19
  146. package/documentation/meta-tools.md +6 -6
  147. package/documentation/migration-0.33.md +5 -5
  148. package/documentation/migration-0.34.md +2 -4
  149. package/documentation/migration-retired-layouts.md +74 -0
  150. package/documentation/operator-tests.md +19 -119
  151. package/documentation/operator-ui.md +3 -4
  152. package/documentation/provider-auth-migration.md +4 -10
  153. package/documentation/provider-migration-0.29.md +4 -15
  154. package/examples/worker/AGENTS.md +4 -6
  155. package/examples/worker/README.md +20 -115
  156. package/guides/connecting-agents.md +7 -30
  157. package/guides/how-it-works.md +2 -2
  158. package/guides/safety-and-trust.md +4 -1
  159. package/package.json +19 -19
  160. package/templates/node/README.md +9 -26
  161. package/templates/node/package.json +1 -1
  162. package/dist/auth/oauth-v2-migration.js +0 -135
  163. package/dist/provider-call-facts.d.ts +0 -115
  164. package/dist/provider-call.d.ts +0 -112
  165. package/dist/storage/d1-copy.js +0 -255
  166. package/dist/storage/sql-schema.d.ts +0 -6
  167. package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
  168. package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
@@ -1,3 +1,5 @@
1
+ import { asRecord, asArray, compact } from "../_shared/json.js";
2
+ import { decodeBase64, decodeBase64url } from "../../bytes.js";
1
3
  // Google Drive named workflows, bounded projections and bound continuation on the provider kit.
2
4
  import { Effect, Option, Schema } from "effect";
3
5
  import { ConnectorCallError } from "../../errors.js";
@@ -83,15 +85,6 @@ const DETAIL_FIELDS = `${SUMMARY_FIELDS},description,createdTime,lastModifyingUs
83
85
  const PERMISSION_FIELDS = "id,type,role,emailAddress,domain,displayName,allowFileDiscovery,expirationTime,deleted,permissionDetails(inherited,inheritedFrom)";
84
86
  /** Every file request reaches shared drives as well as My Drive. */
85
87
  const ALL_DRIVES = { supportsAllDrives: true };
86
- function asRecord(value) {
87
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
88
- }
89
- function asArray(value) {
90
- return Array.isArray(value) ? value : [];
91
- }
92
- function compact(value) {
93
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
94
- }
95
88
  function text(value) {
96
89
  return typeof value === "string" && value !== "" ? value : undefined;
97
90
  }
@@ -260,8 +253,7 @@ function encodeCursor(ctx, resume) {
260
253
  const FINGERPRINT = /^[A-Za-z0-9_-]{16}$/;
261
254
  function decodeCursor(cursor) {
262
255
  try {
263
- const base = cursor.replace(/-/g, "+").replace(/_/g, "/");
264
- const json = new TextDecoder().decode(fromBase64(base + "=".repeat((4 - (base.length % 4)) % 4)));
256
+ const json = new TextDecoder().decode(decodeBase64url(cursor, true));
265
257
  const [token, skip, size, scope, page, last] = JSON.parse(json);
266
258
  const midPage = typeof page === "string" && FINGERPRINT.test(page) && typeof last === "string" && FINGERPRINT.test(last);
267
259
  if ((token === null || (typeof token === "string" && token !== "")) &&
@@ -471,19 +463,6 @@ function base64(bytes) {
471
463
  }
472
464
  return btoa(binary);
473
465
  }
474
- function fromBase64(value) {
475
- let binary;
476
- try {
477
- binary = atob(value);
478
- }
479
- catch {
480
- throw new ConnectorCallError("invalid_args", "contentBase64 is not valid base64.");
481
- }
482
- const bytes = new Uint8Array(binary.length);
483
- for (let index = 0; index < binary.length; index += 1)
484
- bytes[index] = binary.charCodeAt(index);
485
- return bytes;
486
- }
487
466
  /**
488
467
  * Why the content tool returns no content for a file, as the note says it.
489
468
  * Only a well-formed id or media type is interpolated; anything else Drive
@@ -681,7 +660,7 @@ function media(args) {
681
660
  };
682
661
  }
683
662
  if (hasBinary) {
684
- const bytes = fromBase64(args["contentBase64"]);
663
+ const bytes = contentBytes(args["contentBase64"]);
685
664
  if (bytes.length > BINARY_INLINE_BYTES) {
686
665
  throw new ConnectorCallError("invalid_args", `contentBase64 decodes to ${bytes.length} bytes, past this connection's ${BINARY_INLINE_BYTES}-byte upload cap.`);
687
666
  }
@@ -1288,3 +1267,11 @@ function decisionInputs(file, keys) {
1288
1267
  }
1289
1268
  }
1290
1269
  }
1270
+ function contentBytes(value) {
1271
+ try {
1272
+ return decodeBase64(value);
1273
+ }
1274
+ catch {
1275
+ throw new ConnectorCallError("invalid_args", "contentBase64 is not valid base64.");
1276
+ }
1277
+ }
@@ -1,3 +1,4 @@
1
+ import { asRecord, asArray, compact } from "../_shared/json.js";
1
2
  // Forms projections and revision-checked workflows on the standard provider kit.
2
3
  import { Effect, Option, Schema } from "effect";
3
4
  import { ConnectorCallError } from "../../errors.js";
@@ -40,15 +41,6 @@ const MAX_REQUESTS = 100;
40
41
  * cursor included, is measured before it is returned.
41
42
  */
42
43
  const PAGE_RESERVE_BYTES = 4 * 1024;
43
- function asRecord(value) {
44
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
45
- }
46
- function asArray(value) {
47
- return Array.isArray(value) ? value : [];
48
- }
49
- function compact(value) {
50
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
51
- }
52
44
  function text(value) {
53
45
  return typeof value === "string" && value !== "" ? value : undefined;
54
46
  }
@@ -1,3 +1,5 @@
1
+ import { asRecord, asArray, compact } from "../_shared/json.js";
2
+ import { decodeBase64url } from "../../bytes.js";
1
3
  // Curated Gmail workflows over real Effect declarations and the standard kit boundary.
2
4
  import { Effect, Option } from "effect";
3
5
  import { defined } from "../../connectors/api-connector.js";
@@ -31,15 +33,6 @@ const MAX_RECIPIENTS = 100;
31
33
  const MAX_ATTACHMENT_BYTES = 1024 * 1024;
32
34
  /** Same direct-call ceiling and minimum result budget as Drive content reads. */
33
35
  const MAX_ATTACHMENT_RESULT_BYTES = 4 * 1024 * 1024;
34
- function asRecord(value) {
35
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
36
- }
37
- function asArray(value) {
38
- return Array.isArray(value) ? value : [];
39
- }
40
- function compact(value) {
41
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
42
- }
43
36
  function text(value) {
44
37
  return typeof value === "string" && value !== "" ? value : undefined;
45
38
  }
@@ -83,15 +76,6 @@ function htmlToText(ctx, html) {
83
76
  .replace(/\n{3,}/g, "\n\n")
84
77
  .trim();
85
78
  }
86
- function base64UrlBytes(data) {
87
- const base64 = data.replace(/-/g, "+").replace(/_/g, "/");
88
- const binary = atob(base64 + "=".repeat((4 - (base64.length % 4)) % 4));
89
- const bytes = new Uint8Array(binary.length);
90
- for (let index = 0; index < binary.length; index += 1) {
91
- bytes[index] = binary.charCodeAt(index);
92
- }
93
- return bytes;
94
- }
95
79
  function charsetOf(ctx, part) {
96
80
  const type = header(part, "Content-Type") ?? "";
97
81
  if (redactBeforeCut(ctx, type) !== type)
@@ -113,7 +97,7 @@ function charsetOf(ctx, part) {
113
97
  function decodeIn(ctx, part, data) {
114
98
  let bytes;
115
99
  try {
116
- bytes = base64UrlBytes(data);
100
+ bytes = decodeBase64url(data, true);
117
101
  }
118
102
  catch {
119
103
  return { text: "" };
@@ -346,7 +330,7 @@ function attachmentBytes(client, ctx, messageId, part) {
346
330
  validateSize(provenance.originalLength);
347
331
  let bytes;
348
332
  try {
349
- bytes = base64UrlBytes(data);
333
+ bytes = decodeBase64url(data, true);
350
334
  }
351
335
  catch {
352
336
  throw new ConnectorCallError("connector_call_failed", "Gmail returned invalid base64url attachment data.", {
@@ -605,7 +589,7 @@ function openCursor(cursor, tool, call) {
605
589
  return undefined;
606
590
  let claims;
607
591
  try {
608
- const parsed = JSON.parse(new TextDecoder().decode(base64UrlBytes(String(cursor))));
592
+ const parsed = JSON.parse(new TextDecoder().decode(decodeBase64url(String(cursor), true)));
609
593
  if (parsed && typeof parsed === "object" && parsed.v === 1)
610
594
  claims = parsed;
611
595
  }
@@ -1,3 +1,4 @@
1
+ import { asRecord } from "../_shared/json.js";
1
2
  // Overflow v3: reviewed vendor declarations, derived operation index and paging contracts.
2
3
  // Public input: staging OpenAPI 3.0; exact retrieval and known upstream drift are in evidence/review.md.
3
4
  import { Effect, Schema } from "effect";
@@ -1003,6 +1004,3 @@ export const probe = {
1003
1004
  return `Overflow ${options.environment} accepted the client id and API key.${where}`;
1004
1005
  },
1005
1006
  };
1006
- function asRecord(value) {
1007
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
1008
- }
@@ -1,3 +1,4 @@
1
+ import { readOnlyNotice } from "../_shared/guide.js";
1
2
  import { skill } from "./skill.generated.js";
2
3
  import { keys, optionsOf } from "../../config-schema.js";
3
4
  import { PROVIDER_COMMON } from "../../connectors/option-shapes.js";
@@ -81,7 +82,7 @@ export const overflow = defineApiProvider({
81
82
  : "Overflow staging: a sandbox with separate credentials and data.",
82
83
  `Nonprofit purpose: ${options.purpose}`,
83
84
  "Named reads minimize contact data; get_donor is the full-contact lookup. Webhook signing secrets are always removed.",
84
- ...(options.readOnly ? ["This connection is read-only: every write is refused."] : []),
85
+ ...readOnlyNotice(options.readOnly),
85
86
  ...(options.defaultPageSize !== undefined
86
87
  ? [`Paged lists return ${options.defaultPageSize} items by default on this connection.`]
87
88
  : []),
@@ -1,3 +1,5 @@
1
+ import { compact as compactDefined } from "../_shared/json.js";
2
+ import { asRecord, asArray } from "../_shared/json.js";
1
3
  // Overflow's curated kit tools: standard pages, safe projections and narrowing fields.
2
4
  import { Effect, Schema } from "effect";
3
5
  import { ConnectorCallError } from "../../errors.js";
@@ -5,14 +7,8 @@ import { Refusal } from "../../http/errors.js";
5
7
  import { Integer, isMinChars } from "../../http/json-schema.js";
6
8
  import { read, list } from "../../http/tools.js";
7
9
  import * as api from "./api.js";
8
- function asRecord(value) {
9
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
10
- }
11
- function asArray(value) {
12
- return Array.isArray(value) ? value : [];
13
- }
14
10
  function compact(value) {
15
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
11
+ return compactDefined(value);
16
12
  }
17
13
  /** A `{ id, name }` reference, or undefined when Overflow sent none. */
18
14
  function reference(value) {
@@ -1,3 +1,4 @@
1
+ import { asArray, asRecord } from "../_shared/json.js";
1
2
  import { Effect, Schema } from "effect";
2
3
  import { ConnectorCallError } from "../../errors.js";
3
4
  import { Refusal } from "../../http/errors.js";
@@ -11,7 +12,7 @@ import { PlanningCenterIndex } from "./lib/discovery.js";
11
12
  import { PCO_REFUSALS } from "./lib/verdicts.js";
12
13
  import { TABLE } from "./safety.js";
13
14
  import { excludedApps, series } from "./lib/guide.js";
14
- import { asRecord, asArray, resources, attribute, pageOf } from "./lib/json-api.js";
15
+ import { resources, attribute, pageOf } from "./lib/json-api.js";
15
16
  import { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, DEFAULT_USER_AGENT, } from "./lib/constants.js";
16
17
  export { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, PLANNING_CENTER_MAX_RESPONSE_BYTES, PLANNING_CENTER_ADMISSION, } from "./lib/constants.js";
17
18
  export const CREDENTIAL = {
@@ -55,4 +55,4 @@ export declare const PLANNING_CENTER_MAX_RESPONSE_BYTES: number;
55
55
  */
56
56
  export declare const PLANNING_CENTER_ADMISSION: ConnectorCallAdmissionPolicy;
57
57
  /** Planning Center refuses a request without a descriptive one (HTTP 403). */
58
- export declare const DEFAULT_USER_AGENT = "connecta/0.37.1 (+https://github.com/zackbart/connecta)";
58
+ export declare const DEFAULT_USER_AGENT = "connecta/0.38.0 (+https://github.com/zackbart/connecta)";
@@ -1,13 +1,5 @@
1
+ import { asRecord, asArray, compact } from "../../_shared/json.js";
1
2
  import { ConnectorCallError } from "../../../errors.js";
2
- export function asRecord(value) {
3
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4
- }
5
- export function asArray(value) {
6
- return Array.isArray(value) ? value : [];
7
- }
8
- export function compact(value) {
9
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
10
- }
11
3
  function isResource(value) {
12
4
  const record = asRecord(value);
13
5
  return typeof record["id"] === "string" && typeof record["type"] === "string";
@@ -1,3 +1,4 @@
1
+ import { asRecord, compact } from "../_shared/json.js";
1
2
  // Curated Planning Center workflows over the standard provider kit.
2
3
  import { Effect, Schema } from "effect";
3
4
  import { ConnectorCallError } from "../../errors.js";
@@ -6,7 +7,7 @@ import { Integer, isMinChars, emptyRequired } from "../../http/json-schema.js";
6
7
  import { read, write, list as standardList } from "../../http/tools.js";
7
8
  import { PLANNING_CENTER_APPS, MAX_PAGE_SIZE, planningCenterCall } from "./api.js";
8
9
  import { excludedApps } from "./lib/guide.js";
9
- import { asRecord, compact, resources, indexIncluded, relId, related, relatedMany, pick, attribute, writeDocument, pageOf, } from "./lib/json-api.js";
10
+ import { resources, indexIncluded, relId, related, relatedMany, pick, attribute, writeDocument, pageOf, } from "./lib/json-api.js";
10
11
  const fail = (code, message, options) => Effect.fail(new Refusal({ error: new ConnectorCallError(code, message, options) }));
11
12
  const pattern = (source) => Schema.makeFilter((value) => typeof value !== "string" || new RegExp(source).test(value), {
12
13
  toJsonSchema: () => ({ pattern: source }),
@@ -1,3 +1,4 @@
1
+ import { asRecord, asArray, compact } from "../_shared/json.js";
1
2
  // Google Sheets named projections, bounded cell reads and guarded writes.
2
3
  import { Effect, Option, Schema } from "effect";
3
4
  import { ConnectorCallError } from "../../errors.js";
@@ -38,15 +39,6 @@ const MAX_CELL_CHARS = 50_000;
38
39
  const MAX_WRITE_CELLS = 50_000;
39
40
  /** Named ranges a metadata read lists before reporting a size cut. */
40
41
  const MAX_NAMED_RANGES = 200;
41
- function asRecord(value) {
42
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
43
- }
44
- function asArray(value) {
45
- return Array.isArray(value) ? value : [];
46
- }
47
- function compact(value) {
48
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
49
- }
50
42
  function text(value) {
51
43
  return typeof value === "string" && value !== "" ? value : undefined;
52
44
  }
@@ -1,3 +1,5 @@
1
+ import { asRecord, asArray, compact } from "../_shared/json.js";
2
+ import { decodeBase64url } from "../../bytes.js";
1
3
  // Google Slides named workflows on real kit schemas, with bound content continuation.
2
4
  import { Effect, Option, Schema } from "effect";
3
5
  import { ConnectorCallError } from "../../errors.js";
@@ -52,7 +54,7 @@ const MAX_ECHO_CHARS = 100;
52
54
  /**
53
55
  * The comment kinds of Slides' Request. A batch carrying any of them has
54
56
  * comment changes Slides saves apart from the deck's, and only `ALL_SAVED`
55
- * says they all landed (#696).
57
+ * says they all landed.
56
58
  */
57
59
  /** The comment save states that say every comment change in a batch landed, or none was asked for. */
58
60
  const COMMENTS_SETTLED = new Set(["NO_UPDATES_REQUESTED", "ALL_SAVED"]);
@@ -121,15 +123,6 @@ const COMMENT_DECK_FIELDS = "presentationId,revisionId,commentsViewMode,comments
121
123
  "slides(objectId,commentAnchors,slideProperties(notesPage(objectId,commentAnchors)))," +
122
124
  "layouts(objectId,commentAnchors),masters(objectId,commentAnchors),notesMaster(objectId,commentAnchors)";
123
125
  const COMMENT_PAGE_FIELDS = "objectId,revisionId,commentsViewMode,comments,commentAnchors";
124
- function asRecord(value) {
125
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
126
- }
127
- function asArray(value) {
128
- return Array.isArray(value) ? value : [];
129
- }
130
- function compact(value) {
131
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
132
- }
133
126
  function text(value) {
134
127
  return typeof value === "string" && value !== "" ? value : undefined;
135
128
  }
@@ -226,9 +219,7 @@ function decodeCursor(value, expected) {
226
219
  const refuse = (why) => new ConnectorCallError("invalid_args", `${why} Omit cursor to start from the beginning.`);
227
220
  let cursor;
228
221
  try {
229
- const base64 = String(value).replace(/-/g, "+").replace(/_/g, "/");
230
- const binary = atob(base64 + "=".repeat((4 - (base64.length % 4)) % 4));
231
- cursor = JSON.parse(new TextDecoder().decode(Uint8Array.from(binary, (character) => character.charCodeAt(0))));
222
+ cursor = JSON.parse(new TextDecoder().decode(decodeBase64url(String(value), true)));
232
223
  }
233
224
  catch {
234
225
  throw refuse("cursor is not one this connection issued; pass page.nextCursor back unchanged.");
@@ -1,3 +1,4 @@
1
+ import { readOnlyNotice } from "../_shared/guide.js";
1
2
  import { keys, optionsOf, variants } from "../../config-schema.js";
2
3
  import { defineApiProvider } from "../../http/provider.js";
3
4
  import { authenticate, connectedAccount, MAX_RESPONSE_BYTES, READ_CAP_MS, MODE_COPY, probe, safety, stripeErrors, StripeApi, stripeIndex, stripeBinding, STRIPE_PAGES, STRIPE_VERDICTS, } from "./api.js";
@@ -63,7 +64,7 @@ function usageGuide(options) {
63
64
  `Connected account: ${account}. Every call acts as this Connect account; the platform account's own objects are out of reach here.`,
64
65
  ]
65
66
  : []),
66
- ...(options.readOnly === true ? ["This connection is read-only: every write is refused."] : []),
67
+ ...readOnlyNotice(options.readOnly === true),
67
68
  ],
68
69
  required: true,
69
70
  };
@@ -1,4 +1,4 @@
1
- // Stripe's reviewed value-safety table (#801), on the shared mechanism in
1
+ // Stripe's reviewed value-safety table, on the shared mechanism in
2
2
  // `../_shared/rest/value-safety.ts`: every operation the detector flags in the
3
3
  // pinned spec (`evidence/value-safety.json`) has one verdict here, and
4
4
  // `provider.safety.node.test.ts` runs the shared harness over it.
@@ -48,7 +48,7 @@
48
48
  // - Issuing card numbers and CVCs come back only as expansions, which the
49
49
  // connector refuses on every Issuing path (`api.ts`); the card operations
50
50
  // also redact both fields.
51
- // Embedded URL verdict (#812): reviewed paths + global signing parameters.
51
+ // Embedded URL verdict: reviewed paths + global signing parameters.
52
52
  // Evidence: digest-verified Stripe v2555, components/schemas/charge's
53
53
  // failure_message ("Message to user further explaining reason for charge
54
54
  // failure") and charge_outcome.seller_message (Stripe's human-readable
@@ -1,4 +1,5 @@
1
- // Tithe.ly's giving API (v1) as a kit vendor (decision 0010): the operations,
1
+ import { asRecord, asArray } from "../_shared/json.js";
2
+ // Tithe.ly's giving API (v1) as a kit vendor: the operations,
2
3
  // what its statuses mean, its credential, its in-band refusal, the probe, and
3
4
  // the value-safety review. Facts from the per-operation reference at
4
5
  // https://docs.tithe.ly/reference/introduction, each marked where it is
@@ -509,12 +510,6 @@ export function tithelySafety(sensitiveFields) {
509
510
  }
510
511
  // --- Shared readers ------------------------------------------------------------------
511
512
  const failed = (error) => Effect.fail(new Refusal({ error }));
512
- export function asRecord(value) {
513
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
514
- }
515
- export function asArray(value) {
516
- return Array.isArray(value) ? value : [];
517
- }
518
513
  export function text(value) {
519
514
  return typeof value === "string" && value !== "" ? value : undefined;
520
515
  }
@@ -1,6 +1,7 @@
1
+ import { readOnlyNotice } from "../_shared/guide.js";
1
2
  import { skill } from "./skill.generated.js";
2
3
  /**
3
- * Tithe.ly's giving API (v1) on the provider kit (decision 0010). No SDK
4
+ * Tithe.ly's giving API (v1) on the provider kit. No SDK
4
5
  * exists to depend on, and none would be welcome: this is a payments API, and
5
6
  * every tool was chosen and written, not generated (decision record 0001:
6
7
  * hand-authored tools). The vendor lives in `api.ts` and the tools in
@@ -67,7 +68,7 @@ function usageGuide(options) {
67
68
  sensitiveFields(options) === "return"
68
69
  ? "This connection returns organizations' payout bank and legal-contact details through tithely_api_read; the named reads still omit them."
69
70
  : "This connection withholds organizations' payout bank and legal-contact details from every result.",
70
- ...(options.readOnly === true ? ["This connection is read-only: every write is refused."] : []),
71
+ ...readOnlyNotice(options.readOnly === true),
71
72
  ...(options.defaultPageSize !== undefined
72
73
  ? [`List tools return ${options.defaultPageSize} rows by default on this connection.`]
73
74
  : []),
@@ -1,8 +1,10 @@
1
- // Tithe.ly's curated tools on the provider kit (decision 0010) and their
1
+ import { compact as compactDefined } from "../_shared/json.js";
2
+ import { asArray, asRecord } from "../_shared/json.js";
3
+ // Tithe.ly's curated tools on the provider kit and their
2
4
  // projections. Decision 0011's step 2: standard lists ({ items, page } with
3
5
  // limit, cursor and fields), fields on every named read, and no `raw`: what a
4
6
  // projection drops stays reachable through the standard hatch, which the kit
5
- // builds from `api.ts`'s index and verdicts (decision 0014). Each named read
7
+ // builds from `api.ts`'s index and verdicts. Each named read
6
8
  // earns its place against the hatch with paging Tithe.ly cannot report
7
9
  // (lookahead), units (cents, ISO dates), joins (donor, organization and
8
10
  // payment method inline) or reconciliation of a known id after a write.
@@ -13,7 +15,7 @@ import { Refusal } from "../../http/errors.js";
13
15
  import { Integer } from "../../http/json-schema.js";
14
16
  import { lookahead } from "../../http/paging.js";
15
17
  import { get as getTool, list } from "../../http/tools.js";
16
- import { asArray, asRecord, entity, getCharge, getRecurring, listAccounts, listAllAccounts, listCharges, listOrganizations, listPaymentMethods, listRecurring, MAX_PAGE_SIZE, PAYOUT_CURRENCY, text, } from "./api.js";
18
+ import { entity, getCharge, getRecurring, listAccounts, listAllAccounts, listCharges, listOrganizations, listPaymentMethods, listRecurring, MAX_PAGE_SIZE, PAYOUT_CURRENCY, text, } from "./api.js";
17
19
  // --- Schemas ---------------------------------------------------------------------
18
20
  const Id = Schema.String.check(Schema.isMinLength(1));
19
21
  const Text = Schema.optionalKey(Schema.String);
@@ -267,7 +269,7 @@ function seconds(value, name) {
267
269
  return Effect.succeed(Math.floor(parsed / 1000));
268
270
  }
269
271
  function compact(value) {
270
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
272
+ return compactDefined(value);
271
273
  }
272
274
  /** Integer cents from Tithe.ly's string-or-number amounts. */
273
275
  function cents(value) {
@@ -325,7 +327,7 @@ function projectOrganization(value) {
325
327
  return compact({
326
328
  organizationId: text(organization["organization_id"]),
327
329
  name: text(organization["name"]),
328
- // The giving widget id embeds Tithe.ly's giving form; the step-2 eval showed agents need it (decision 0014).
330
+ // The giving widget id embeds Tithe.ly's giving form; the step-2 eval showed agents need it.
329
331
  widgetId: text(organization["widget_id"]),
330
332
  ownerAccountId: text(organization["account_id"]),
331
333
  website: text(organization["website"]),
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "../_shared/json.js";
1
2
  // Vercel's pinned contract, authentication, failures and value-safety boundary.
2
3
  import { Effect, Schema } from "effect";
3
4
  import { ConnectorCallError } from "../../errors.js";
@@ -24,9 +25,6 @@ const READ_POSTS = [
24
25
  ["POST", "/v1/registrar/domains/search", "Searches domain availability and pricing; buys nothing."],
25
26
  ["POST", "/v8/artifacts", "Reads Remote Cache metadata for a list of artifact hashes; uploads nothing."],
26
27
  ];
27
- export function isRecord(value) {
28
- return typeof value === "object" && value !== null && !Array.isArray(value);
29
- }
30
28
  export function record(value) {
31
29
  return isRecord(value) ? value : {};
32
30
  }
@@ -36,9 +34,6 @@ export function text(value) {
36
34
  export function invalid(message) {
37
35
  throw new ConnectorCallError("invalid_args", message);
38
36
  }
39
- export function compact(value) {
40
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
41
- }
42
37
  function truthy(value) {
43
38
  return value === true || value === 1 || (typeof value === "string" && /^(?:1|true)$/i.test(value.trim()));
44
39
  }
@@ -4,7 +4,7 @@ import { ConnectorCallError } from "../../../errors.js";
4
4
  import { Refusal } from "../../../http/errors.js";
5
5
  import { Budget } from "../../../http/services.js";
6
6
  import { checked } from "../../_shared/rest/kit.js";
7
- export const MAX_PIN_LOOKUPS = 8;
7
+ const MAX_PIN_LOOKUPS = 8;
8
8
  export const PIN_WRITE_STEPS = Object.freeze({
9
9
  write: "once",
10
10
  lookup1: "once",
@@ -214,7 +214,7 @@ const absent = {
214
214
  //
215
215
  // Residual risk: a secret a person typed into a free-text
216
216
  // name, description, log line, or identifier is out of scope.
217
- // Embedded URL verdict (#812): reviewed paths + global signing parameters.
217
+ // Embedded URL verdict: reviewed paths + global signing parameters.
218
218
  // Evidence: pinned index/candidates sha256:5a702fd2dc76 identify deployment
219
219
  // reads/creation/listing, build events and runtime logs; the operation/path
220
220
  // list below scans errorMessage, deployments.errorMessage, payload.text/text
@@ -1,3 +1,4 @@
1
+ import { compact, isRecord } from "../_shared/json.js";
1
2
  // Curated Vercel metadata and bounded logs through kit-owned call services.
2
3
  import { Effect, Schema } from "effect";
3
4
  import { ConnectorCallError } from "../../errors.js";
@@ -9,7 +10,7 @@ import { schema, typedParams } from "../_shared/rest/kit.js";
9
10
  import { refuseWithheldUpload } from "../_shared/rest/value-safety.js";
10
11
  import { rowStop } from "./lib/logs.js";
11
12
  import { checked } from "../_shared/rest/kit.js";
12
- import { admit, compact, defaultTeam, isRecord, invalid, named, queryOf, queryPairs, record, reviewedCode, restCall, text, vercelIndex, refuse, checkServer, } from "./api.js";
13
+ import { admit, defaultTeam, invalid, named, queryOf, queryPairs, record, reviewedCode, restCall, text, vercelIndex, refuse, checkServer, } from "./api.js";
13
14
  import { pinOptions, readPinnedTeam, PIN_WRITE_STEPS } from "./lib/pin.js";
14
15
  const MAX_LOG_BYTES = 1024 * 1024;
15
16
  const MAX_LOG_TEXT = 4_000;
@@ -7,7 +7,7 @@ export const REDACTED = "[redacted]";
7
7
  * secrets from every value of a vendor body before result safety reads it, so a rule that decides
8
8
  * on a value (a type discriminator, a record's label or type, a URL's scheme or parameter names,
9
9
  * a Content-Type) can find the placeholder where the vendor wrote what the rule matches.
10
- * Such a decision takes its most protective outcome (#703).
10
+ * Such a decision takes its most protective outcome.
11
11
  */
12
12
  export function redactionAltered(value) {
13
13
  return typeof value === "string" && value.includes(REDACTED);
@@ -92,7 +92,7 @@ function credentialUnit(code) {
92
92
  }
93
93
  /**
94
94
  * The text right after the placeholder at `at` that may be the rest of a credential whose public
95
- * prefix redaction took (#703): up to the first unit `ends` accepts, reading later placeholders
95
+ * prefix redaction took: up to the first unit `ends` accepts, reading later placeholders
96
96
  * whole so none is split. Redaction can take `sk_live_`, `whsec_`, `Bearer `, or a signing
97
97
  * parameter's name and `=`, leaving what follows; with that prefix known, any remainder rebuilds
98
98
  * the credential, however short or encoded, so `readable` holds whenever it carries any base64 or
@@ -240,7 +240,7 @@ export declare class Registry implements RegistryView {
240
240
  *
241
241
  * `chunks[n]` lands on `resultKeys.chunk(id, n)` inside `partition` — the
242
242
  * layout connecta.result reads back, so a page fetches only the chunks it covers
243
- * instead of the whole stored result (issue #540). Chunking is a read-cost
243
+ * instead of the whole stored result. Chunking is a read-cost
244
244
  * decision, not a capacity one: however many keys an envelope occupies, it
245
245
  * is one stash entry charged its total ASCII length.
246
246
  *
package/dist/registry.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { wrapViewCredentials } from "./runtime/call.js";
2
+ import { sha256Hex } from "./bytes.js";
1
3
  import { catalogRevision } from "./catalog-revision.js";
2
4
  import { authCall, authInvoke } from "./auth/native.js";
3
5
  import { Clock, Duration, Effect, Random } from "effect";
@@ -23,7 +25,7 @@ import { causeError, runEdge } from "./runtime/run.js";
23
25
  import { Storage } from "./runtime/services.js";
24
26
  import { SharedRead } from "./runtime/shared-read.js";
25
27
  import { runOnPartition, storageCompareAndSet, storageDelete, storageGet, storageSet, storageList, } from "./runtime/storage.js";
26
- import { redactCatalog, sentSecretsFor, sentSecretsForRequest, trackCredentialReads } from "./sent-secrets.js";
28
+ import { redactCatalog, sentSecretsFor, sentSecretsForRequest } from "./sent-secrets.js";
27
29
  import { GUIDE_SUMMARY_LENGTH, normalizeGuideSummary } from "./skills.js";
28
30
  import { jsonCodec, OAUTH_HANDOFF_TTL_SECONDS, oauthHandoffKeys, resultKeys, scopes, stashExpiryKeys, stashLedgerKeys, } from "./storage/keys.js";
29
31
  import { assertStaticToolNames, hasControlCharacters } from "./tool-name.js";
@@ -232,10 +234,6 @@ function swapStashLedger(plan) {
232
234
  }).pipe(Effect.catch(() => Effect.succeed({ status: "exhausted" })));
233
235
  });
234
236
  }
235
- async function sha256Hex(value) {
236
- const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
237
- return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
238
- }
239
237
  /**
240
238
  * Holds the connector set, resolves addresses, and coalesces tool listings
241
239
  * within one request. Remote MCP clients own persisted response caching. Connector failures are isolated: a broken
@@ -518,7 +516,7 @@ export class Registry {
518
516
  : {}),
519
517
  });
520
518
  sentSecretsFor(context);
521
- trackCredentialReads(context);
519
+ wrapViewCredentials(context);
522
520
  attachOAuthPartition(context, this.oauthPartition);
523
521
  // Downstream OAuth state is sealed under the vault key, bound to this
524
522
  // connector and owner. The sealer rides beside the context, not on it.
@@ -581,7 +579,7 @@ export class Registry {
581
579
  *
582
580
  * `chunks[n]` lands on `resultKeys.chunk(id, n)` inside `partition` — the
583
581
  * layout connecta.result reads back, so a page fetches only the chunks it covers
584
- * instead of the whole stored result (issue #540). Chunking is a read-cost
582
+ * instead of the whole stored result. Chunking is a read-cost
585
583
  * decision, not a capacity one: however many keys an envelope occupies, it
586
584
  * is one stash entry charged its total ASCII length.
587
585
  *
@@ -1,4 +1,4 @@
1
- // The admitted principal a request-auth resolver may receive (#1103).
1
+ // The admitted principal a request-auth resolver may receive.
2
2
  //
3
3
  // Only the caller core attached from the admitting authorization counts
4
4
  // (`callerOf`, see connector-caller.ts). No argument, program or header can
@@ -1,3 +1,4 @@
1
+ import { sha256Hex } from "./bytes.js";
1
2
  import { ProtocolError, INVALID_PARAMS } from "@modelcontextprotocol/server";
2
3
  export const REQUEST_STATE_TTL_MS = 10 * 60_000;
3
4
  export const MAX_INPUT_ROUNDS = 3;
@@ -16,6 +17,5 @@ export function canonicalState(value) {
16
17
  : item);
17
18
  }
18
19
  export async function requestDigest(tool, args) {
19
- const bytes = new TextEncoder().encode(canonicalState({ tool, args }));
20
- return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
20
+ return sha256Hex(canonicalState({ tool, args }));
21
21
  }
@@ -67,7 +67,7 @@ export async function routeAccessTokens(context, manager) {
67
67
  const admin = await authorizeUiIdentity(request, baseUrl, opts.config.auth, "access token management", context.runtimeContext, opts.config.logger);
68
68
  if (!admin.ok)
69
69
  return admin.response;
70
- // Interactive maintainers alone manage agent tokens (#837); anyone else
70
+ // Interactive maintainers alone manage agent tokens; anyone else
71
71
  // gets the same private 404 every operator route gives them.
72
72
  if (!managesAgentTokens(opts, admin) || !admin.identity.principal) {
73
73
  return privateJson({ error: "not found" }, { status: 404 });
@@ -6,7 +6,7 @@ function activityToolVisible(access, event) {
6
6
  }
7
7
  /**
8
8
  * One disclosure rule for the timeline and the UI's last-call overlay. Only
9
- * maintainers read activity (#837), and only after the optional
9
+ * maintainers read activity, and only after the optional
10
10
  * read gate. They may see other principals' shared-connector history,
11
11
  * including request IDs, client name/version, and actor kind/id/namespace, on
12
12
  * plain /mcp and in every declared pool, within the tools that pool declares.
@@ -88,7 +88,7 @@ async function connect(context) {
88
88
  if (!authz.ok) {
89
89
  // Access sign-in is enforced at the edge. Clerk needs its own sign-in page.
90
90
  // The sign-in page returns to this link, which then sends a maintainer on
91
- // to the operator UI. Pool members cannot use that UI yet (#837), so
91
+ // to the operator UI. Pool members cannot use that UI yet, so
92
92
  // signing in there would strand them.
93
93
  if (authz.response.status === 401 &&
94
94
  authorizationCredential(request).kind === "absent" &&
@@ -107,7 +107,7 @@ async function connect(context) {
107
107
  if (authz.principalKey !== handoff.principal || !mayManageConnector(opts, authz, connector)) {
108
108
  return refuse("Sign in as the user who requested this connection and has permission to manage it.");
109
109
  }
110
- // The operator UI serves maintainers only until member views ship (#837).
110
+ // The operator UI serves maintainers only until member views ship.
111
111
  // A member's OAuth link starts here directly, as it does without a UI.
112
112
  const viaUi = Boolean(opts.config.ui) && admitsMaintainer(opts, authz);
113
113
  if (!connector.startAuth) {