@zackbart/connecta 0.28.0 → 0.29.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 (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1632 -1509
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +526 -0
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. package/templates/node/src/file-activity.ts +0 -182
@@ -17,7 +17,7 @@
17
17
  // runtime holds, because createConnecta passes the same two objects to both.
18
18
  // A scoped view holds no storage of its own: it reaches Storage through the
19
19
  // registry it delegates to, and the result stash is always the root's,
20
- // because its capacity is runtime-wide.
20
+ // because its capacity is deployment-wide.
21
21
  import { Context, Effect } from "effect";
22
22
  import { runEdge } from "./run.js";
23
23
  import { Logger, Storage } from "./services.js";
@@ -44,6 +44,13 @@ export function storageDelete(key) {
44
44
  catch: (error) => error,
45
45
  }));
46
46
  }
47
+ /** Atomic compare-and-set of one key, as `KVStorage.compareAndSet`. */
48
+ export function storageCompareAndSet(key, expected, next, options) {
49
+ return Storage.use((storage) => Effect.tryPromise({
50
+ try: () => Promise.resolve(storage.compareAndSet(key, expected, next, options)),
51
+ catch: (error) => error,
52
+ }));
53
+ }
47
54
  // One context per partition object, built on first use. A registry's options
48
55
  // object lives exactly as long as the registry, so the WeakMap never holds a
49
56
  // partition past its owner.
@@ -0,0 +1,52 @@
1
+ import type { ConnectorContext, Logger } from "./types.js";
2
+ /** One payload-free warning per configured connector, regardless of requests. */
3
+ export declare function shortSecretWarning(): (value: string | undefined, logger: Logger) => void;
4
+ export declare class SentSecrets {
5
+ private readonly values;
6
+ private matcher;
7
+ private unicode;
8
+ private readonly recipients;
9
+ private form;
10
+ /** The request receives existing and future credentials from every context. */
11
+ include(source: SentSecrets): void;
12
+ add(value: string): void;
13
+ /** Explicit secrets use the same floor as every other credential. */
14
+ secret(value: string): void;
15
+ /** Structural fields must be refused, never repaired into different URLs. */
16
+ contains(value: string, ignoreCase?: boolean): boolean;
17
+ containsUrl(value: string): boolean;
18
+ header(value: string): void;
19
+ request(input: RequestInfo | URL, init?: RequestInit): void;
20
+ text(value: string): string;
21
+ /** Skill supporting files may contain arbitrary bytes, including credential echoes. */
22
+ private blob;
23
+ /** Decode bounded windows with source spans, including twice-escaped JSON.
24
+ * Scratch space follows credential length, never the whole skill file. */
25
+ private escapedText;
26
+ /** Copy, including non-enumerable Error fields; never retain a raw cause. */
27
+ redact<T>(value: T): T;
28
+ /** Match across blocks before isError/JSON unwrapping concatenates them. */
29
+ private joinedContent;
30
+ }
31
+ export declare function sentSecretsFor(ctx: ConnectorContext): SentSecrets;
32
+ /** One identity per upstream request, including all discovery and call work. */
33
+ export declare function sentSecretsForRequest(scope: object, secrets?: SentSecrets): SentSecrets;
34
+ /** The agent-facing choke point. Apply to values before bridge serialization
35
+ * and to serialized wire text, so JSON escapes and structured strings share
36
+ * the same rule. Intake redaction protects caches independently of this edge. */
37
+ export declare function redactAgentOutput<T>(secrets: SentSecrets, value: T): T;
38
+ /** Wrap the complete operation table, including rejections. Adding an operation
39
+ * cannot add an unredacted exit. A direct invocation owns a fresh scope unless
40
+ * its HTTP request supplied one. */
41
+ export declare function agentOutputOperations<T extends Record<string, (...args: never[]) => Promise<unknown>>>(create: (scope: object) => T, requestScope?: object): T;
42
+ /** Slot reads cover custom handlers too, including keys put in query strings. */
43
+ export declare function trackCredentialReads(ctx: ConnectorContext): void;
44
+ export declare function redactSentSecrets<T>(ctx: ConnectorContext, value: T): T;
45
+ /** Sanitize listing facts before any cache or consumer receives them. A name
46
+ * cannot be rewritten without changing dispatch, and dropping one entry would
47
+ * publish a partial catalog, so refuse the complete listing instead. */
48
+ export declare function redactCatalog<T extends {
49
+ name: string;
50
+ }>(ctx: ConnectorContext, tools: T[]): T[];
51
+ /** Register only after the transport has assembled the request it sends. */
52
+ export declare function sentSecretsFetch(ctx: ConnectorContext, send?: typeof fetch): typeof fetch;
@@ -0,0 +1,474 @@
1
+ // Credentials used by one upstream request. Memory only; never part of a context's public
2
+ // shape, a failure record, storage, or a log. Web APIs only.
3
+ import { carryFailureFacts } from "./operator-record.js";
4
+ import { ConnectorCallError } from "./errors.js";
5
+ import { credentialUrlViews } from "./credential-url.js";
6
+ const REDACTED = "[redacted]";
7
+ const encoder = new TextEncoder();
8
+ const contexts = new WeakMap();
9
+ const requests = new WeakMap();
10
+ const wrappedCredentials = new WeakSet();
11
+ const sensitiveName = /key|token|secret|password|auth|signature|session/i;
12
+ const explicitSecretName = /secret|password/i;
13
+ // Short credentials can match protocol fields or legitimate configuration.
14
+ const MIN_SECRET_LENGTH = 8;
15
+ /** One payload-free warning per configured connector, regardless of requests. */
16
+ export function shortSecretWarning() {
17
+ let warned = false;
18
+ return (value, logger) => {
19
+ if (warned || !value || value.length >= MIN_SECRET_LENGTH)
20
+ return;
21
+ warned = true;
22
+ const fact = { code: "short_secret_not_redacted" };
23
+ logger.warn("[connecta] Credentials shorter than 8 characters are not redacted from echoes; use longer secrets.", fact);
24
+ };
25
+ }
26
+ function literal(value) {
27
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28
+ }
29
+ /** Percent escapes are case insensitive; the credential's other bytes are not. */
30
+ function wirePattern(value) {
31
+ return value
32
+ .split(/(%[0-9a-f]{2})/i)
33
+ .map((part) => /^%[0-9a-f]{2}$/i.test(part)
34
+ ? `%${part
35
+ .slice(1)
36
+ .split("")
37
+ .map((char) => (/[a-f]/i.test(char) ? `[${char.toUpperCase()}${char.toLowerCase()}]` : char))
38
+ .join("")}`
39
+ : literal(part))
40
+ .join("");
41
+ }
42
+ const jsonEscape = /\\(?:u[0-9a-fA-F]{4}|["\\/bfnrt])/g;
43
+ const shortEscape = {
44
+ '"': '"',
45
+ "\\": "\\",
46
+ "/": "/",
47
+ b: "\b",
48
+ f: "\f",
49
+ n: "\n",
50
+ r: "\r",
51
+ t: "\t",
52
+ };
53
+ function base64(value) {
54
+ let binary = "";
55
+ for (const byte of encoder.encode(value))
56
+ binary += String.fromCharCode(byte);
57
+ return btoa(binary);
58
+ }
59
+ export class SentSecrets {
60
+ values = new Set();
61
+ matcher;
62
+ unicode = false;
63
+ recipients = new Set();
64
+ form(value) {
65
+ if (this.values.has(value))
66
+ return;
67
+ this.values.add(value);
68
+ this.matcher = undefined;
69
+ this.unicode ||= [...value].some((char) => char.charCodeAt(0) > 127);
70
+ for (const recipient of this.recipients)
71
+ recipient.form(value);
72
+ }
73
+ /** The request receives existing and future credentials from every context. */
74
+ include(source) {
75
+ if (source === this)
76
+ return;
77
+ source.recipients.add(this);
78
+ for (const value of source.values)
79
+ this.form(value);
80
+ }
81
+ add(value) {
82
+ if (value.length < MIN_SECRET_LENGTH)
83
+ return;
84
+ for (const form of [value, `Bearer ${value}`, `token ${value}`]) {
85
+ this.form(form);
86
+ this.form(encodeURIComponent(form));
87
+ this.form(encodeURI(form));
88
+ this.form(new URLSearchParams({ value: form }).toString().slice(6));
89
+ const encoded = base64(form);
90
+ this.form(encoded);
91
+ this.form(encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""));
92
+ }
93
+ }
94
+ /** Explicit secrets use the same floor as every other credential. */
95
+ secret(value) {
96
+ this.add(value);
97
+ }
98
+ /** Structural fields must be refused, never repaired into different URLs. */
99
+ contains(value, ignoreCase = false) {
100
+ return [...this.values].some((secret) => new RegExp(wirePattern(secret), ignoreCase ? "i" : "").test(value));
101
+ }
102
+ containsUrl(value) {
103
+ const { hosts, components, joined } = credentialUrlViews(value);
104
+ return (hosts.some((host) => this.contains(host, true)) || [...components, ...joined].some((view) => this.contains(view)));
105
+ }
106
+ header(value) {
107
+ this.add(value);
108
+ const framed = /^(?:Bearer|token|Basic)\s+(.+)$/i.exec(value);
109
+ if (!framed)
110
+ return;
111
+ this.add(framed[1]);
112
+ if (/^Basic\s/i.test(value)) {
113
+ try {
114
+ const decoded = atob(framed[1].replace(/-/g, "+").replace(/_/g, "/"));
115
+ this.add(decoded);
116
+ const colon = decoded.indexOf(":");
117
+ if (colon !== -1) {
118
+ this.add(decoded.slice(0, colon));
119
+ this.secret(decoded.slice(colon + 1));
120
+ }
121
+ }
122
+ catch {
123
+ /* An invalid Basic value is still registered verbatim. */
124
+ }
125
+ }
126
+ else
127
+ this.secret(framed[1]);
128
+ }
129
+ request(input, init) {
130
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
131
+ for (const [name, value] of headers) {
132
+ if (explicitSecretName.test(name))
133
+ this.secret(value);
134
+ else if (name === "cookie" || sensitiveName.test(name))
135
+ this.header(value);
136
+ if (name === "cookie") {
137
+ for (const cookie of value.split(";")) {
138
+ const equals = cookie.indexOf("=");
139
+ if (equals !== -1)
140
+ this.add(cookie.slice(equals + 1).trim());
141
+ }
142
+ }
143
+ }
144
+ const url = new URL(input instanceof Request ? input.url : String(input));
145
+ for (const [name, value] of url.searchParams) {
146
+ if (explicitSecretName.test(name))
147
+ this.secret(value);
148
+ else if (sensitiveName.test(name))
149
+ this.add(value);
150
+ }
151
+ // The OAuth SDK sends token requests as form data. Do not read a body
152
+ // stream or clone a Request: registration must not consume its payload.
153
+ if (headers.get("content-type")?.startsWith("application/x-www-form-urlencoded") &&
154
+ (typeof init?.body === "string" || init?.body instanceof URLSearchParams)) {
155
+ for (const [name, value] of new URLSearchParams(init.body)) {
156
+ if (/^(?:client_secret|password)$/i.test(name))
157
+ this.secret(value);
158
+ else if (/^(?:refresh_token|code|client_assertion)$/i.test(name))
159
+ this.add(value);
160
+ }
161
+ }
162
+ }
163
+ text(value) {
164
+ if (this.values.size === 0)
165
+ return value;
166
+ const original = value;
167
+ // Replace longer forms first so a raw token cannot leave its prefix or
168
+ // encoded suffix behind. Literal matches only: ordinary diagnostics stay.
169
+ this.matcher ??= new RegExp([literal(REDACTED), ...[...this.values].sort((a, b) => b.length - a.length).map(wirePattern)].join("|"), "gu");
170
+ // A single pass never scans a newly inserted placeholder as credential
171
+ // text. Protect existing placeholders when another boundary runs too.
172
+ value = value.replace(this.matcher, REDACTED);
173
+ if (value.includes("\\"))
174
+ value = this.escapedText(value);
175
+ // Ordinary skill bytes, including example credential header names, stay
176
+ // exact. Header-line scrubbing belongs to an actual credential echo.
177
+ if (value === original)
178
+ return value;
179
+ return value.replace(/(^|[\r\n])([\t ]*(?:cookie|set-cookie|[a-z0-9-]*(?:key|token|secret|auth|signature|session)[a-z0-9-]*)\s*:\s*)[^\r\n]*/gi, `$1$2${REDACTED}`);
180
+ }
181
+ /** Skill supporting files may contain arbitrary bytes, including credential echoes. */
182
+ blob(value) {
183
+ let binary;
184
+ try {
185
+ binary = atob(value);
186
+ }
187
+ catch {
188
+ return this.text(value);
189
+ }
190
+ const forms = [...this.values].map((secret) => Array.from(encoder.encode(secret), (byte) => String.fromCharCode(byte)).join(""));
191
+ const pattern = new RegExp(forms
192
+ .sort((a, b) => b.length - a.length)
193
+ .map(literal)
194
+ .join("|"), "g");
195
+ let redacted = this.text(binary.replace(pattern, REDACTED));
196
+ if (this.unicode) {
197
+ // atob returns byte-valued code units. A UTF-8 view also detects mixed
198
+ // literal/JSON-escaped Unicode echoes. Never re-encode that view: an
199
+ // arbitrary supporting file may contain invalid UTF-8 or a leading BOM.
200
+ const view = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true }).decode(Uint8Array.from(redacted, (char) => char.charCodeAt(0)));
201
+ if (this.text(view) !== view)
202
+ redacted = REDACTED;
203
+ }
204
+ const encoded = redacted === binary ? value : btoa(redacted);
205
+ // A file's encoding can itself equal a credential. Withhold that file as
206
+ // a valid encoded placeholder, never insert prose into its base64 field.
207
+ if (this.text(encoded) === encoded)
208
+ return encoded;
209
+ const withheld = btoa(REDACTED);
210
+ return this.text(withheld) === withheld ? withheld : "";
211
+ }
212
+ /** Decode bounded windows with source spans, including twice-escaped JSON.
213
+ * Scratch space follows credential length, never the whole skill file. */
214
+ escapedText(source) {
215
+ const step = 65_536;
216
+ let longest = 0;
217
+ for (const value of this.values)
218
+ longest = Math.max(longest, value.length);
219
+ // Each of two Unicode escape layers expands a code unit at most sixfold.
220
+ const overlap = 36 * longest + 12;
221
+ const output = [];
222
+ let copied = 0;
223
+ for (let offset = 0; offset < source.length;) {
224
+ let view = source.slice(offset, offset + step + overlap);
225
+ let starts;
226
+ let ends;
227
+ const matches = [];
228
+ for (let pass = 0; pass < 2 && view.includes("\\"); pass++) {
229
+ const parts = [];
230
+ const nextStarts = new Uint32Array(view.length);
231
+ const nextEnds = new Uint32Array(view.length);
232
+ let cursor = 0;
233
+ let length = 0;
234
+ const copy = (end) => {
235
+ if (cursor < end)
236
+ parts.push(view.slice(cursor, end));
237
+ for (; cursor < end; cursor++, length++) {
238
+ nextStarts[length] = starts?.[cursor] ?? cursor;
239
+ nextEnds[length] = ends?.[cursor] ?? cursor + 1;
240
+ }
241
+ };
242
+ for (const match of view.matchAll(jsonEscape)) {
243
+ copy(match.index);
244
+ const code = match[0].slice(1);
245
+ parts.push(code.startsWith("u") ? String.fromCharCode(parseInt(code.slice(1), 16)) : shortEscape[code]);
246
+ nextStarts[length] = starts?.[match.index] ?? match.index;
247
+ cursor = match.index + match[0].length;
248
+ nextEnds[length++] = ends?.[cursor - 1] ?? cursor;
249
+ }
250
+ if (cursor === 0)
251
+ break;
252
+ copy(view.length);
253
+ view = parts.join("");
254
+ starts = nextStarts.subarray(0, length);
255
+ ends = nextEnds.subarray(0, length);
256
+ for (const match of view.matchAll(this.matcher)) {
257
+ const start = starts[match.index];
258
+ if (match[0] !== REDACTED && start < step)
259
+ matches.push({ start, end: ends[match.index + match[0].length - 1] });
260
+ }
261
+ }
262
+ matches.sort((a, b) => a.start - b.start || b.end - a.end);
263
+ let rewritten = "";
264
+ let cursor = 0;
265
+ for (let i = 0; i < matches.length; i++) {
266
+ const match = matches[i];
267
+ let end = match.end;
268
+ while (matches[i + 1] && matches[i + 1].start <= end)
269
+ end = Math.max(end, matches[++i].end);
270
+ rewritten += source.slice(offset + cursor, offset + match.start) + REDACTED;
271
+ cursor = end;
272
+ }
273
+ if (cursor) {
274
+ output.push(source.slice(copied, offset), rewritten);
275
+ copied = offset + cursor;
276
+ }
277
+ let boundary = step;
278
+ if (starts && ends) {
279
+ // Keep the next window at an original escape boundary. Starting at
280
+ // the second slash of a JSON pair changes its meaning and can leave
281
+ // an invalid escape immediately before a redaction placeholder.
282
+ let left = 0;
283
+ let right = ends.length;
284
+ while (left < right) {
285
+ const middle = (left + right) >>> 1;
286
+ if (ends[middle] <= step)
287
+ left = middle + 1;
288
+ else
289
+ right = middle;
290
+ }
291
+ if (left < starts.length)
292
+ boundary = Math.min(step, starts[left]);
293
+ }
294
+ offset += Math.max(boundary, cursor);
295
+ }
296
+ return output.length ? output.join("") + source.slice(copied) : source;
297
+ }
298
+ /** Copy, including non-enumerable Error fields; never retain a raw cause. */
299
+ redact(value) {
300
+ if (this.values.size === 0)
301
+ return value;
302
+ const seen = new Map();
303
+ let changed = false;
304
+ const text = (value) => {
305
+ const redacted = this.text(value);
306
+ changed ||= redacted !== value;
307
+ return redacted;
308
+ };
309
+ const visit = (item) => {
310
+ if (typeof item === "string")
311
+ return text(item);
312
+ if (item === null || typeof item !== "object")
313
+ return item;
314
+ const prior = seen.get(item);
315
+ if (prior)
316
+ return prior;
317
+ const copy = Array.isArray(item)
318
+ ? []
319
+ : item instanceof Error
320
+ ? Object.create(Object.getPrototypeOf(item))
321
+ : {};
322
+ seen.set(item, copy);
323
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(item))) {
324
+ // V8 lazily renders Error.stack through an own accessor. Snapshot it
325
+ // before rebuilding an error, so its diagnostic is redacted too.
326
+ if (key === "stack" && item instanceof Error && !("value" in descriptor)) {
327
+ Object.defineProperty(copy, key, { value: visit(item.stack), configurable: true, writable: true });
328
+ continue;
329
+ }
330
+ // JSON and errors carry data properties. A downstream-authored getter
331
+ // is not a safe way to expose a diagnostic to an agent.
332
+ if (!("value" in descriptor)) {
333
+ changed = true;
334
+ continue;
335
+ }
336
+ const redactedKey = text(key);
337
+ const blob = key === "blob" && "uri" in item && typeof descriptor.value === "string";
338
+ const field = key === "content"
339
+ ? this.joinedContent(descriptor.value)
340
+ : blob
341
+ ? this.blob(descriptor.value)
342
+ : descriptor.value;
343
+ changed ||= field !== descriptor.value;
344
+ Object.defineProperty(copy, redactedKey, {
345
+ ...descriptor,
346
+ value: blob ? field : visit(field),
347
+ ...(redactedKey !== key ? { configurable: true } : {}),
348
+ });
349
+ }
350
+ return item instanceof Error ? carryFailureFacts(item, copy) : copy;
351
+ };
352
+ const copy = visit(value);
353
+ return changed ? copy : value;
354
+ }
355
+ /** Match across blocks before isError/JSON unwrapping concatenates them. */
356
+ joinedContent(value) {
357
+ if (!Array.isArray(value))
358
+ return value;
359
+ const blocks = value.filter((block) => block?.type === "text" && typeof block.text === "string");
360
+ if (blocks.length < 2)
361
+ return value;
362
+ for (const separator of ["", "\n"]) {
363
+ const joined = blocks.map((block) => block.text).join(separator);
364
+ const redacted = this.text(joined);
365
+ if (redacted === blocks.map((block) => this.text(block.text)).join(separator))
366
+ continue;
367
+ let first = true;
368
+ return value.map((block) => {
369
+ if (!blocks.includes(block))
370
+ return block;
371
+ const text = first ? redacted : "";
372
+ first = false;
373
+ return { ...block, text };
374
+ });
375
+ }
376
+ return value;
377
+ }
378
+ }
379
+ export function sentSecretsFor(ctx) {
380
+ let secrets = contexts.get(ctx);
381
+ if (!secrets) {
382
+ secrets = new SentSecrets();
383
+ contexts.set(ctx, secrets);
384
+ }
385
+ sentSecretsForRequest(ctx.requestScope ?? ctx).include(secrets);
386
+ return secrets;
387
+ }
388
+ /** One identity per upstream request, including all discovery and call work. */
389
+ export function sentSecretsForRequest(scope, secrets) {
390
+ let request = requests.get(scope);
391
+ if (!request) {
392
+ request = secrets ?? new SentSecrets();
393
+ requests.set(scope, request);
394
+ }
395
+ else if (secrets) {
396
+ request.include(secrets);
397
+ }
398
+ return request;
399
+ }
400
+ /** The agent-facing choke point. Apply to values before bridge serialization
401
+ * and to serialized wire text, so JSON escapes and structured strings share
402
+ * the same rule. Intake redaction protects caches independently of this edge. */
403
+ export function redactAgentOutput(secrets, value) {
404
+ return secrets.redact(value);
405
+ }
406
+ /** Wrap the complete operation table, including rejections. Adding an operation
407
+ * cannot add an unredacted exit. A direct invocation owns a fresh scope unless
408
+ * its HTTP request supplied one. */
409
+ export function agentOutputOperations(create, requestScope) {
410
+ return Object.fromEntries(Object.keys(create(requestScope ?? {})).map((name) => [
411
+ name,
412
+ async (...args) => {
413
+ const scope = requestScope ?? {};
414
+ const secrets = sentSecretsForRequest(scope);
415
+ try {
416
+ return redactAgentOutput(secrets, await create(scope)[name](...args));
417
+ }
418
+ catch (error) {
419
+ throw redactAgentOutput(secrets, error);
420
+ }
421
+ },
422
+ ]));
423
+ }
424
+ /** Slot reads cover custom handlers too, including keys put in query strings. */
425
+ export function trackCredentialReads(ctx) {
426
+ if (!ctx.credential || wrappedCredentials.has(ctx))
427
+ return;
428
+ wrappedCredentials.add(ctx);
429
+ const credential = ctx.credential;
430
+ ctx.credential = {
431
+ async get(field) {
432
+ const value = await credential.get(field);
433
+ if (value) {
434
+ const secrets = sentSecretsFor(ctx);
435
+ if (field && explicitSecretName.test(field))
436
+ secrets.secret(value);
437
+ else
438
+ secrets.add(value);
439
+ }
440
+ return value;
441
+ },
442
+ async getAll() {
443
+ const values = await credential.getAll();
444
+ for (const [field, value] of Object.entries(values ?? {})) {
445
+ if (explicitSecretName.test(field))
446
+ sentSecretsFor(ctx).secret(value);
447
+ else
448
+ sentSecretsFor(ctx).add(value);
449
+ }
450
+ return values;
451
+ },
452
+ };
453
+ }
454
+ export function redactSentSecrets(ctx, value) {
455
+ return sentSecretsFor(ctx).redact(value);
456
+ }
457
+ /** Sanitize listing facts before any cache or consumer receives them. A name
458
+ * cannot be rewritten without changing dispatch, and dropping one entry would
459
+ * publish a partial catalog, so refuse the complete listing instead. */
460
+ export function redactCatalog(ctx, tools) {
461
+ sentSecretsFor(ctx);
462
+ const redacted = sentSecretsForRequest(ctx.requestScope ?? ctx).redact(tools);
463
+ if (redacted.some((tool, index) => tool.name !== tools[index].name)) {
464
+ throw new ConnectorCallError("connector_call_failed", "Downstream catalog contains a tool name that echoes a sent credential; refusing the complete catalog.", { retryable: false });
465
+ }
466
+ return redacted;
467
+ }
468
+ /** Register only after the transport has assembled the request it sends. */
469
+ export function sentSecretsFetch(ctx, send = fetch) {
470
+ return (input, init) => {
471
+ sentSecretsFor(ctx).request(input, init);
472
+ return send(input, init);
473
+ };
474
+ }