@zackbart/connecta 0.28.1 → 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.
- package/CHANGELOG.md +788 -5
- package/README.md +62 -42
- package/bin/connecta.mjs +110 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1584 -1580
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +93 -71
- package/dist/auth-elicitation.js +283 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +368 -224
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +631 -0
- package/dist/config.js +503 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +84 -35
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1774 -320
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +24 -256
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +117 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +110 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +320 -54
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +137 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +434 -0
- package/documentation/architecture.md +664 -231
- package/documentation/auth.md +936 -546
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +120 -54
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +133 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/basecamp.js +0 -445
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
package/dist/runtime/storage.js
CHANGED
|
@@ -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
|
|
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
|
+
}
|