@zackbart/connecta 0.40.0 → 0.42.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 +148 -0
- package/bin/connecta.mjs +26 -17
- package/bin/meta-tool-names.d.mts +3 -1
- package/bin/meta-tool-names.mjs +4 -1
- package/bin/version.mjs +1 -1
- package/dist/activity.d.ts +32 -5
- package/dist/activity.js +32 -6
- package/dist/advertised-schema.js +15 -0
- package/dist/agent-observation.js +24 -2
- package/dist/agent-routes.js +33 -44
- package/dist/auth/clerk.d.ts +6 -1
- package/dist/auth/clerk.js +16 -0
- package/dist/auth/cloudflare-access.js +49 -7
- package/dist/auth/downstream-oauth/consent.js +79 -29
- package/dist/auth/downstream-oauth/grant-store.js +58 -7
- package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
- package/dist/auth/downstream-oauth/refresh-records.js +6 -5
- package/dist/auth/downstream-oauth.js +15 -4
- package/dist/auth/oauth-request-not-sent.js +4 -0
- package/dist/auth/static-oauth.js +77 -39
- package/dist/auth-elicitation.js +118 -61
- package/dist/authorization-handoff.js +36 -3
- package/dist/catalog-cache.js +352 -43
- package/dist/catalog-freshness.js +25 -0
- package/dist/catalog-service.js +408 -78
- package/dist/catalog.js +216 -62
- package/dist/config-defaults.d.ts +2 -1
- package/dist/config-defaults.js +4 -1
- package/dist/config.d.ts +53 -21
- package/dist/config.js +56 -17
- package/dist/connectors/remote-mcp/call.js +1 -1
- package/dist/connectors/remote-mcp/catalog.js +52 -28
- package/dist/connectors/remote-mcp/connection.js +113 -22
- package/dist/connectors/remote-mcp/oauth.js +2 -2
- package/dist/connectors/remote-mcp/resources.js +5 -2
- package/dist/connectors/remote-mcp/transport.js +24 -6
- package/dist/connectors/remote-mcp.js +14 -5
- package/dist/credential-contract.d.ts +3 -1
- package/dist/describe-config.d.ts +4 -2
- package/dist/describe-config.js +1 -0
- package/dist/discovery-health.js +26 -11
- package/dist/downstream-input.js +11 -2
- package/dist/errors.d.ts +60 -4
- package/dist/errors.js +88 -3
- package/dist/execute.js +491 -202
- package/dist/executor-admission.js +2 -0
- package/dist/executor-contract.js +6 -0
- package/dist/executor-result.js +3 -3
- package/dist/executors/quickjs-child.js +14 -5
- package/dist/executors/quickjs-runtime.d.ts +2 -0
- package/dist/executors/quickjs-runtime.js +37 -20
- package/dist/executors/quickjs.js +24 -7
- package/dist/guarded-transport.js +2 -0
- package/dist/guest-functions.js +5 -4
- package/dist/guest-runtime.js +37 -1
- package/dist/guest-types.d.ts +18 -0
- package/dist/http/client.js +14 -2
- package/dist/http/errors.js +9 -4
- package/dist/http/fields.js +17 -5
- package/dist/http/hatch.js +11 -3
- package/dist/http/node-transport.js +110 -4
- package/dist/http/oauth.js +17 -6
- package/dist/http/paging.js +1 -1
- package/dist/http/provider.js +2 -6
- package/dist/http/vendor.js +6 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +15 -21
- package/dist/invocation-failure.js +28 -0
- package/dist/invocation.js +167 -64
- package/dist/membership.d.ts +1 -1
- package/dist/meta-output.js +24 -18
- package/dist/meta-tool-names.d.ts +1 -1
- package/dist/meta-tool-names.js +1 -1
- package/dist/meta-tools.js +286 -73
- package/dist/module-contracts.d.ts +3 -1
- package/dist/native-recovery.js +4 -1
- package/dist/native-tools.js +177 -55
- package/dist/node.d.ts +7 -0
- package/dist/node.js +85 -4
- package/dist/oauth-handoff.js +53 -43
- package/dist/operation-activity.js +104 -0
- package/dist/operation-vocabulary.js +6 -0
- package/dist/operator-record.d.ts +9 -1
- package/dist/operator-record.js +16 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/program-result.js +2 -0
- package/dist/program-source.js +1 -1
- package/dist/program-writes.js +112 -19
- package/dist/provider.d.ts +1 -14
- package/dist/provider.js +0 -3
- package/dist/providers/_shared/google/kit.js +2 -2
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/breeze/api.js +2 -2
- package/dist/providers/breeze/index.js +1 -1
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/ccb/api.js +2 -2
- package/dist/providers/ccb/index.js +1 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/cloudflare/api.js +2 -2
- package/dist/providers/cloudflare/index.js +3 -1
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/index.js +1 -1
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/forms/index.js +2 -1
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/github/api.js +98 -6
- package/dist/providers/github/index.js +8 -3
- package/dist/providers/github/lib/auth.d.ts +3 -1
- package/dist/providers/github/lib/auth.js +43 -12
- package/dist/providers/github/lib/discovery.js +11 -1
- package/dist/providers/github/lib/operations.js +8 -0
- package/dist/providers/github/safety.js +39 -0
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +12 -5
- package/dist/providers/gmail/index.js +2 -1
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/granola/index.js +1 -1
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/index.js +3 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/planning-center/index.js +12 -6
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/quickbooks/api.js +28 -25
- package/dist/providers/quickbooks/index.js +3 -6
- package/dist/providers/quickbooks/lib/writes.js +55 -0
- package/dist/providers/quickbooks/safety.js +17 -5
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/index.js +2 -1
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/slides/index.js +3 -3
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/stripe/index.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/index.js +2 -1
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/vercel/api.js +3 -2
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/registry.d.ts +36 -4
- package/dist/registry.js +142 -30
- package/dist/resource-read.js +21 -2
- package/dist/result-preview.js +210 -0
- package/dist/result-seal.js +168 -0
- package/dist/route-names.js +53 -0
- package/dist/routes/activity-context.js +1 -0
- package/dist/routes/activity-disclosure.js +26 -4
- package/dist/routes/activity.js +2 -1
- package/dist/routes/connect.js +21 -13
- package/dist/routes/mcp.js +83 -35
- package/dist/routes/shared.d.ts +6 -0
- package/dist/routes/ui-config.js +1 -1
- package/dist/routes/ui-data.js +3 -2
- package/dist/runtime/call.js +15 -10
- package/dist/runtime/native-storage.js +12 -0
- package/dist/runtime/request-reads.js +66 -14
- package/dist/runtime/request-scope.js +38 -2
- package/dist/runtime/run.js +4 -1
- package/dist/sent-secrets.d.ts +20 -0
- package/dist/sent-secrets.js +49 -1
- package/dist/skill-origin.js +16 -0
- package/dist/skills.js +213 -57
- package/dist/storage/d1-driver.js +33 -5
- package/dist/storage/keys.js +16 -7
- package/dist/storage/sql-native.js +60 -3
- package/dist/storage/sql.js +6 -0
- package/dist/timing-trace.js +84 -0
- package/dist/tool-safety.d.ts +33 -0
- package/dist/tool-safety.js +51 -15
- package/dist/types.d.ts +36 -0
- package/dist/url-safety.js +38 -20
- package/dist/usage-guide.js +14 -13
- package/dist/utility-recovery.js +33 -0
- package/dist/validate.js +29 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +60 -15
- package/documentation/architecture.md +152 -43
- package/documentation/auth.md +160 -53
- package/documentation/code-mode.md +396 -119
- package/documentation/deploying.md +35 -10
- package/documentation/hosted-mcp-migration.md +9 -3
- package/documentation/integrating.md +27 -5
- package/documentation/meta-tools.md +761 -157
- package/documentation/operating.md +91 -4
- package/documentation/operator-ui.md +51 -2
- package/documentation/providers.md +34 -20
- package/examples/worker/README.md +3 -3
- package/guides/how-it-works.md +2 -1
- package/guides/safety-and-trust.md +1 -3
- package/package.json +10 -8
- package/templates/node/AGENTS.md +3 -3
- package/templates/node/README.md +1 -1
- package/templates/node/package.json +1 -1
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import { RequestScope } from "../runtime/request-scope.js";
|
|
3
|
+
import { hasTimingTraces, traceFor } from "../timing-trace.js";
|
|
4
|
+
import { utf8Length } from "../bytes.js";
|
|
5
|
+
// Request-wide totals include admission, credentials and catalog work. These
|
|
6
|
+
// I/O-spanning observations are not pure storage latency or CPU attribution.
|
|
7
|
+
function timedD1(family, statements, operation) {
|
|
8
|
+
return Effect.suspend(() => {
|
|
9
|
+
if (!hasTimingTraces())
|
|
10
|
+
return operation;
|
|
11
|
+
return Effect.flatMap(Effect.serviceOption(RequestScope), (owner) => {
|
|
12
|
+
const trace = Option.isSome(owner) ? traceFor(owner.value.identity) : undefined;
|
|
13
|
+
if (!trace)
|
|
14
|
+
return operation;
|
|
15
|
+
const start = performance.now();
|
|
16
|
+
let bytes = 0;
|
|
17
|
+
return operation.pipe(Effect.tap((value) => Effect.sync(() => {
|
|
18
|
+
// Count serialized application result bytes, not D1 wire bytes.
|
|
19
|
+
try {
|
|
20
|
+
bytes = utf8Length(JSON.stringify(value) ?? "");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
/* no payload retained */
|
|
24
|
+
}
|
|
25
|
+
})), Effect.ensuring(Effect.sync(() => trace.record(family, performance.now() - start, bytes, statements))));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
1
29
|
import { effectSqlDriver, sqlDriverEdge, sqlPromise } from "./sql-driver.js";
|
|
2
30
|
export function d1Driver(db) {
|
|
3
31
|
const prepare = (statement) => statement.params.length > 0 ? db.prepare(statement.sql).bind(...statement.params) : db.prepare(statement.sql);
|
|
4
32
|
return sqlDriverEdge(effectSqlDriver({
|
|
5
|
-
all: (statement) => sqlPromise(() => prepare(statement)
|
|
33
|
+
all: (statement) => timedD1("d1.read", 1, sqlPromise(() => prepare(statement)
|
|
6
34
|
.all()
|
|
7
|
-
.then((result) => result.results)),
|
|
8
|
-
run: (statement) => sqlPromise(() => prepare(statement)
|
|
35
|
+
.then((result) => result.results))),
|
|
36
|
+
run: (statement) => timedD1("d1.write", 1, sqlPromise(() => prepare(statement)
|
|
9
37
|
.run()
|
|
10
|
-
.then((result) => result.meta.changes ?? 0)),
|
|
38
|
+
.then((result) => result.meta.changes ?? 0))),
|
|
11
39
|
// D1's public binding API preserves per-statement changes, including zero;
|
|
12
40
|
// execute the whole array once. No withTransaction, RETURNING, or replay.
|
|
13
|
-
batch: (statements) => sqlPromise(() => db.batch(statements.map(prepare)).then((results) => results.map((result) => result.meta.changes ?? 0))),
|
|
41
|
+
batch: (statements) => timedD1("d1.batch", statements.length, sqlPromise(() => db.batch(statements.map(prepare)).then((results) => results.map((result) => result.meta.changes ?? 0)))),
|
|
14
42
|
}));
|
|
15
43
|
}
|
package/dist/storage/keys.js
CHANGED
|
@@ -58,7 +58,8 @@ export const scopes = {
|
|
|
58
58
|
export const RESULT_TTL_SECONDS = 900;
|
|
59
59
|
/**
|
|
60
60
|
* A stashed result: chunk 0 at `result:<id>`, chunk n at `result:<id>#<n>`.
|
|
61
|
-
* The value is the paging envelope `src/meta-tools.ts` writes and reads
|
|
61
|
+
* The value is the paging envelope `src/meta-tools.ts` writes and reads, or at
|
|
62
|
+
* chunk 0 the refusal `src/result-seal.ts` writes for a result it did not publish.
|
|
62
63
|
*/
|
|
63
64
|
export const resultKeys = {
|
|
64
65
|
family: {
|
|
@@ -176,6 +177,8 @@ export const connectorHealthKeys = {
|
|
|
176
177
|
discovery: (connectorId, partitionDigest) => validateStorageKey(`connector-health:v1:${connectorId}:${partitionDigest}:discovery`),
|
|
177
178
|
status: (connectorId, partitionDigest) => validateStorageKey(`connector-health:v1:${connectorId}:${partitionDigest}:status`),
|
|
178
179
|
};
|
|
180
|
+
const responseCacheLocatorPrefix = (id, config) => validateStorageKey(`response-cache:v1:${id}:${config}:`);
|
|
181
|
+
const responseCacheLocatorSeparator = ":";
|
|
179
182
|
/**
|
|
180
183
|
* SDK catalog response cache, complete manifests published after their chunks.
|
|
181
184
|
* Secret-bearing inputs reach these keys only through `secretDigest`.
|
|
@@ -189,13 +192,17 @@ export const responseCacheKeys = {
|
|
|
189
192
|
codec: textCodec,
|
|
190
193
|
ttl: {
|
|
191
194
|
kind: "configured",
|
|
192
|
-
by: "bounded downstream ttlMs; generations and hash refresh baselines expire after 48 hours",
|
|
195
|
+
by: "bounded downstream ttlMs; generations, refusal fences and hash refresh baselines expire after 48 hours",
|
|
193
196
|
},
|
|
194
197
|
durable: false,
|
|
195
198
|
},
|
|
196
199
|
prefix: (id) => validateStorageKey(`response-cache:v1:${id}:`),
|
|
197
200
|
generation: (id) => validateStorageKey(`response-cache:v1:${id}:generation`),
|
|
198
|
-
namespace: (id, config, generation) => validateStorageKey(
|
|
201
|
+
namespace: (id, config, generation) => validateStorageKey(`${responseCacheLocatorPrefix(id, config)}${generation}${responseCacheLocatorSeparator}`),
|
|
202
|
+
/** SQL binds these layout parts around the stored generation; it owns no delimiters. */
|
|
203
|
+
locatorPrefix: responseCacheLocatorPrefix,
|
|
204
|
+
locatorSeparator: responseCacheLocatorSeparator,
|
|
205
|
+
locatedEntry: (prefix, generation, partition) => validateStorageKey(`${prefix}${generation}${responseCacheLocatorSeparator}${partition}`),
|
|
199
206
|
entry: (namespace, partition) => validateStorageKey(`${namespace}${partition}`),
|
|
200
207
|
refreshDigest: (id, config, partition) => validateStorageKey(`response-cache:v1:${id}:refresh-digest:${config}:${partition}`),
|
|
201
208
|
chunk: (entry, revision, index) => validateStorageKey(`${entry}:chunk:${revision}:${index}`),
|
|
@@ -350,19 +357,21 @@ export const oauthRefreshActiveKeys = {
|
|
|
350
357
|
prefix: "oauth:refresh-active:",
|
|
351
358
|
holder: (epoch, holder) => validateStorageKey(`oauth:refresh-active:${epoch}:${holder}`),
|
|
352
359
|
};
|
|
353
|
-
/** Single-use browser links (`used`/`started`/`failed
|
|
360
|
+
/** Single-use browser links and forced resets (`used`/`started`/`failed`), and MRTR rounds. */
|
|
354
361
|
export const oauthConnectKeys = {
|
|
355
362
|
family: {
|
|
356
363
|
name: "oauth-connect",
|
|
357
364
|
scope: "connector",
|
|
358
|
-
prefixes: ["oauth:connect-used:", "oauth:request-used:"],
|
|
365
|
+
prefixes: ["oauth:connect-used:", "oauth:connect-reset:", "oauth:request-used:"],
|
|
359
366
|
version: { number: 1, in: "untagged" },
|
|
360
367
|
codec: textCodec,
|
|
361
|
-
//
|
|
362
|
-
|
|
368
|
+
// A link's remaining lifetime, fifteen minutes for a round, or thirty for a
|
|
369
|
+
// reset, which must outlive every link its flow can issue.
|
|
370
|
+
ttl: { kind: "fixed", seconds: 30 * 60 },
|
|
363
371
|
durable: false,
|
|
364
372
|
},
|
|
365
373
|
used: (nonce) => validateStorageKey(`oauth:connect-used:${nonce}`),
|
|
374
|
+
reset: (flow) => validateStorageKey(`oauth:connect-reset:${flow}`),
|
|
366
375
|
retry: (nonce) => validateStorageKey(`oauth:request-used:${nonce}`),
|
|
367
376
|
};
|
|
368
377
|
/**
|
|
@@ -27,7 +27,7 @@ import { Effect } from "effect";
|
|
|
27
27
|
import { activityBehaviorFacts, activityPackageVersion, activityClientFact, InvalidActivityCursorError, } from "../activity.js";
|
|
28
28
|
import { assertKnownOptions, ConfigError, keys, optionsOf } from "../config-schema.js";
|
|
29
29
|
import { agentFrictionForCode } from "../activity-friction.js";
|
|
30
|
-
import { accessTokenKeys, validateStorageKey } from "./keys.js";
|
|
30
|
+
import { accessTokenKeys, responseCacheKeys, validateStorageKey } from "./keys.js";
|
|
31
31
|
const sql = (text, ...params) => ({
|
|
32
32
|
sql: text,
|
|
33
33
|
params,
|
|
@@ -132,6 +132,34 @@ export function nativeSqlStorage(driver) {
|
|
|
132
132
|
return new Map(rows.map((row) => [row.key, textOf(row.value, row.value_bytes, "Stored access token metadata")]));
|
|
133
133
|
});
|
|
134
134
|
},
|
|
135
|
+
catalogSnapshot(keys, generationKey, prefix, suffixes) {
|
|
136
|
+
return Effect.gen(function* () {
|
|
137
|
+
if (keys.length < 1 || keys.length > 4 || suffixes.length > 3)
|
|
138
|
+
throw new RangeError("Catalog locator exceeds bounds");
|
|
139
|
+
for (const key of [...keys, generationKey, prefix, ...suffixes])
|
|
140
|
+
validateStorageKey(key);
|
|
141
|
+
yield* ensure();
|
|
142
|
+
const rows = yield* driver.all(sql(`WITH initial AS (
|
|
143
|
+
SELECT key, value FROM connecta_kv WHERE key IN (${keys.map(() => "?").join(",")}) AND ${live}
|
|
144
|
+
), suffixes(suffix) AS (VALUES ${suffixes.map(() => "(?)").join(",") || "(NULL)"}), wanted(key) AS (
|
|
145
|
+
SELECT key FROM initial UNION ALL
|
|
146
|
+
SELECT ? || (SELECT value FROM initial WHERE key = ?) || ? || suffix FROM suffixes
|
|
147
|
+
) SELECT key, ${textColumn("value")} FROM connecta_kv
|
|
148
|
+
WHERE key IN (SELECT key FROM wanted) AND ${live}`, ...keys, ...suffixes, prefix, generationKey, responseCacheKeys.locatorSeparator));
|
|
149
|
+
const values = new Map(rows.map((row) => [row.key, textOf(row.value, row.value_bytes, "Stored catalog locator")]));
|
|
150
|
+
for (const key of keys)
|
|
151
|
+
if (!values.has(key))
|
|
152
|
+
values.set(key, null);
|
|
153
|
+
const generation = values.get(generationKey);
|
|
154
|
+
if (generation)
|
|
155
|
+
for (const suffix of suffixes) {
|
|
156
|
+
const key = responseCacheKeys.locatedEntry(prefix, generation, suffix);
|
|
157
|
+
if (!values.has(key))
|
|
158
|
+
values.set(key, null);
|
|
159
|
+
}
|
|
160
|
+
return values;
|
|
161
|
+
});
|
|
162
|
+
},
|
|
135
163
|
getMany(keys) {
|
|
136
164
|
return Effect.gen(function* () {
|
|
137
165
|
for (const key of keys)
|
|
@@ -157,6 +185,25 @@ export function nativeSqlStorage(driver) {
|
|
|
157
185
|
return values;
|
|
158
186
|
});
|
|
159
187
|
},
|
|
188
|
+
setMany(entries, opts) {
|
|
189
|
+
return Effect.gen(function* () {
|
|
190
|
+
if (entries.length > 64)
|
|
191
|
+
throw new RangeError("setMany accepts at most 64 rows");
|
|
192
|
+
for (const [key] of entries)
|
|
193
|
+
validateStorageKey(key);
|
|
194
|
+
const expiry = expiryOptions(opts);
|
|
195
|
+
if (!entries.length)
|
|
196
|
+
return;
|
|
197
|
+
yield* ensure();
|
|
198
|
+
const changes = yield* driver.batch([
|
|
199
|
+
expirySweep(),
|
|
200
|
+
...entries.map(([key, value]) => sql(`INSERT INTO connecta_kv (key, value, expires_at_ms) VALUES (?, ?, ${expiry.expression})
|
|
201
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value, expires_at_ms = excluded.expires_at_ms`, key, value, expiry.value)),
|
|
202
|
+
]);
|
|
203
|
+
if (changes.length !== entries.length + 1 || changes.slice(1).some((changed) => changed !== 1))
|
|
204
|
+
throw new Error("SQL multi-set confirmation is incomplete");
|
|
205
|
+
});
|
|
206
|
+
},
|
|
160
207
|
set(key, value, opts) {
|
|
161
208
|
return Effect.gen(function* () {
|
|
162
209
|
validateStorageKey(key);
|
|
@@ -334,11 +381,20 @@ function activityRow(read) {
|
|
|
334
381
|
}
|
|
335
382
|
return row;
|
|
336
383
|
}
|
|
384
|
+
// Operation metadata shares the existing kind-tagged text slot; no new columns.
|
|
385
|
+
function parseOperation(raw) {
|
|
386
|
+
try {
|
|
387
|
+
return raw === null ? undefined : JSON.parse(raw);
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
337
393
|
function rowToEvent(row) {
|
|
338
394
|
// Rows written before `friction` had a column derive it from the code.
|
|
339
395
|
// Friction without a code — an oversized but successful result — exists
|
|
340
396
|
// only in the column, which is why the column exists.
|
|
341
|
-
const friction = row.friction ?? agentFrictionForCode(row.error_code ?? undefined);
|
|
397
|
+
const friction = row.event_kind === "operation" ? undefined : (row.friction ?? agentFrictionForCode(row.error_code ?? undefined));
|
|
342
398
|
const packageVersion = activityPackageVersion(row.package_version);
|
|
343
399
|
const clientName = activityClientFact(row.client_name, "name");
|
|
344
400
|
const clientVersion = activityClientFact(row.client_version, "version");
|
|
@@ -351,6 +407,7 @@ function rowToEvent(row) {
|
|
|
351
407
|
classification: row.classification,
|
|
352
408
|
resultBytes: row.result_bytes,
|
|
353
409
|
kind: row.event_kind,
|
|
410
|
+
operation: row.event_kind === "operation" ? parseOperation(row.drift_kind) : undefined,
|
|
354
411
|
pool: row.pool_name,
|
|
355
412
|
actorBasis: row.actor_basis,
|
|
356
413
|
drift: {
|
|
@@ -461,7 +518,7 @@ export function nativeSqlActivityStore(driver, kind, options = {}) {
|
|
|
461
518
|
// Stored beside the code rather than derived from it: a truncated
|
|
462
519
|
// result is friction on a call that succeeded, and
|
|
463
520
|
// `error_code IS NOT NULL` stays an honest count of failures.
|
|
464
|
-
event.friction ?? null, event.approval ?? null, activityPackageVersion(event.packageVersion) ?? null, event.serverName, event.serverVersion, activityClientFact(event.clientName, "name") ?? null, activityClientFact(event.clientVersion, "version") ?? null, event.deploymentId ?? null, facts.classification ?? null, facts.resultBytes ?? null, facts.kind ?? null, facts.drift?.kind ?? null, facts.drift?.addedTools ?? null, facts.drift?.removedTools ?? null, facts.drift?.changedTools ?? null, facts.pool ?? null, facts.actorBasis ?? null),
|
|
521
|
+
event.kind === "operation" ? null : (event.friction ?? null), event.approval ?? null, activityPackageVersion(event.packageVersion) ?? null, event.serverName, event.serverVersion, activityClientFact(event.clientName, "name") ?? null, activityClientFact(event.clientVersion, "version") ?? null, event.deploymentId ?? null, facts.classification ?? null, facts.resultBytes ?? null, facts.kind ?? null, facts.kind === "operation" ? JSON.stringify(facts.operation ?? null) : (facts.drift?.kind ?? null), facts.drift?.addedTools ?? null, facts.drift?.removedTools ?? null, facts.drift?.changedTools ?? null, facts.pool ?? null, facts.actorBasis ?? null),
|
|
465
522
|
sql(`DELETE FROM tool_call_activity WHERE id IN (
|
|
466
523
|
SELECT id FROM tool_call_activity
|
|
467
524
|
WHERE occurred_at_ms < ?
|
package/dist/storage/sql.js
CHANGED
|
@@ -10,6 +10,12 @@ export function sqlStorage(driver, kind) {
|
|
|
10
10
|
const root = {};
|
|
11
11
|
const native = {
|
|
12
12
|
...store,
|
|
13
|
+
setMany: (entries, options) => {
|
|
14
|
+
let write = store.setMany(entries, options);
|
|
15
|
+
for (const [key] of entries)
|
|
16
|
+
write = observedWrite(root, key, write);
|
|
17
|
+
return write;
|
|
18
|
+
},
|
|
13
19
|
set: (key, value, options) => observedWrite(root, key, store.set(key, value, options)),
|
|
14
20
|
delete: (key) => observedWrite(root, key, store.delete(key)),
|
|
15
21
|
compareAndSet: (key, expected, next, options) => observedWrite(root, key, store.compareAndSet(key, expected, next, options)),
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const families = new Set([
|
|
2
|
+
"catalog.cache.lookup",
|
|
3
|
+
"catalog.cache.hit",
|
|
4
|
+
"catalog.cache.expired",
|
|
5
|
+
"catalog.chunks",
|
|
6
|
+
"catalog.validated.hit",
|
|
7
|
+
"catalog.validated.miss",
|
|
8
|
+
"catalog.intake",
|
|
9
|
+
"catalog.decode",
|
|
10
|
+
"catalog.validate",
|
|
11
|
+
"catalog.accepted",
|
|
12
|
+
"catalog",
|
|
13
|
+
"admission",
|
|
14
|
+
"connector",
|
|
15
|
+
"result",
|
|
16
|
+
"d1.read",
|
|
17
|
+
"d1.write",
|
|
18
|
+
"d1.batch",
|
|
19
|
+
"memo.hit",
|
|
20
|
+
"memo.miss",
|
|
21
|
+
"memo.fresh",
|
|
22
|
+
]);
|
|
23
|
+
const traces = new WeakMap();
|
|
24
|
+
let active = 0;
|
|
25
|
+
let firstRequest = true;
|
|
26
|
+
export const hasTimingTraces = () => active > 0;
|
|
27
|
+
export const traceFor = (identity) => identity ? traces.get(identity) : undefined;
|
|
28
|
+
const finite = (value) => (Number.isFinite(value) && value >= 0 ? value : 0);
|
|
29
|
+
export class TimingTrace {
|
|
30
|
+
first;
|
|
31
|
+
started = performance.now();
|
|
32
|
+
totals = new Map();
|
|
33
|
+
closed = false;
|
|
34
|
+
constructor(first) {
|
|
35
|
+
this.first = first;
|
|
36
|
+
}
|
|
37
|
+
record(family, durationMs = 0, bytes = 0, statements = 0, absent = 0, items = 0) {
|
|
38
|
+
if (this.closed || !families.has(family))
|
|
39
|
+
return;
|
|
40
|
+
const total = this.totals.get(family) ?? { count: 0, durationMs: 0, bytes: 0, statements: 0, absent: 0, items: 0 };
|
|
41
|
+
total.count++;
|
|
42
|
+
total.durationMs += finite(durationMs);
|
|
43
|
+
total.bytes += finite(bytes);
|
|
44
|
+
total.statements += finite(statements);
|
|
45
|
+
total.absent += finite(absent);
|
|
46
|
+
total.items += finite(items);
|
|
47
|
+
this.totals.set(family, total);
|
|
48
|
+
}
|
|
49
|
+
finish(logger) {
|
|
50
|
+
if (this.closed)
|
|
51
|
+
return;
|
|
52
|
+
this.closed = true;
|
|
53
|
+
try {
|
|
54
|
+
logger.info("connecta.timing", {
|
|
55
|
+
version: 1,
|
|
56
|
+
isolateFirstRequest: this.first,
|
|
57
|
+
durationMs: performance.now() - this.started,
|
|
58
|
+
weakReferences: typeof WeakRef === "function" && typeof FinalizationRegistry === "function",
|
|
59
|
+
operations: Object.fromEntries(this.totals),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* Observability cannot change a request's outcome. */
|
|
64
|
+
}
|
|
65
|
+
this.totals.clear();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Called for each MCP request, including those not sampled. */
|
|
69
|
+
export function startTimingTrace(identity, sampleRate, logger) {
|
|
70
|
+
const first = firstRequest;
|
|
71
|
+
firstRequest = false;
|
|
72
|
+
if (sampleRate <= 0 || (sampleRate < 1 && Math.random() >= sampleRate))
|
|
73
|
+
return undefined;
|
|
74
|
+
const trace = new TimingTrace(first);
|
|
75
|
+
traces.set(identity, trace);
|
|
76
|
+
active++;
|
|
77
|
+
return () => {
|
|
78
|
+
if (traces.get(identity) !== trace)
|
|
79
|
+
return;
|
|
80
|
+
traces.delete(identity);
|
|
81
|
+
active--;
|
|
82
|
+
trace.finish(logger);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ToolDef } from "./types.js";
|
|
2
|
+
/** What deployment code may say about one tool. */
|
|
3
|
+
export type ClassificationOverride = "read" | "read-repeatable" | "write" | "hidden";
|
|
4
|
+
export declare function sourceClassification(tool: ToolDef): "read" | "write";
|
|
5
|
+
/** The source said this tool writes, as opposed to saying nothing. */
|
|
6
|
+
export declare function sourceDeclaresWrite(tool: ToolDef): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Deployment override, then fail-closed annotations. `"read"` speaks only where
|
|
9
|
+
* the source is silent: a declared write stays a write, because a read annotation
|
|
10
|
+
* lets a host resend the call. `"read-repeatable"` is the deployment accepting
|
|
11
|
+
* that resend for a tool it has checked, so it serves as a read regardless. Either
|
|
12
|
+
* way a source write stays write-like, so Connecta itself never replays it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function classifyTool(definition: ToolDef, override?: Exclude<ClassificationOverride, "hidden">): "read" | "write";
|
|
15
|
+
export declare function carryToolClassification(source: ToolDef, served: ToolDef, acceptRepeat?: boolean): ToolDef;
|
|
16
|
+
/** Replay and outcome policy only; admission and presentation use the served class. */
|
|
17
|
+
export declare function isWriteLike(tool: ToolDef): boolean;
|
|
18
|
+
/** The surface that runs a read-only program: it never dispatches a write-like call. */
|
|
19
|
+
export declare const READ_PROGRAM_SOURCE: "execute_read_code";
|
|
20
|
+
/**
|
|
21
|
+
* What a read program refuses: anything write-like, unless the deployment accepted
|
|
22
|
+
* repeats. A plain `"read"` over a source write stays out, because only
|
|
23
|
+
* `"read-repeatable"` says a host's resend of the program is acceptable.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readProgramRefuses(tool: ToolDef): boolean;
|
|
26
|
+
export declare function cloneTool(tool: ToolDef): ToolDef;
|
|
27
|
+
/**
|
|
28
|
+
* The only permission decision for a connector call. Approval is client-owned.
|
|
29
|
+
* A read-only endpoint refuses every write on every surface; a writable one
|
|
30
|
+
* sends writes through call_destructive_tool and writing programs, never call_tool
|
|
31
|
+
* or a read program.
|
|
32
|
+
*/
|
|
33
|
+
export declare function writeRefusal(classification: ToolDef["classification"], surface: string, readOnly?: boolean): "read_only_endpoint" | "destructive_tool_requires_approval" | undefined;
|
package/dist/tool-safety.js
CHANGED
|
@@ -1,33 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
function sourceFactsOf(annotations) {
|
|
2
|
+
const readOnly = annotations?.readOnlyHint;
|
|
3
|
+
const destructive = annotations?.destructiveHint;
|
|
4
|
+
if (readOnly === true && destructive !== true)
|
|
5
|
+
return "read";
|
|
6
|
+
// Anything but a clean read or silence counts as declared, including contradictory
|
|
7
|
+
// or malformed hints: an override must not turn an unclear statement into a read.
|
|
8
|
+
return readOnly === undefined && destructive !== true ? "silent" : "declared_write";
|
|
9
|
+
}
|
|
10
|
+
// Source policy is fail-closed annotations, with silence kept apart from a
|
|
11
|
+
// declared write. Deployment overrides never enter it. It is private metadata,
|
|
12
|
+
// not catalog data. Keep it through host-owned clones without exposing it to
|
|
13
|
+
// tools or storage.
|
|
14
|
+
const sourceFacts = new WeakMap();
|
|
15
|
+
const factsFor = (tool) => sourceFacts.get(tool) ?? sourceFactsOf(tool.annotations);
|
|
16
|
+
export function sourceClassification(tool) {
|
|
17
|
+
return factsFor(tool) === "read" ? "read" : "write";
|
|
18
|
+
}
|
|
19
|
+
/** The source said this tool writes, as opposed to saying nothing. */
|
|
20
|
+
export function sourceDeclaresWrite(tool) {
|
|
21
|
+
return factsFor(tool) === "declared_write";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Deployment override, then fail-closed annotations. `"read"` speaks only where
|
|
25
|
+
* the source is silent: a declared write stays a write, because a read annotation
|
|
26
|
+
* lets a host resend the call. `"read-repeatable"` is the deployment accepting
|
|
27
|
+
* that resend for a tool it has checked, so it serves as a read regardless. Either
|
|
28
|
+
* way a source write stays write-like, so Connecta itself never replays it.
|
|
29
|
+
*/
|
|
2
30
|
export function classifyTool(definition, override) {
|
|
3
|
-
if (override
|
|
31
|
+
if (override === "read-repeatable")
|
|
32
|
+
return "read";
|
|
33
|
+
if (override === "write" || (override === "read" && !sourceDeclaresWrite(definition)))
|
|
4
34
|
return override;
|
|
5
|
-
return definition
|
|
6
|
-
? "read"
|
|
7
|
-
: "write";
|
|
8
|
-
}
|
|
9
|
-
// Source policy is fail-closed annotations.
|
|
10
|
-
// Deployment overrides never enter it. It is private metadata, not catalog data.
|
|
11
|
-
// Keep it through host-owned clones without exposing it to tools or storage.
|
|
12
|
-
const sourceClassifications = new WeakMap();
|
|
13
|
-
export function sourceClassification(tool) {
|
|
14
|
-
return sourceClassifications.get(tool) ?? classifyTool(tool);
|
|
35
|
+
return sourceClassification(definition);
|
|
15
36
|
}
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
// Tools a deployment set to `"read-repeatable"`, private like the source facts.
|
|
38
|
+
const repeatAccepted = new WeakSet();
|
|
39
|
+
export function carryToolClassification(source, served, acceptRepeat = repeatAccepted.has(source)) {
|
|
40
|
+
sourceFacts.set(served, factsFor(source));
|
|
41
|
+
if (acceptRepeat)
|
|
42
|
+
repeatAccepted.add(served);
|
|
18
43
|
return served;
|
|
19
44
|
}
|
|
20
45
|
/** Replay and outcome policy only; admission and presentation use the served class. */
|
|
21
46
|
export function isWriteLike(tool) {
|
|
22
47
|
return tool.classification !== "read" || sourceClassification(tool) === "write";
|
|
23
48
|
}
|
|
49
|
+
/** The surface that runs a read-only program: it never dispatches a write-like call. */
|
|
50
|
+
export const READ_PROGRAM_SOURCE = "execute_read_code";
|
|
51
|
+
/**
|
|
52
|
+
* What a read program refuses: anything write-like, unless the deployment accepted
|
|
53
|
+
* repeats. A plain `"read"` over a source write stays out, because only
|
|
54
|
+
* `"read-repeatable"` says a host's resend of the program is acceptable.
|
|
55
|
+
*/
|
|
56
|
+
export function readProgramRefuses(tool) {
|
|
57
|
+
return isWriteLike(tool) && !repeatAccepted.has(tool);
|
|
58
|
+
}
|
|
24
59
|
export function cloneTool(tool) {
|
|
25
60
|
return carryToolClassification(tool, structuredClone(tool));
|
|
26
61
|
}
|
|
27
62
|
/**
|
|
28
63
|
* The only permission decision for a connector call. Approval is client-owned.
|
|
29
64
|
* A read-only endpoint refuses every write on every surface; a writable one
|
|
30
|
-
* sends writes through call_destructive_tool and programs, never call_tool
|
|
65
|
+
* sends writes through call_destructive_tool and writing programs, never call_tool
|
|
66
|
+
* or a read program.
|
|
31
67
|
*/
|
|
32
68
|
export function writeRefusal(classification, surface, readOnly = false) {
|
|
33
69
|
if (classification === "read")
|
package/dist/types.d.ts
CHANGED
|
@@ -335,6 +335,12 @@ export type ConnectorSkillResourceContents = {
|
|
|
335
335
|
text?: never;
|
|
336
336
|
});
|
|
337
337
|
/** The whole plugin contract — the one open seam. */
|
|
338
|
+
/** A complete cached catalog and the times it was fetched and expires, in epoch milliseconds. */
|
|
339
|
+
export interface ConnectorCatalogPeek {
|
|
340
|
+
tools: ToolDef[];
|
|
341
|
+
fetchedAt: number;
|
|
342
|
+
expiresAt: number;
|
|
343
|
+
}
|
|
338
344
|
export interface Connector {
|
|
339
345
|
/** Serve only read-classified tools on every endpoint. Defaults to false. */
|
|
340
346
|
readOnly?: boolean;
|
|
@@ -399,6 +405,16 @@ export interface Connector {
|
|
|
399
405
|
* fetched lazily over the network and are not known at construction time.
|
|
400
406
|
*/
|
|
401
407
|
staticTools?: ToolDef[];
|
|
408
|
+
/**
|
|
409
|
+
* Optional: the newest complete catalog this context could be served, fresh
|
|
410
|
+
* or stale, read from storage alone. No transport, no authorization and no
|
|
411
|
+
* writes. Discovery lists its result and marks it; call resolution never
|
|
412
|
+
* reads it. `remoteMcp()` implements it. A custom connector omits it and
|
|
413
|
+
* discovery falls back to the catalog core retains for an authenticated owner.
|
|
414
|
+
* A wrapper that replaces `listTools` must replace or remove this as well, or
|
|
415
|
+
* discovery lists what the wrapper would have changed.
|
|
416
|
+
*/
|
|
417
|
+
peekCatalog?(ctx: ConnectorContext): Promise<ConnectorCatalogPeek | undefined>;
|
|
402
418
|
/**
|
|
403
419
|
* Optional: what this connector is, for `Connecta.describeConfig()` and the
|
|
404
420
|
* operator surfaces built on it — where it points, how it authenticates,
|
|
@@ -616,6 +632,21 @@ export interface Executor {
|
|
|
616
632
|
* rewrite; set this to say the same thing minification-proof.
|
|
617
633
|
*/
|
|
618
634
|
readonly name?: string;
|
|
635
|
+
/**
|
|
636
|
+
* The run's wall-clock deadline, in whole milliseconds, when this sandbox has
|
|
637
|
+
* one: time from `execute()` to its own timeout result. Connecta ends a
|
|
638
|
+
* program's reads just before it, so a slow read becomes a catchable
|
|
639
|
+
* `timeout` rather than the whole run's failure. Omit it if the sandbox
|
|
640
|
+
* has no fixed deadline. See documentation/code-mode.md#cancellation-and-limits.
|
|
641
|
+
*/
|
|
642
|
+
readonly deadlineMs?: number;
|
|
643
|
+
/**
|
|
644
|
+
* The largest stored result, in UTF-8 bytes, that one program may read whole
|
|
645
|
+
* with `connecta.result(id, { all: true })` and still have budget left to use
|
|
646
|
+
* it. Default 1.5 MiB. Set it lower when a tighter CPU or memory limit would
|
|
647
|
+
* end the run first.
|
|
648
|
+
*/
|
|
649
|
+
readonly maxReadBytes?: number;
|
|
619
650
|
execute(code: string, providers: ExecutorProvider[]): Promise<ExecuteResult>;
|
|
620
651
|
/** Release runtime resources. Node's built-in pool implements this. */
|
|
621
652
|
close?(): void | Promise<void>;
|
|
@@ -842,6 +873,11 @@ export interface InboundAuth {
|
|
|
842
873
|
kind: string;
|
|
843
874
|
/** This provider may admit a human identity to operator mutation routes. */
|
|
844
875
|
interactiveOperator?: true;
|
|
876
|
+
/**
|
|
877
|
+
* This provider checks credentials against the deployment's own URL and was
|
|
878
|
+
* given none, so construction refuses a configuration without `publicUrl`.
|
|
879
|
+
*/
|
|
880
|
+
requiresPublicUrl?: true;
|
|
845
881
|
/**
|
|
846
882
|
* Recognize credential syntax or trusted runtime context, without verifying
|
|
847
883
|
* it or doing I/O. The first recognizing provider owns the verdict, including
|
package/dist/url-safety.js
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* legacy IPv4 spelling (`2130706433`, `0x7f.1`, `0177.0.0.1`) into dotted
|
|
14
14
|
* decimal and every IPv6 literal into compressed hex, so one canonical form is
|
|
15
15
|
* all this module reads. It never resolves a name: that needs DNS, which the
|
|
16
|
-
* Workers-safe core cannot do
|
|
17
|
-
*
|
|
16
|
+
* Workers-safe core cannot do. The owned Node transport additionally resolves
|
|
17
|
+
* and pins learned destinations at connect time. Pure Web API, no `node:` import.
|
|
18
18
|
*/
|
|
19
19
|
function classifyIpv4(octets) {
|
|
20
|
-
const [a = 0, b = 0] = octets;
|
|
20
|
+
const [a = 0, b = 0, c = 0] = octets;
|
|
21
21
|
if (a === 127)
|
|
22
22
|
return "loopback";
|
|
23
23
|
if (a === 0 ||
|
|
@@ -25,7 +25,14 @@ function classifyIpv4(octets) {
|
|
|
25
25
|
(a === 172 && b >= 16 && b <= 31) ||
|
|
26
26
|
(a === 192 && b === 168) ||
|
|
27
27
|
(a === 169 && b === 254) ||
|
|
28
|
-
(a === 100 && b >= 64 && b <= 127)
|
|
28
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
29
|
+
(a === 192 && b === 0 && (c === 0 || c === 2)) || // IETF protocol, documentation
|
|
30
|
+
(a === 192 && b === 88 && c === 99) || // 6to4 relay anycast
|
|
31
|
+
(a === 198 && (b === 18 || b === 19)) || // benchmarking
|
|
32
|
+
(a === 198 && b === 51 && c === 100) || // documentation
|
|
33
|
+
(a === 203 && b === 0 && c === 113) || // documentation
|
|
34
|
+
a >= 224 // multicast, reserved, broadcast
|
|
35
|
+
) {
|
|
29
36
|
return "private";
|
|
30
37
|
}
|
|
31
38
|
return "public";
|
|
@@ -61,24 +68,35 @@ function ipv6Pieces(host) {
|
|
|
61
68
|
return [...head, ...Array.from({ length: missing }, () => 0), ...tail];
|
|
62
69
|
}
|
|
63
70
|
function classifyIpv6(pieces) {
|
|
64
|
-
const [
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
const [p0 = 0, p1 = 0, p2 = 0, p3 = 0, p4 = 0, p5 = 0, p6 = 0, p7 = 0] = pieces;
|
|
72
|
+
const v4 = (high, low) => classifyIpv4([high >> 8, high & 0xff, low >> 8, low & 0xff]);
|
|
73
|
+
// A transitional form reaches, at most, the IPv4 address it carries. Only a
|
|
74
|
+
// mapped address can reach this host, so only it stays `loopback`; every
|
|
75
|
+
// other non-public carrier is `private`, keeping it out of a loopback
|
|
76
|
+
// connector's learned URLs.
|
|
77
|
+
const carried = (high, low) => (v4(high, low) === "public" ? "public" : "private");
|
|
78
|
+
const zeros = p0 === 0 && p1 === 0 && p2 === 0 && p3 === 0;
|
|
79
|
+
if (zeros && p4 === 0) {
|
|
80
|
+
if (p5 === 0xffff)
|
|
81
|
+
return v4(p6, p7); // ::ffff:a.b.c.d mapped
|
|
82
|
+
if (p5 === 0) {
|
|
83
|
+
if (p6 === 0 && p7 === 1)
|
|
84
|
+
return "loopback"; // ::1
|
|
85
|
+
return carried(p6, p7); // :: and ::a.b.c.d compatible
|
|
86
|
+
}
|
|
71
87
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
if (zeros && p4 === 0xffff && p5 === 0)
|
|
89
|
+
return carried(p6, p7); // ::ffff:0:a.b.c.d translated
|
|
90
|
+
if (p0 === 0x64 && p1 === 0xff9b && p2 === 0 && p3 === 0 && p4 === 0 && p5 === 0)
|
|
91
|
+
return carried(p6, p7); // NAT64
|
|
92
|
+
if (p0 === 0x2002)
|
|
93
|
+
return carried(p1, p2); // 6to4
|
|
94
|
+
// Global unicast is 2000::/3. Outside it: unique-local, link-local,
|
|
95
|
+
// site-local, multicast, local-use NAT64, reserved. Inside it, special-purpose
|
|
96
|
+
// 2001::/23 and documentation 2001:db8::/32 and 3fff::/20 are not destinations.
|
|
97
|
+
if ((p0 & 0xe000) !== 0x2000 || (p0 === 0x2001 && (p1 < 0x200 || p1 === 0xdb8)) || (p0 === 0x3fff && p1 < 0x1000)) {
|
|
98
|
+
return "private";
|
|
77
99
|
}
|
|
78
|
-
if ((first & 0xfe00) === 0xfc00)
|
|
79
|
-
return "private"; // fc00::/7
|
|
80
|
-
if ((first & 0xffc0) === 0xfe80)
|
|
81
|
-
return "private"; // fe80::/10
|
|
82
100
|
return "public";
|
|
83
101
|
}
|
|
84
102
|
/**
|