@zackbart/connecta 0.37.1 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +2 -2
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth.js +19 -153
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/oauth-grant.js +11 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +2 -2
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-service.js +1 -1
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +41 -14
- package/dist/config.js +21 -71
- package/dist/connectors/api-connector.js +4 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/remote-mcp.js +10 -43
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/downstream-input.js +2 -2
- package/dist/execute.js +22 -13
- package/dist/executor-result.js +2 -1
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-runtime.js +5 -3
- package/dist/guest-types.d.ts +5 -5
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +2 -2
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +1 -1
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +1 -1
- package/dist/http/safety.js +1 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +1 -7
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/invocation-auth.js +2 -1
- package/dist/meta-tools.js +27 -27
- package/dist/native-tools.js +2 -1
- package/dist/oauth-handoff.js +2 -2
- package/dist/operation-vocabulary.js +1 -1
- package/dist/operator-record.js +1 -49
- package/dist/operator-ui/contract.d.ts +2 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +1 -3
- package/dist/providers/_shared/rest/value-safety.js +26 -28
- package/dist/providers/breeze/api.js +2 -7
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/tools.js +3 -4
- package/dist/providers/ccb/api.js +1 -6
- package/dist/providers/ccb/index.js +2 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +2 -2
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/docs/tools.js +2 -10
- package/dist/providers/drive/tools.js +12 -25
- package/dist/providers/forms/tools.js +1 -9
- package/dist/providers/gmail/tools.js +5 -21
- package/dist/providers/overflow/api.js +1 -3
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +2 -1
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/sheets/tools.js +1 -9
- package/dist/providers/slides/tools.js +4 -13
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/tithely/api.js +2 -7
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +1 -6
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/tools.js +2 -1
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +5 -7
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +1 -1
- package/dist/routes/mcp.js +4 -3
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +4 -3
- package/dist/routes/shared.d.ts +8 -8
- package/dist/routes/shared.js +9 -9
- package/dist/routes/ui-config.js +1 -1
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +1 -1
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +8 -35
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +16 -125
- package/dist/storage/sql-native.js +0 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +1 -12
- package/dist/tool-error.js +4 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/documentation/README.md +6 -42
- package/documentation/architecture.md +86 -496
- package/documentation/auth.md +12 -25
- package/documentation/code-mode.md +90 -66
- package/documentation/deploying.md +19 -40
- package/documentation/integrating.md +4 -19
- package/documentation/meta-tools.md +6 -6
- package/documentation/migration-0.33.md +5 -5
- package/documentation/migration-0.34.md +2 -4
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +19 -119
- package/documentation/operator-ui.md +3 -4
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/examples/worker/AGENTS.md +4 -6
- package/examples/worker/README.md +20 -115
- package/guides/connecting-agents.md +7 -30
- package/guides/how-it-works.md +2 -2
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.38.0 — 2026-10-28
|
|
6
|
+
|
|
7
|
+
0.38.0 is a minor release that retires the old storage and OAuth layout migrations, adds a configurable program return cap, makes in-program skill calls more forgiving, and moves the Eve helper to Eve 0.86.
|
|
8
|
+
|
|
9
|
+
Breaking: the pre-layout-3 OAuth grant converter, the Workers KV → D1 copy APIs and script, and the Node JSON-state import API and `connecta migrate-state` command are removed. A deployment still holding old state must first upgrade through 0.37.1, run `copyKvToD1` or `migrate-state` as needed, and open every OAuth grant once on that version before moving to 0.38.0; otherwise old OAuth grants need fresh consent and old KV or JSON state is not imported. Retired pause, identity, grant and trust options still fail construction, now as ordinary unknown options without migration hints. `documentation/migration-retired-layouts.md` covers verification and recovery. Deployments that already run on 0.37.x layouts need no change.
|
|
10
|
+
|
|
11
|
+
`execute.maxResultChars` sets how many serialized characters an `execute_code` program may return before Connecta pages it. The default stays 24,000, and paging notices now ask for pages sized to the configured cap. Programs can call `connecta.skill({ name })` and `connecta.skills(...)` as well as `connecta.skill(name)`, and the `execute_code` description states each host-call deadline truthfully when call and host-call timeouts differ.
|
|
12
|
+
|
|
13
|
+
Also breaking: `@zackbart/connecta-eve` now requires Eve `>=0.86.0 <0.87.0`, so Eve 0.84 apps upgrade Eve to 0.86.0 together with the helper. Eve 0.86.0 still pins undici 8.10.2, so pnpm apps rekey their override to `"eve@0.86.0>undici": "8.12.3"`; npm apps keep the same override. The rest of the release is contributor-facing: refreshed development dependencies, a slimmer test suite, consolidated internals and trimmed documentation, with no runtime behavior change.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Add `execute.maxResultChars`, the serialized characters an `execute_code` program may return before Connecta stashes the return behind a paging notice. The default stays 24,000; values from 4,000 to 1,000,000 are accepted, and anything else fails at construction. Emitted text shares the configured cap, and the paging notices that `execute_code` and direct calls return ask for pages sized to it instead of a fixed 3,829 bytes, never more than the stashed call's `maxResultBytes` or the 32 KiB bridge ceiling. A connector whose cap is below 3,829 bytes now gets a notice asking for the page size it actually serves. `describeConfig()` and the operator settings report it. Stash pages still obey each call's `maxResultBytes`. Connecta has no response-size limit, so keep the cap below what every client accepts: Claude Code passes at most 50,000 characters of text.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Accept the shapes models copy from the top-level `skills` tool into programs. `connecta.skill({ name })` now works like `connecta.skill(name)`, and `connecta.skills` is an undeclared alias of `connecta.skill` with the same host-call accounting, so neither costs a failed run. Any other argument fails `invalid_args` naming `connecta.skill("usage")`. The `execute_code` description, the server instructions, the usage guide and the program-error hint now show the in-program form `connecta.skill("usage")` and call the top-level one the skills tool, instead of giving `skills({ name: "usage" })` beside the guest API.
|
|
22
|
+
- Consolidate internal helpers and remove unused code without changing runtime behavior.
|
|
23
|
+
- Trim historical narration and duplicate procedures from documentation, correct contributor evidence references, and remove unreferenced eval baselines.
|
|
24
|
+
- Refresh development dependencies: TypeScript 6.0, Vitest 5 with Vite 8.3, `@cloudflare/vitest-plugin` 1.4 (renamed from `@cloudflare/vitest-pool-workers`) with Wrangler 4.151, Playwright 1.64, and current oxlint, oxfmt, knip, esbuild, tsx, React and Radix releases, in core and the Eve helper. Published dependency and peer ranges are unchanged, and deployments need no change.
|
|
25
|
+
- **Breaking:** `@zackbart/connecta-eve` now requires Eve 0.86 (`>=0.86.0 <0.87.0`) and is tested against Eve 0.86.0; Eve 0.84 apps must upgrade Eve to 0.86.0 together with the helper. Eve 0.86.0 still pins undici 8.10.2, so pnpm apps rekey their override to `"eve@0.86.0>undici": "8.12.3"`; npm apps keep the same override.
|
|
26
|
+
- Contributor test suite slimmed: about 100 regression, lifetime, parity and meta-guard suites are removed, and workerd now runs only the suites that need real Workers bindings or isolates. No runtime behavior changes.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- The `execute_code` description no longer overstates the deadline of `connecta.result`, `connecta.skill` and `connecta.read`. When `calls.defaultTimeoutMs` differs from `execute.hostCallTimeoutMs`, it states both, for example `Limits: 20 host calls, 10 writes, 25s/call (15s result/skill/read).`, instead of advertising the call default for every host call. Enforcement is unchanged; deployments without `calls.defaultTimeoutMs` see the same `15s/host call` text as before.
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
|
|
34
|
+
- **Breaking:** Remove the pre-layout-3 OAuth grant converter and cleanup, Workers KV → D1 copy APIs/types/script, and Node JSON-state import API and `connecta migrate-state` command. Retired pause, identity, grant, and trust options still fail construction as ordinary unknown options, without migration hints. Before upgrading, deployments with old state must upgrade through 0.37.1, run `copyKvToD1` or `migrate-state` as needed, and open every OAuth grant once on that version. Old OAuth grants otherwise require fresh consent; old KV/JSON state is not imported. Follow `documentation/migration-retired-layouts.md` for verification and recovery.
|
|
35
|
+
|
|
5
36
|
## 0.37.1 — 2026-10-28
|
|
6
37
|
|
|
7
38
|
0.37.1 is a patch release of fixes and request-path performance work. It needs no configuration change, and deployments on 0.37.0 can upgrade in place. One type change is additive: connector status gains a `per_caller` state, so code that switches exhaustively over status states must handle it.
|
package/README.md
CHANGED
|
@@ -91,8 +91,8 @@ on Node, in Docker, or on Cloudflare Workers. The
|
|
|
91
91
|
| [Safety and trust](./guides/safety-and-trust.md) | Uncertain changes, secrets, read-only access, and your responsibilities. |
|
|
92
92
|
| [Operator documentation](./documentation/README.md) | Setup, feature availability, upgrades, and exact contracts. |
|
|
93
93
|
|
|
94
|
-
<a id="existing-client-tokens"></a>
|
|
95
|
-
|
|
96
94
|
Upgrading? Follow the [upgrade guide](./documentation/deploying.md#upgrade-an-existing-deployment).
|
|
95
|
+
Pre-0.29 storage and OAuth layouts must first migrate on 0.37.1; see the
|
|
96
|
+
[required bridge upgrade](./documentation/migration-retired-layouts.md).
|
|
97
97
|
Breaking changes are expected before 1.0. See the [changelog](./CHANGELOG.md)
|
|
98
98
|
and [security policy](./SECURITY.md).
|
package/bin/connecta.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { copyFile, cp, lstat, mkdtemp, readFile, rename, rm, symlink, writeFile
|
|
|
4
4
|
import { CONNECTA_VERSION } from "./version.mjs";
|
|
5
5
|
import { META_TOOL_NAMES } from "./meta-tool-names.mjs";
|
|
6
6
|
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
-
import { fileURLToPath
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
10
|
const [command, ...args] = process.argv.slice(2);
|
|
@@ -19,7 +19,6 @@ function shellCd(path) {
|
|
|
19
19
|
function usage() {
|
|
20
20
|
console.log(`Usage:
|
|
21
21
|
connecta init [directory]
|
|
22
|
-
connecta migrate-state <state.json> <connecta.sqlite>
|
|
23
22
|
CONNECTA_TOKEN=<bearer> connecta doctor [--config] [--url http://localhost:8787] [--pool <name>]
|
|
24
23
|
CF_ACCESS_CLIENT_ID=<id> CF_ACCESS_CLIENT_SECRET=<secret> connecta doctor --url https://worker.example`);
|
|
25
24
|
}
|
|
@@ -307,46 +306,9 @@ async function doctor() {
|
|
|
307
306
|
);
|
|
308
307
|
}
|
|
309
308
|
|
|
310
|
-
/**
|
|
311
|
-
* One-shot copy of a 0.28 `fileStorage` JSON state file into the SQLite
|
|
312
|
-
* database 0.29 stores everything in. Stop the old deployment first; the file
|
|
313
|
-
* is read, never changed.
|
|
314
|
-
*/
|
|
315
|
-
async function migrateState() {
|
|
316
|
-
if (args.length !== 2 || args.some((arg) => arg.startsWith("--"))) {
|
|
317
|
-
usage();
|
|
318
|
-
process.exitCode = 1;
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
const [statePath, databasePath] = args.map((arg) => resolve(process.cwd(), arg));
|
|
322
|
-
const { importStateFile, openSqlite } = await import(pathToFileURL(join(packageRoot, "dist", "sqlite.js")).href);
|
|
323
|
-
let database;
|
|
324
|
-
try {
|
|
325
|
-
database = openSqlite(databasePath);
|
|
326
|
-
} catch {
|
|
327
|
-
throw new Error("State migration failed: could not open the SQLite database");
|
|
328
|
-
}
|
|
329
|
-
let closeFailed = false;
|
|
330
|
-
try {
|
|
331
|
-
const result = importStateFile(database, statePath);
|
|
332
|
-
console.log(
|
|
333
|
-
`Imported ${result.imported} entries from ${statePath} into ${databasePath}` +
|
|
334
|
-
` (${result.kept} already present, ${result.expired} expired).`,
|
|
335
|
-
);
|
|
336
|
-
} finally {
|
|
337
|
-
try {
|
|
338
|
-
database.close();
|
|
339
|
-
} catch {
|
|
340
|
-
closeFailed = true;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
if (closeFailed) throw new Error("State migration failed: could not close the SQLite database");
|
|
344
|
-
}
|
|
345
|
-
|
|
346
309
|
try {
|
|
347
310
|
if (command === "init") await init();
|
|
348
311
|
else if (command === "doctor") await doctor();
|
|
349
|
-
else if (command === "migrate-state") await migrateState();
|
|
350
312
|
else {
|
|
351
313
|
usage();
|
|
352
314
|
process.exitCode = command === "--help" || command === "-h" ? 0 : 1;
|
package/bin/version.mjs
CHANGED
package/dist/access-tokens.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sha256Hex } from "./bytes.js";
|
|
1
2
|
import { withAuthenticationDeadline } from "./auth-admission.js";
|
|
2
3
|
import { authorizationCredential, isMachineCredential } from "./inbound-credential.js";
|
|
3
4
|
import { assertKnownOptions, ConfigError, optionsOf, readPlain } from "./config-schema.js";
|
|
@@ -137,11 +138,8 @@ function bytesToBase64Url(bytes) {
|
|
|
137
138
|
binary += String.fromCharCode(byte);
|
|
138
139
|
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
139
140
|
}
|
|
140
|
-
function bytesToHex(bytes) {
|
|
141
|
-
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
142
|
-
}
|
|
143
141
|
async function hashToken(token) {
|
|
144
|
-
return
|
|
142
|
+
return sha256Hex(token);
|
|
145
143
|
}
|
|
146
144
|
const REVISION_RE = /^[A-Za-z0-9_-]{22}$/u;
|
|
147
145
|
/** Whether `value` has the shape of a slot revision. */
|
package/dist/agent-identity.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Code-declared agents
|
|
1
|
+
// Code-declared agents: the names deployment code lists in `agents`,
|
|
2
2
|
// the identity an admitted agent carries, and the built-in providers trusted
|
|
3
3
|
// to assert it. Pure, so core reads it without reaching credential modules.
|
|
4
4
|
import { ConfigError } from "./config-schema.js";
|
|
@@ -1833,4 +1833,5 @@ var Bc = new Set(["error", "warn", "info", "debug", "trace"]), Nt = { samplingRa
|
|
|
1833
1833
|
};
|
|
1834
1834
|
var ud = Mn(Z);
|
|
1835
1835
|
function jc(t) { let e = { ...t }, r = he(e), s = qn({ options: e, apiClient: r }), n = new Jt({ publishableKey: e.publishableKey, secretKey: e.secretKey, samplingRate: .1, ...e.sdkMetadata ? { sdk: e.sdkMetadata.name, sdkVersion: e.sdkMetadata.version } : {}, ...e.telemetry || {} }); return { ...r, ...s, telemetry: n }; }
|
|
1836
|
-
|
|
1836
|
+
const createClerkClient = jc;
|
|
1837
|
+
export { createClerkClient };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bytesToHex, sha256Hex } from "../bytes.js";
|
|
1
2
|
import { UnauthorizedError } from "@modelcontextprotocol/client";
|
|
2
3
|
import { Effect, Exit } from "effect";
|
|
3
4
|
import { byteReadResponse } from "../byte-read-response.js";
|
|
@@ -8,12 +9,12 @@ import { keepErrorName } from "../redaction-identity.js";
|
|
|
8
9
|
import { fromSignal, runEdge } from "../runtime/run.js";
|
|
9
10
|
import { Settlement } from "../runtime/settlement.js";
|
|
10
11
|
import { SentSecrets } from "../sent-secrets.js";
|
|
11
|
-
import { OAUTH_FLOW_TTL_SECONDS, OAUTH_REFRESH_LEASE_SECONDS, oauthFlowKeys, oauthGrantKeys, oauthRefreshActiveKeys, oauthRefreshKeys, oauthRefreshSpentKeys, } from "../storage/keys.js";
|
|
12
|
+
import { OAUTH_FLOW_TTL_SECONDS, OAUTH_REFRESH_LEASE_SECONDS, oauthFlowKeys, oauthGrantKeys, retiredOAuthKeys, oauthRefreshActiveKeys, oauthRefreshKeys, oauthRefreshSpentKeys, } from "../storage/keys.js";
|
|
12
13
|
import { authenticateRemoteClient, downstreamClientMetadata, remoteClientAuthMethod, } from "./downstream-client-metadata.js";
|
|
13
14
|
import { revokeDownstreamGrant } from "./downstream-revocation.js";
|
|
14
15
|
import { authAwait, authCall, authCatch, authDetached, authFlow, authInvoke, authMethod, authOwnMethod, authProgram, authStorage, authThen, authValue, registerAuth, registerAuthMethod, } from "./native.js";
|
|
15
16
|
import { isOAuthCredentialRequest, isRefreshTokenRequest, refreshResponseOutcome, sdkSafeTokenResponse, tokenGrantType, } from "./oauth-token-response.js";
|
|
16
|
-
import {
|
|
17
|
+
import { discoveryIssuer } from "./oauth-grant.js";
|
|
17
18
|
/** @internal Keep the SDK's cloned body reads inside the code exchange's request. */
|
|
18
19
|
export function codeExchangeResponse(response, signal) {
|
|
19
20
|
if (!response.body)
|
|
@@ -105,12 +106,11 @@ export class OAuthRequestNotSentError extends ConnectorCallError {
|
|
|
105
106
|
function randomState() {
|
|
106
107
|
const bytes = new Uint8Array(32);
|
|
107
108
|
crypto.getRandomValues(bytes);
|
|
108
|
-
return
|
|
109
|
+
return bytesToHex(bytes);
|
|
109
110
|
}
|
|
110
111
|
/** Lowercase hex SHA-256 of a consent's `state`: the name of its flow record. */
|
|
111
112
|
export async function oauthStateDigest(state) {
|
|
112
|
-
|
|
113
|
-
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
113
|
+
return sha256Hex(state);
|
|
114
114
|
}
|
|
115
115
|
function storedRefreshSpent(raw) {
|
|
116
116
|
const value = raw === null ? undefined : parsed(raw);
|
|
@@ -818,7 +818,7 @@ export class KvOAuthProvider {
|
|
|
818
818
|
if (client)
|
|
819
819
|
this.addClientAuthentication = (headers, params) => authenticateRemoteClient(client, headers, params);
|
|
820
820
|
}
|
|
821
|
-
/** Stored facts only: no
|
|
821
|
+
/** Stored facts only: no sealing writes, refresh or discovery. */
|
|
822
822
|
storedAuthorization(issuer) {
|
|
823
823
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "storedAuthorization", [issuer]));
|
|
824
824
|
}
|
|
@@ -834,39 +834,6 @@ export class KvOAuthProvider {
|
|
|
834
834
|
wait(work, signal = this.signal) {
|
|
835
835
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "wait", [work, signal]));
|
|
836
836
|
}
|
|
837
|
-
/** Legacy helpers must fence their inner reads/deletes too, not just our join. */
|
|
838
|
-
preparationStorage(signal) {
|
|
839
|
-
if (!signal)
|
|
840
|
-
return this.storage;
|
|
841
|
-
const storage = this.storage;
|
|
842
|
-
return {
|
|
843
|
-
...storage,
|
|
844
|
-
get: authMethod((key) => {
|
|
845
|
-
return authInvoke(this, "wait", [
|
|
846
|
-
authMethod(() => {
|
|
847
|
-
return authStorage(storage, "get", [key]);
|
|
848
|
-
}),
|
|
849
|
-
signal,
|
|
850
|
-
]);
|
|
851
|
-
}),
|
|
852
|
-
list: authMethod((prefix) => {
|
|
853
|
-
return authInvoke(this, "wait", [
|
|
854
|
-
authMethod(() => {
|
|
855
|
-
return authStorage(storage, "list", [prefix]);
|
|
856
|
-
}),
|
|
857
|
-
signal,
|
|
858
|
-
]);
|
|
859
|
-
}),
|
|
860
|
-
delete: authMethod((key) => {
|
|
861
|
-
return authInvoke(this, "wait", [
|
|
862
|
-
authMethod(() => {
|
|
863
|
-
return authStorage(storage, "delete", [key]);
|
|
864
|
-
}),
|
|
865
|
-
signal,
|
|
866
|
-
]);
|
|
867
|
-
}),
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
837
|
// --- the grant record ---------------------------------------------------
|
|
871
838
|
/** The grant's additional authenticated data: record and epoch. */
|
|
872
839
|
grantLabel(epoch) {
|
|
@@ -884,21 +851,13 @@ export class KvOAuthProvider {
|
|
|
884
851
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "openGrant", [raw, signal]));
|
|
885
852
|
}
|
|
886
853
|
/**
|
|
887
|
-
* The grant
|
|
854
|
+
* The current grant record. A plaintext
|
|
888
855
|
* body, written before the vault could seal, is sealed where it lies by
|
|
889
856
|
* compare-and-set; a failure leaves it readable for the next write to seal.
|
|
890
857
|
*/
|
|
891
858
|
readGrant(signal = this.signal) {
|
|
892
859
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "readGrant", [signal]));
|
|
893
860
|
}
|
|
894
|
-
/** Layout 2 migration and its cleanup retain the reading operation's owner. */
|
|
895
|
-
migrate(signal) {
|
|
896
|
-
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "migrate", [signal]));
|
|
897
|
-
}
|
|
898
|
-
/** Deletion is retried on every read, including reads after a reset. */
|
|
899
|
-
cleanupV2(raw, signal = this.signal) {
|
|
900
|
-
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "cleanupV2", [raw, signal]));
|
|
901
|
-
}
|
|
902
861
|
/** The grant a bound flow reads: a replaced epoch fails it. */
|
|
903
862
|
boundGrant(signal = this.signal) {
|
|
904
863
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "boundGrant", [signal]));
|
|
@@ -928,7 +887,6 @@ export class KvOAuthProvider {
|
|
|
928
887
|
throw this.flowSuperseded();
|
|
929
888
|
return {
|
|
930
889
|
epoch: `${ACTIVE_EPOCH_PREFIX}${crypto.randomUUID()}`,
|
|
931
|
-
...(grant.cleanupPending ? { cleanupPending: true } : {}),
|
|
932
890
|
body,
|
|
933
891
|
};
|
|
934
892
|
}
|
|
@@ -1410,7 +1368,6 @@ export class KvOAuthProvider {
|
|
|
1410
1368
|
const stored = {
|
|
1411
1369
|
connectaOAuth: GRANT_VERSION,
|
|
1412
1370
|
epoch: grant.epoch,
|
|
1413
|
-
...(grant.cleanupPending ? { cleanupPending: true } : {}),
|
|
1414
1371
|
...(grant.flow !== undefined ? { flow: grant.flow } : {}),
|
|
1415
1372
|
};
|
|
1416
1373
|
if (Object.keys(grant.body).length > 0) {
|
|
@@ -1466,7 +1423,6 @@ export class KvOAuthProvider {
|
|
|
1466
1423
|
}
|
|
1467
1424
|
return {
|
|
1468
1425
|
epoch: stored.epoch,
|
|
1469
|
-
...(stored.cleanupPending ? { cleanupPending: true } : {}),
|
|
1470
1426
|
...(stored.flow !== undefined ? { flow: stored.flow } : {}),
|
|
1471
1427
|
body: grantBody(body),
|
|
1472
1428
|
};
|
|
@@ -1497,10 +1453,17 @@ export class KvOAuthProvider {
|
|
|
1497
1453
|
}),
|
|
1498
1454
|
signal,
|
|
1499
1455
|
]));
|
|
1500
|
-
if (raw === null)
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1456
|
+
if (raw === null) {
|
|
1457
|
+
// Inspect names only: retired credentials are never opened or copied.
|
|
1458
|
+
const keys = (yield authInvoke(this, "wait", [
|
|
1459
|
+
authMethod(() => authStorage(this.storage, "list", [retiredOAuthKeys.scan])),
|
|
1460
|
+
signal,
|
|
1461
|
+
]));
|
|
1462
|
+
if (keys.some((key) => retiredOAuthKeys.prefixes.some((prefix) => key.startsWith(prefix))))
|
|
1463
|
+
(this.sealer ?? console).warn(`[connecta] connector "${this.connectorId}" has an unsupported pre-layout-3 OAuth grant; ` +
|
|
1464
|
+
"treating it as absent. Connect again to authorize, or restore a backup and run the " +
|
|
1465
|
+
"OAuth migration on Connecta 0.37.1 before upgrading.");
|
|
1466
|
+
}
|
|
1504
1467
|
const grant = (yield authInvoke(this, "openGrant", [raw, signal]));
|
|
1505
1468
|
if (raw !== null && this.sealer && storedGrant(raw)?.body !== undefined) {
|
|
1506
1469
|
const sealed = (yield authInvoke(this, "encodeGrant", [grant, signal]));
|
|
@@ -1515,99 +1478,6 @@ export class KvOAuthProvider {
|
|
|
1515
1478
|
return { raw, grant };
|
|
1516
1479
|
}.bind(this));
|
|
1517
1480
|
}, KvOAuthProvider.prototype.readGrant);
|
|
1518
|
-
registerAuthMethod(KvOAuthProvider.prototype, "migrate", function (signal) {
|
|
1519
|
-
return authFlow(function* () {
|
|
1520
|
-
const sealer = this.sealer;
|
|
1521
|
-
const found = (yield authInvoke(this, "wait", [
|
|
1522
|
-
authMethod(() => {
|
|
1523
|
-
return authCall(readV2Grant, [
|
|
1524
|
-
this.preparationStorage(signal),
|
|
1525
|
-
sealer && {
|
|
1526
|
-
...sealer,
|
|
1527
|
-
open: authMethod((label, value) => {
|
|
1528
|
-
return authInvoke(this, "wait", [
|
|
1529
|
-
authMethod(() => {
|
|
1530
|
-
return authInvoke(sealer, "open", [label, value]);
|
|
1531
|
-
}),
|
|
1532
|
-
signal,
|
|
1533
|
-
]);
|
|
1534
|
-
}),
|
|
1535
|
-
},
|
|
1536
|
-
]);
|
|
1537
|
-
}),
|
|
1538
|
-
signal,
|
|
1539
|
-
]));
|
|
1540
|
-
if (!found)
|
|
1541
|
-
return null;
|
|
1542
|
-
const prefix = found.disconnected ? DISCONNECTED_EPOCH_PREFIX : ACTIVE_EPOCH_PREFIX;
|
|
1543
|
-
const encoded = (yield authInvoke(this, "encodeGrant", [
|
|
1544
|
-
{
|
|
1545
|
-
epoch: `${prefix}${crypto.randomUUID()}`,
|
|
1546
|
-
body: found.body,
|
|
1547
|
-
cleanupPending: true,
|
|
1548
|
-
},
|
|
1549
|
-
signal,
|
|
1550
|
-
]));
|
|
1551
|
-
let written;
|
|
1552
|
-
try {
|
|
1553
|
-
written = (yield authInvoke(this, "wait", [
|
|
1554
|
-
authMethod(() => {
|
|
1555
|
-
return authStorage(this.storage, "compareAndSet", [GRANT, null, encoded]);
|
|
1556
|
-
}),
|
|
1557
|
-
signal,
|
|
1558
|
-
]));
|
|
1559
|
-
}
|
|
1560
|
-
catch {
|
|
1561
|
-
if (signal?.aborted)
|
|
1562
|
-
throw aborted(signal);
|
|
1563
|
-
throw this.credentialWriteError();
|
|
1564
|
-
}
|
|
1565
|
-
return (yield written
|
|
1566
|
-
? authValue(() => encoded)
|
|
1567
|
-
: authInvoke(this, "wait", [
|
|
1568
|
-
authMethod(() => {
|
|
1569
|
-
return authStorage(this.storage, "get", [GRANT]);
|
|
1570
|
-
}),
|
|
1571
|
-
signal,
|
|
1572
|
-
]));
|
|
1573
|
-
}.bind(this));
|
|
1574
|
-
}, KvOAuthProvider.prototype.migrate);
|
|
1575
|
-
registerAuthMethod(KvOAuthProvider.prototype, "cleanupV2", function (raw, signal) {
|
|
1576
|
-
signal = signal === undefined ? this.signal : signal;
|
|
1577
|
-
return authFlow(function* () {
|
|
1578
|
-
try {
|
|
1579
|
-
if (!(yield authInvoke(this, "wait", [
|
|
1580
|
-
authMethod(() => {
|
|
1581
|
-
return authCall(deleteV2Keys, [this.preparationStorage(signal)]);
|
|
1582
|
-
}),
|
|
1583
|
-
signal,
|
|
1584
|
-
])))
|
|
1585
|
-
return raw;
|
|
1586
|
-
const grant = (yield authInvoke(this, "openGrant", [raw, signal]));
|
|
1587
|
-
delete grant.cleanupPending;
|
|
1588
|
-
const cleared = (yield authInvoke(this, "encodeGrant", [grant, signal]));
|
|
1589
|
-
if ((yield authInvoke(this, "wait", [
|
|
1590
|
-
authMethod(() => {
|
|
1591
|
-
return authStorage(this.storage, "compareAndSet", [GRANT, raw, cleared]);
|
|
1592
|
-
}),
|
|
1593
|
-
signal,
|
|
1594
|
-
])))
|
|
1595
|
-
return cleared;
|
|
1596
|
-
return (yield authInvoke(this, "wait", [
|
|
1597
|
-
authMethod(() => {
|
|
1598
|
-
return authStorage(this.storage, "get", [GRANT]);
|
|
1599
|
-
}),
|
|
1600
|
-
signal,
|
|
1601
|
-
]));
|
|
1602
|
-
}
|
|
1603
|
-
catch {
|
|
1604
|
-
if (signal?.aborted)
|
|
1605
|
-
throw aborted(signal);
|
|
1606
|
-
// The durable marker survives both deletion and marker-clear failures.
|
|
1607
|
-
return raw;
|
|
1608
|
-
}
|
|
1609
|
-
}.bind(this));
|
|
1610
|
-
}, KvOAuthProvider.prototype.cleanupV2);
|
|
1611
1481
|
registerAuthMethod(KvOAuthProvider.prototype, "boundGrant", function (signal) {
|
|
1612
1482
|
signal = signal === undefined ? this.signal : signal;
|
|
1613
1483
|
return authFlow(function* () {
|
|
@@ -2763,10 +2633,7 @@ export class KvOAuthProvider {
|
|
|
2763
2633
|
for (let attempt = 0; attempt < MAX_GRANT_WRITES; attempt++) {
|
|
2764
2634
|
const { raw, grant } = (yield authInvoke(this, "readGrant", [null]));
|
|
2765
2635
|
const carried = preserveClient && !operatorDisconnected ? this.carriableClient(grant) : undefined;
|
|
2766
|
-
const encoded = (yield authInvoke(this, "encodeGrant", [
|
|
2767
|
-
{ epoch, body: carried ?? {}, cleanupPending: true },
|
|
2768
|
-
null,
|
|
2769
|
-
]));
|
|
2636
|
+
const encoded = (yield authInvoke(this, "encodeGrant", [{ epoch, body: carried ?? {} }, null]));
|
|
2770
2637
|
let written;
|
|
2771
2638
|
try {
|
|
2772
2639
|
written = (yield authStorage(this.storage, "compareAndSet", [GRANT, raw, encoded]));
|
|
@@ -2777,7 +2644,6 @@ export class KvOAuthProvider {
|
|
|
2777
2644
|
if (written) {
|
|
2778
2645
|
this.refreshCoordinator?.retire(grant.epoch);
|
|
2779
2646
|
try {
|
|
2780
|
-
(yield authInvoke(this, "cleanupV2", [encoded, null]));
|
|
2781
2647
|
(yield authInvoke(this, "sweepConsents", []));
|
|
2782
2648
|
(yield authInvoke(this, "sweepRefreshLeases", []));
|
|
2783
2649
|
}
|
|
@@ -4,7 +4,7 @@ import { fromSignal, runEdge } from "../runtime/run.js";
|
|
|
4
4
|
import { classifyHost } from "../url-safety.js";
|
|
5
5
|
import { authenticateRemoteClient, remoteClientAuthMethod, } from "./downstream-client-metadata.js";
|
|
6
6
|
import { authCall, authFlow, authProgram, registerAuth } from "./native.js";
|
|
7
|
-
import { discoveryIssuer } from "./oauth-
|
|
7
|
+
import { discoveryIssuer } from "./oauth-grant.js";
|
|
8
8
|
/** Local removal succeeded; provider-side removal needs the operator's attention. */
|
|
9
9
|
export class OAuthRevocationError extends ConnectorCallError {
|
|
10
10
|
constructor() {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function plainObject(value) {
|
|
2
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
/** The issuer a discovery record names, as the SDK derives it. */
|
|
5
|
+
export function discoveryIssuer(state) {
|
|
6
|
+
if (!plainObject(state) || typeof state.authorizationServerUrl !== "string")
|
|
7
|
+
return undefined;
|
|
8
|
+
const metadata = state.authorizationServerMetadata;
|
|
9
|
+
const issuer = plainObject(metadata) && typeof metadata.issuer === "string" ? metadata.issuer : state.authorizationServerUrl;
|
|
10
|
+
return issuer === "" ? undefined : issuer;
|
|
11
|
+
}
|
|
@@ -3,7 +3,7 @@ import { authCall, authFlow, authInvoke, authProgram, registerAuth } from "./nat
|
|
|
3
3
|
// The token endpoint's answers as downstream OAuth classifies them and the SDK
|
|
4
4
|
// gets to parse them: a refresh's re-consent verdict, and every failure
|
|
5
5
|
// rebuilt from its OAuth `error` code alone, so nothing else a token endpoint
|
|
6
|
-
// wrote reaches the SDK's console output
|
|
6
|
+
// wrote reaches the SDK's console output.
|
|
7
7
|
/** The `grant_type` of a token request, or undefined for any other request. */
|
|
8
8
|
export function tokenGrantType(init) {
|
|
9
9
|
if ((init?.method ?? "GET").toUpperCase() !== "POST")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeBase64url } from "../bytes.js";
|
|
1
2
|
import { runEdge } from "../runtime/run.js";
|
|
2
3
|
import { authCall, authCatch, authFetch, authFlow, authInvoke, authMethod, authProgram, registerAuth, } from "./native.js";
|
|
3
4
|
import { AGENT_NAMESPACE, agentSubjectId, trustAgentProvider } from "../agent-identity.js";
|
|
@@ -14,7 +15,7 @@ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
|
14
15
|
function bytes(value) {
|
|
15
16
|
if (!/^[A-Za-z0-9_-]+(?![\s\S])/.test(value))
|
|
16
17
|
throw new Error("Invalid JWT encoding");
|
|
17
|
-
return
|
|
18
|
+
return decodeBase64url(value);
|
|
18
19
|
}
|
|
19
20
|
function record(value) {
|
|
20
21
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
package/dist/auth-elicitation.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toolError } from "./tool-error.js";
|
|
1
2
|
import { RequestScope, requestOwner } from "./runtime/request-scope.js";
|
|
2
3
|
import { upwardStep } from "./upward.js";
|
|
3
4
|
import { authAwait, authCall, authInvoke, authFlow, authMethod, registerAuthMethod, authOwnMethod, } from "./auth/native.js";
|
|
@@ -17,8 +18,7 @@ import { canonicalState as canonical, requestDigest as digest, stateObject as ob
|
|
|
17
18
|
const INPUT_KEY = "connecta_auth";
|
|
18
19
|
const MESSAGE = "Connect this service in your browser, then retry the request.";
|
|
19
20
|
function failure(code, message) {
|
|
20
|
-
|
|
21
|
-
return { content: [{ type: "text", text: JSON.stringify(structuredContent) }], structuredContent, isError: true };
|
|
21
|
+
return toolError({ code, message, retryable: false }, { ok: false });
|
|
22
22
|
}
|
|
23
23
|
/** Request-local auth recovery at the MCP boundary, never inside a guest provider. */
|
|
24
24
|
export class AuthElicitation {
|
package/dist/branding.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ConnectaBranding, ConnectaTheme, UiAuthConfig } from "./types.js";
|
|
2
2
|
/** Connecta's default monochrome "C" mark. */
|
|
3
3
|
export declare const CONNECTA_FAVICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\">\n <style>\n .fg { fill: #000 }\n @media (prefers-color-scheme: dark) { .fg { fill: #fff } }\n </style>\n <path class=\"fg\" d=\"M27 9.4A13 13 0 1 0 27 22.6l-4.4-2.5a8 8 0 1 1 0-8.2z\"/>\n</svg>";
|
|
4
|
-
|
|
4
|
+
interface ResolvedTheme {
|
|
5
5
|
accent?: string;
|
|
6
6
|
radius?: string;
|
|
7
7
|
fontFamily?: string;
|
|
@@ -28,26 +28,12 @@ interface ResolvedBranding {
|
|
|
28
28
|
theme: ResolvedTheme;
|
|
29
29
|
}
|
|
30
30
|
export declare function resolveBranding(branding?: ConnectaBranding): ResolvedBranding;
|
|
31
|
-
/**
|
|
32
|
-
* Read a theme the way branding URLs are read: gate every field, drop what
|
|
33
|
-
* fails, never throw. This runs during `createConnecta`, so a malformed value
|
|
34
|
-
* has to fall back to the stylesheet default instead of refusing to serve the
|
|
35
|
-
* page. `droppedThemeTokens` names the drops for the startup warning.
|
|
36
|
-
*/
|
|
37
|
-
export declare function resolveTheme(theme?: ConnectaTheme): ResolvedTheme;
|
|
38
31
|
/**
|
|
39
32
|
* Names of the theme tokens the operator set that failed their gate. Same
|
|
40
33
|
* contract as `droppedBrandingUrls`: rendering falls back silently, so this is
|
|
41
34
|
* the only place an operator learns their value never reached the page.
|
|
42
35
|
*/
|
|
43
36
|
export declare function droppedThemeTokens(theme?: ConnectaTheme): string[];
|
|
44
|
-
/**
|
|
45
|
-
* The resolved theme as a `:root` block, or "" when a deployment configured
|
|
46
|
-
* nothing. It is emitted after the stylesheet so it overrides the defaults.
|
|
47
|
-
* There is no escaping here: every value has already passed a gate above, and
|
|
48
|
-
* anything that would need escaping is dropped rather than rewritten.
|
|
49
|
-
*/
|
|
50
|
-
export declare function themeCss(theme: ResolvedTheme): string;
|
|
51
37
|
/** Text or attribute value, escaped for any position in an HTML document. */
|
|
52
38
|
export declare function escapeHtml(value: string): string;
|
|
53
39
|
export interface PageLayout {
|
package/dist/branding.js
CHANGED
|
@@ -71,7 +71,7 @@ const COLOR_SCHEMES = ["system", "light", "dark"];
|
|
|
71
71
|
* has to fall back to the stylesheet default instead of refusing to serve the
|
|
72
72
|
* page. `droppedThemeTokens` names the drops for the startup warning.
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
function resolveTheme(theme) {
|
|
75
75
|
const accent = trimmedString(theme?.accent);
|
|
76
76
|
const fontFamily = trimmedString(theme?.fontFamily);
|
|
77
77
|
const monoFamily = trimmedString(theme?.monoFamily);
|
|
@@ -175,7 +175,7 @@ function accentTokens(accent) {
|
|
|
175
175
|
* There is no escaping here: every value has already passed a gate above, and
|
|
176
176
|
* anything that would need escaping is dropped rather than rewritten.
|
|
177
177
|
*/
|
|
178
|
-
|
|
178
|
+
function themeCss(theme) {
|
|
179
179
|
const derived = theme.accent ? accentTokens(theme.accent) : undefined;
|
|
180
180
|
const declarations = [
|
|
181
181
|
theme.accent ? `--accent:${theme.accent}` : "",
|
package/dist/bytes.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Lowercase hexadecimal, preserving each byte's leading zero. */
|
|
2
|
+
export function bytesToHex(bytes) {
|
|
3
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4
|
+
}
|
|
5
|
+
export async function sha256Hex(value) {
|
|
6
|
+
return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))));
|
|
7
|
+
}
|
|
8
|
+
/** UTF-8 byte count without allocating encoded bytes; stops once the limit is exceeded. */
|
|
9
|
+
export function utf8Length(value, limit = Infinity) {
|
|
10
|
+
let bytes = 0;
|
|
11
|
+
for (let i = 0; i < value.length; i++) {
|
|
12
|
+
const code = value.charCodeAt(i);
|
|
13
|
+
if (code < 0x80)
|
|
14
|
+
bytes++;
|
|
15
|
+
else if (code < 0x800)
|
|
16
|
+
bytes += 2;
|
|
17
|
+
else if (code >= 0xd800 &&
|
|
18
|
+
code <= 0xdbff &&
|
|
19
|
+
value.charCodeAt(i + 1) >= 0xdc00 &&
|
|
20
|
+
value.charCodeAt(i + 1) <= 0xdfff) {
|
|
21
|
+
bytes += 4;
|
|
22
|
+
i++;
|
|
23
|
+
}
|
|
24
|
+
else
|
|
25
|
+
bytes += 3;
|
|
26
|
+
if (bytes > limit)
|
|
27
|
+
return bytes;
|
|
28
|
+
}
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
export function decodeBase64(value) {
|
|
32
|
+
const binary = atob(value);
|
|
33
|
+
const bytes = new Uint8Array(binary.length);
|
|
34
|
+
for (let index = 0; index < binary.length; index += 1)
|
|
35
|
+
bytes[index] = binary.charCodeAt(index);
|
|
36
|
+
return bytes;
|
|
37
|
+
}
|
|
38
|
+
/** Optional padding preserves callers that pad before handing input to atob. */
|
|
39
|
+
export function decodeBase64url(value, pad = false) {
|
|
40
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
41
|
+
return decodeBase64(pad ? base64 + "=".repeat((4 - (base64.length % 4)) % 4) : base64);
|
|
42
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
export function attachCallAdmission(ctx, permit) {
|
|
4
|
-
setViewService(ctx, Budget, { charge: chargeCallBudget(permit, ctx.signal) });
|
|
5
|
-
}
|
|
1
|
+
// The Promise view of the call's admission service; authority stays internal.
|
|
2
|
+
import { Budget, callScopeKnown, chargeView, recordedService } from "./runtime/call.js";
|
|
6
3
|
export function chargeConnectorCall(ctx) {
|
|
7
4
|
return recordedService(ctx, Budget) || callScopeKnown(ctx) ? chargeView(ctx) : Promise.resolve();
|
|
8
5
|
}
|
package/dist/call-recovery.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { bytesToHex } from "./bytes.js";
|
|
1
2
|
const encoder = new TextEncoder();
|
|
2
3
|
async function fingerprintBytes(bytes) {
|
|
3
4
|
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new Uint8Array(bytes)));
|
|
4
|
-
const hex =
|
|
5
|
+
const hex = bytesToHex(digest);
|
|
5
6
|
return `sha256:${bytes.byteLength}:${hex}`;
|
|
6
7
|
}
|
|
7
8
|
/** Fingerprint an already serialized catalog without parsing/reserializing it. */
|
|
@@ -12,20 +13,3 @@ export async function fingerprintSerializedCatalog(serializedTools) {
|
|
|
12
13
|
byteLength: bytes.byteLength,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Capture every persisted/discovered field in one deterministic snapshot.
|
|
17
|
-
* Object key order is allowed to cause a conservative extra write; changing
|
|
18
|
-
* any serialized field must change the digest.
|
|
19
|
-
*/
|
|
20
|
-
export async function snapshotCatalog(tools) {
|
|
21
|
-
const serializedTools = JSON.stringify(tools);
|
|
22
|
-
if (serializedTools === undefined) {
|
|
23
|
-
throw new TypeError("Tool catalog is not JSON-serializable.");
|
|
24
|
-
}
|
|
25
|
-
const bytes = encoder.encode(serializedTools);
|
|
26
|
-
return {
|
|
27
|
-
tools,
|
|
28
|
-
fingerprint: await fingerprintBytes(bytes),
|
|
29
|
-
serializedBytes: bytes,
|
|
30
|
-
};
|
|
31
|
-
}
|