@agen-ai/acp-registry 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.md +347 -2
- package/dist/catalog/assert.d.ts +6 -0
- package/dist/catalog/assert.js +46 -0
- package/dist/catalog/contracts.d.ts +70 -0
- package/dist/catalog/contracts.js +0 -0
- package/dist/catalog/launch.d.ts +4 -0
- package/dist/catalog/launch.js +19 -0
- package/dist/catalog/limits.d.ts +5 -0
- package/dist/catalog/limits.js +40 -0
- package/dist/catalog/normalize.d.ts +9 -0
- package/dist/catalog/normalize.js +82 -0
- package/dist/catalog/parse.d.ts +3 -0
- package/dist/catalog/parse.js +46 -0
- package/dist/catalog/schemas.d.ts +215 -0
- package/dist/catalog/schemas.js +62 -0
- package/dist/catalog/source.d.ts +19 -0
- package/dist/catalog/source.js +46 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +16 -0
- package/dist/node/contracts.d.ts +30 -0
- package/dist/node/contracts.js +13 -0
- package/dist/node/fetch-policy.d.ts +11 -0
- package/dist/node/fetch-policy.js +64 -0
- package/dist/node/fetch.d.ts +4 -0
- package/dist/node/fetch.js +74 -0
- package/dist/node/index.d.ts +11 -0
- package/dist/node/index.js +14 -0
- package/dist/node/install/artifacts/acquire.d.ts +14 -0
- package/dist/node/install/artifacts/acquire.js +191 -0
- package/dist/node/install/binary/archive.d.ts +18 -0
- package/dist/node/install/binary/archive.js +256 -0
- package/dist/node/install/binary/paths.d.ts +6 -0
- package/dist/node/install/binary/paths.js +37 -0
- package/dist/node/install/binary/prepare.d.ts +4 -0
- package/dist/node/install/binary/prepare.js +66 -0
- package/dist/node/install/binary/sink.d.ts +37 -0
- package/dist/node/install/binary/sink.js +188 -0
- package/dist/node/install/contracts.d.ts +163 -0
- package/dist/node/install/contracts.js +0 -0
- package/dist/node/install/errors.d.ts +6 -0
- package/dist/node/install/errors.js +11 -0
- package/dist/node/install/index.d.ts +4 -0
- package/dist/node/install/index.js +86 -0
- package/dist/node/install/input.d.ts +23 -0
- package/dist/node/install/input.js +52 -0
- package/dist/node/install/internal.d.ts +33 -0
- package/dist/node/install/internal.js +0 -0
- package/dist/node/install/limits.d.ts +4 -0
- package/dist/node/install/limits.js +50 -0
- package/dist/node/install/packages/context.d.ts +14 -0
- package/dist/node/install/packages/context.js +111 -0
- package/dist/node/install/packages/metadata.d.ts +14 -0
- package/dist/node/install/packages/metadata.js +149 -0
- package/dist/node/install/packages/npm.d.ts +4 -0
- package/dist/node/install/packages/npm.js +109 -0
- package/dist/node/install/packages/python-scripts.d.ts +4 -0
- package/dist/node/install/packages/python-scripts.js +24 -0
- package/dist/node/install/packages/sources.d.ts +8 -0
- package/dist/node/install/packages/sources.js +56 -0
- package/dist/node/install/packages/uv.d.ts +4 -0
- package/dist/node/install/packages/uv.js +127 -0
- package/dist/node/install/store/errors.d.ts +6 -0
- package/dist/node/install/store/errors.js +25 -0
- package/dist/node/install/store/filesystem.d.ts +22 -0
- package/dist/node/install/store/filesystem.js +187 -0
- package/dist/node/install/store/index.d.ts +35 -0
- package/dist/node/install/store/index.js +179 -0
- package/dist/node/install/store/lock.d.ts +14 -0
- package/dist/node/install/store/lock.js +100 -0
- package/dist/node/install/store/payload.d.ts +14 -0
- package/dist/node/install/store/payload.js +167 -0
- package/dist/node/install/store/receipt.d.ts +7 -0
- package/dist/node/install/store/receipt.js +175 -0
- package/dist/node/install/store/references.d.ts +9 -0
- package/dist/node/install/store/references.js +105 -0
- package/dist/node/install/store/stage.d.ts +13 -0
- package/dist/node/install/store/stage.js +66 -0
- package/dist/node/install/tools/inspect.d.ts +12 -0
- package/dist/node/install/tools/inspect.js +105 -0
- package/dist/node/install/tools/run.d.ts +4 -0
- package/dist/node/install/tools/run.js +66 -0
- package/dist/node/response.d.ts +3 -0
- package/dist/node/response.js +41 -0
- package/dist/planning/binary.d.ts +3 -0
- package/dist/planning/binary.js +20 -0
- package/dist/planning/contracts.d.ts +107 -0
- package/dist/planning/contracts.js +0 -0
- package/dist/planning/create.d.ts +4 -0
- package/dist/planning/create.js +33 -0
- package/dist/planning/derive.d.ts +6 -0
- package/dist/planning/derive.js +68 -0
- package/dist/planning/lock.d.ts +10 -0
- package/dist/planning/lock.js +120 -0
- package/dist/planning/package.d.ts +6 -0
- package/dist/planning/package.js +81 -0
- package/dist/planning/parse.d.ts +4 -0
- package/dist/planning/parse.js +91 -0
- package/dist/planning/policy.d.ts +6 -0
- package/dist/planning/policy.js +72 -0
- package/dist/planning/specifier.d.ts +4 -0
- package/dist/planning/specifier.js +24 -0
- package/dist/selection/distribution.d.ts +11 -0
- package/dist/selection/distribution.js +43 -0
- package/dist/selection/input.d.ts +21 -0
- package/dist/selection/input.js +54 -0
- package/dist/selection/search.d.ts +9 -0
- package/dist/selection/search.js +29 -0
- package/dist/shared/bounds.d.ts +7 -0
- package/dist/shared/bounds.js +149 -0
- package/dist/shared/digest.d.ts +3 -0
- package/dist/shared/digest.js +20 -0
- package/dist/shared/errors.d.ts +9 -0
- package/dist/shared/errors.js +21 -0
- package/dist/shared/immutable.d.ts +2 -0
- package/dist/shared/immutable.js +14 -0
- package/package.json +62 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Trevor Nichols
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,348 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agen-ai/acp-registry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Optional ACP catalog validation, search, immutable planning and verified installation for Node.js 22 or newer.
|
|
4
|
+
The coordinated SDK version is `0.4.0`. The package is ESM, MIT licensed and side-effect-free on import.
|
|
5
|
+
The metadata graph depends only on `@agen-ai/validation` and exact `zod@4.4.3`. Explicit Node
|
|
6
|
+
installation uses pinned tar, ZIP and bzip2 readers. Public contracts are plain TypeScript values
|
|
7
|
+
and AgenAI validation issues, with no exposed Zod schemas or Agent Runtime dependency.
|
|
8
|
+
|
|
9
|
+
## Boundaries
|
|
10
|
+
|
|
11
|
+
The root parses inert metadata and creates plans. It never fetches, writes files, installs packages,
|
|
12
|
+
spawns processes, chooses credentials or activates an agent. `/node` provides explicit bounded
|
|
13
|
+
fetching and installation into an explicitly opened owned store. Provider composition, credentials
|
|
14
|
+
and process lifetime remain caller-owned.
|
|
15
|
+
|
|
16
|
+
Catalog labels, licenses, launch metadata and `bundled` flags grant no authority. Digests identify
|
|
17
|
+
inputs; they do not authenticate their publisher. Use an independently trusted source and expected
|
|
18
|
+
artifact evidence appropriate to your host. A plan's `verified: false` never changes. A separate
|
|
19
|
+
installation receipt records verification of its declared obligations against actual artifacts and
|
|
20
|
+
toolchains. This source preparation does not publish the package or authorize a host deployment.
|
|
21
|
+
|
|
22
|
+
## Parse, search and plan
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import {
|
|
26
|
+
createAcpInstallPlan,
|
|
27
|
+
parseAcpRegistrySnapshot,
|
|
28
|
+
searchAcpRegistry,
|
|
29
|
+
} from '@agen-ai/acp-registry';
|
|
30
|
+
|
|
31
|
+
const snapshot = parseAcpRegistrySnapshot({
|
|
32
|
+
source: {
|
|
33
|
+
namespace: 'my-company',
|
|
34
|
+
kind: 'local',
|
|
35
|
+
channel: 'stable',
|
|
36
|
+
location: 'approved-catalog-2026-10-09',
|
|
37
|
+
profile: 'published-v1',
|
|
38
|
+
},
|
|
39
|
+
content: catalogJson, // string or Uint8Array supplied by the caller
|
|
40
|
+
});
|
|
41
|
+
const [entry] = searchAcpRegistry(snapshot, {
|
|
42
|
+
query: 'coding',
|
|
43
|
+
target: 'linux-x86_64',
|
|
44
|
+
distributionKinds: ['binary'],
|
|
45
|
+
});
|
|
46
|
+
if (!entry) throw new Error('No approved binary distribution matches.');
|
|
47
|
+
|
|
48
|
+
const plan = createAcpInstallPlan(snapshot, {
|
|
49
|
+
entryIdentity: entry.identity,
|
|
50
|
+
expectedSnapshotDigest: snapshot.digest,
|
|
51
|
+
expectedEntryDigest: entry.digest,
|
|
52
|
+
target: 'linux-x86_64',
|
|
53
|
+
distributionPreference: ['binary'],
|
|
54
|
+
policy: {
|
|
55
|
+
network: 'offline',
|
|
56
|
+
allowedOrigins: [],
|
|
57
|
+
allowInsecureHttp: false,
|
|
58
|
+
packageScripts: 'deny',
|
|
59
|
+
integrity: 'require-expected-sha256',
|
|
60
|
+
reproducibility: 'require-complete',
|
|
61
|
+
allowedEnvironmentKeys: [],
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This example requires a matching binary with an expected SHA-256 and no catalog environment keys.
|
|
67
|
+
Missing evidence or a conflicting caller pin throws `AcpRegistryValidationError`; it never silently
|
|
68
|
+
weakens policy. For a binary without catalog integrity, supply `binaryIntegrity` with an independently
|
|
69
|
+
expected digest and the digest of its resolution evidence. Alternatively, explicitly select weaker
|
|
70
|
+
integrity/reproducibility policy and inspect the plan's recorded gaps.
|
|
71
|
+
|
|
72
|
+
Search matches all whitespace-separated query tokens case-insensitively against ID, name, description
|
|
73
|
+
and authors, then sorts by source-qualified identity. The explicit distribution preference is ranked;
|
|
74
|
+
selection never substitutes another architecture or a distribution outside that list. The six target
|
|
75
|
+
identifiers are `darwin`, `linux` or `windows` followed by `-aarch64` or `-x86_64`. Unsupported Windows
|
|
76
|
+
`.cmd`/`.bat` launches fail explicitly; no implicit shell interpreter is introduced.
|
|
77
|
+
|
|
78
|
+
Snapshots, entries, search results, distributions and plans are deeply frozen. Serializable values
|
|
79
|
+
are structurally revalidated and their digests recomputed before lookup or planning, so JSON round
|
|
80
|
+
trips remain usable without a hidden object-identity registry. The host must retain its expected
|
|
81
|
+
snapshot and entry pins; recomputing a digest does not establish trust in a modified object.
|
|
82
|
+
|
|
83
|
+
## Identity and the accepted profile
|
|
84
|
+
|
|
85
|
+
Source identity includes the caller-selected namespace, local/remote kind, stable/preview channel,
|
|
86
|
+
canonical location and profile. Agent IDs are unique within that source. Changing the source location
|
|
87
|
+
or channel produces a different identity. Redirect destinations never replace the selected source.
|
|
88
|
+
`rawDigest` is SHA-256 over exact accepted UTF-8 bytes, including whitespace and any UTF-8 BOM.
|
|
89
|
+
`snapshot.digest` binds the normalized source, entries and raw digest; `entry.digest` binds the
|
|
90
|
+
normalized entry. Public digests use `sha256:` followed by lowercase hexadecimal. Upstream binary
|
|
91
|
+
`sha256` fields remain normalized bare hexadecimal in distribution metadata.
|
|
92
|
+
|
|
93
|
+
The `published-v1` profile is grounded in upstream registry commit
|
|
94
|
+
[`7f9be29696108572a2bca70e0563af528073a0c3`](https://github.com/agentclientprotocol/registry/commit/7f9be29696108572a2bca70e0563af528073a0c3),
|
|
95
|
+
its [format](https://github.com/agentclientprotocol/registry/blob/7f9be29696108572a2bca70e0563af528073a0c3/FORMAT.md),
|
|
96
|
+
[agent schema](https://github.com/agentclientprotocol/registry/blob/7f9be29696108572a2bca70e0563af528073a0c3/agent.schema.json),
|
|
97
|
+
and [builder](https://github.com/agentclientprotocol/registry/blob/7f9be29696108572a2bca70e0563af528073a0c3/.github/workflows/build_registry.py).
|
|
98
|
+
It accepts the complete published `{ version: '1.0.0', agents, extensions?: [] }` envelope. The builder
|
|
99
|
+
emits an empty `extensions` array even though that commit's registry JSON Schema omits the field.
|
|
100
|
+
Only the empty form is accepted; extension installation and source-manifest preview overlays are
|
|
101
|
+
unsupported. Stable and preview are separate complete catalogs, selected by the caller.
|
|
102
|
+
|
|
103
|
+
The committed catalog fixtures are derived from pinned source entries and the builder's output
|
|
104
|
+
shape. They are not downloaded CDN publication bytes and do not establish the freshness or
|
|
105
|
+
authenticity of a live registry response. Retain and approve each actual source snapshot separately.
|
|
106
|
+
|
|
107
|
+
Agent release versions are opaque dotted numeric identities, including leading-zero date releases
|
|
108
|
+
such as `2026.10.01` and preview suffixes such as `1.2.3-preview.1`. They never resolve a package version.
|
|
109
|
+
Unknown fields, unknown distributions, duplicate agent identities or JSON keys, unsafe paths, NUL,
|
|
110
|
+
invalid UTF-8, unpaired surrogates and malformed URLs are rejected. URLs and argument strings remain
|
|
111
|
+
inert metadata; no shell expansion occurs. Binary paths become contained POSIX-relative paths,
|
|
112
|
+
including normalization of Windows separators. The parser accepts clean HTTP metadata, while all
|
|
113
|
+
network operations require their own explicit transport policy.
|
|
114
|
+
|
|
115
|
+
Default limits are 8 MiB of input, 1,024 entries, depth 16, 200,000 JSON values, 16 KiB per string,
|
|
116
|
+
128 launch arguments and 128 environment entries. `DEFAULT_ACP_REGISTRY_LIMITS` exposes these values;
|
|
117
|
+
`limits` can adjust them only within finite package ceilings. Parsing fails as a whole, without
|
|
118
|
+
returning a partially accepted catalog. Validation failures carry immutable `issues` with code,
|
|
119
|
+
path and message.
|
|
120
|
+
|
|
121
|
+
## Package resolution and reproducibility
|
|
122
|
+
|
|
123
|
+
`npx` and `uvx` describe catalog distribution kinds, not instructions to run download-on-demand
|
|
124
|
+
launchers. Planning requires an explicit `packageResolution` with the normalized package name,
|
|
125
|
+
exact version and package-owned bin or console-script command. An unversioned name or named tag
|
|
126
|
+
also requires caller-supplied resolution evidence. Exact catalog pins must match the supplied version.
|
|
127
|
+
Supported range syntax remains searchable metadata, but planning rejects it explicitly until range
|
|
128
|
+
resolution semantics are qualified; a digest alone does not prove that a version satisfies a range.
|
|
129
|
+
The command may differ from the package name. Aliases, local paths and VCS specifiers are unsupported.
|
|
130
|
+
No package registry is contacted by planning.
|
|
131
|
+
|
|
132
|
+
Package plans require exact Node/npm or Python/uv toolchain versions and measured executable digests,
|
|
133
|
+
without embedding machine-specific toolchain paths. Strict package reproducibility requires expected
|
|
134
|
+
artifact integrity, disabled package scripts and a supplied dependency lock. Supported lock input
|
|
135
|
+
profiles are a bounded npm package-lock v3 subset and hashed Python requirements. Both require a
|
|
136
|
+
single declared dependency closure containing the selected package, exact versions, target artifact
|
|
137
|
+
SHA-256 values and a digest of the complete lock document. These are locked inputs with pending
|
|
138
|
+
installer verification, not proof that a resolver or publisher is trustworthy. A top-level version
|
|
139
|
+
alone records a missing dependency closure; enabling scripts records another gap.
|
|
140
|
+
|
|
141
|
+
The npm profile requires one exact root dependency, contained `node_modules` paths, canonical
|
|
142
|
+
HTTP(S) artifact URLs and strong SHA-256/384/512 SRI. Links, bundled, dev, optional, platform-conditional
|
|
143
|
+
and peer dependency metadata require additional qualification and are rejected. Declared dependency
|
|
144
|
+
names must agree with the lock. The Python profile accepts canonical `name==version` lines with one
|
|
145
|
+
target SHA-256 each, comments and line continuations; direct URLs, options, extras and markers are
|
|
146
|
+
unsupported. Python requirements do not encode dependency edges, so the supplied graph remains
|
|
147
|
+
caller evidence until an installer compares actual package metadata and verifies closure. The same
|
|
148
|
+
installed-artifact verification remains mandatory for npm.
|
|
149
|
+
|
|
150
|
+
Plans bind the source, accepted snapshot and entry, selected distribution, target and preference,
|
|
151
|
+
literal launch arguments/environment, exact resolution and lock, toolchain identities, expected
|
|
152
|
+
integrity, mapping profile and explicit policy. Repeated equal normalized inputs yield the same plan
|
|
153
|
+
digest. `pinned-binary` describes the payload input only; it does not pin an operating system, shared
|
|
154
|
+
library set or all runtime behavior. Catalog environment variables are launch-only and require an
|
|
155
|
+
exact allowlist; process, shell, installer, proxy and runtime plumbing variables remain reserved.
|
|
156
|
+
|
|
157
|
+
## Explicit fetching
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { fetchAcpRegistrySnapshot } from '@agen-ai/acp-registry/node';
|
|
161
|
+
|
|
162
|
+
const fetched = await fetchAcpRegistrySnapshot({
|
|
163
|
+
source: {
|
|
164
|
+
namespace: 'approved-registry',
|
|
165
|
+
kind: 'remote',
|
|
166
|
+
channel: 'stable',
|
|
167
|
+
location: 'https://registry.example.test/catalog.json',
|
|
168
|
+
profile: 'published-v1',
|
|
169
|
+
},
|
|
170
|
+
trustedOrigins: ['https://registry.example.test'],
|
|
171
|
+
timeoutMs: 15_000,
|
|
172
|
+
maxRedirects: 3,
|
|
173
|
+
signal: abortController.signal,
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Fetching requires an exact canonical origin allowlist and validates the initial URL and every
|
|
178
|
+
redirect before contacting it. HTTP requires `allowInsecureHttp: true` in addition to the origin
|
|
179
|
+
allowlist; this also explicitly permits a trusted HTTPS-to-HTTP redirect. Credentials and cookies
|
|
180
|
+
are not forwarded. Origin trust is the caller's network policy, including any private or loopback
|
|
181
|
+
address it chooses; the package does not claim DNS or network sandbox enforcement.
|
|
182
|
+
|
|
183
|
+
The total deadline defaults to 15 seconds (maximum five minutes), redirects to three (maximum eight),
|
|
184
|
+
and the decoded response byte ceiling follows the catalog limits. Content-Length and streamed bytes
|
|
185
|
+
are checked, including decompressed responses. Only HTTP 200 is accepted. Cancellation, deadline,
|
|
186
|
+
origin, redirect, status and size failures produce `AcpRegistryFetchError` with a stable `code`;
|
|
187
|
+
invalid catalogs retain `AcpRegistryValidationError`. No response diagnostic body is included in
|
|
188
|
+
HTTP-status errors. `fetchedAt` is an observation time, not a freshness or authenticity guarantee.
|
|
189
|
+
|
|
190
|
+
The result includes the validated snapshot, exact accepted UTF-8 `content`, final URL and observation
|
|
191
|
+
time. Persist content and its source descriptor under caller policy if needed. Fetch failure never
|
|
192
|
+
chooses cached data, changes an installation or activates a provider. No cache directory is created.
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
## Install, retain and launch
|
|
196
|
+
|
|
197
|
+
The following lifecycle sketch assumes an approved binary plan and independently retained input
|
|
198
|
+
pins. A production host must persist the installation identity/reference and own error recovery.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { parseAgentInstanceId, parseAgentProviderKey } from '@agen-ai/agent-protocol';
|
|
202
|
+
import { createAgentProviderRegistry } from '@agen-ai/agent-runtime';
|
|
203
|
+
import { openAcpInstallationStore } from '@agen-ai/acp-registry/node';
|
|
204
|
+
import { createNodeAcpProviderDriver } from '@agen-ai/provider-acp/node';
|
|
205
|
+
|
|
206
|
+
const store = await openAcpInstallationStore({ root: '/var/lib/my-host/acp-installations' });
|
|
207
|
+
const receipt = await store.install({
|
|
208
|
+
plan,
|
|
209
|
+
expectedPlanDigest: approvedPlanDigest,
|
|
210
|
+
localArtifacts: [{ path: '/approved-inputs/agent.tar.gz', sha256: approvedArtifactSha256 }],
|
|
211
|
+
signal: abortController.signal,
|
|
212
|
+
});
|
|
213
|
+
const identity = { planDigest: receipt.plan.digest, installationId: receipt.installationId };
|
|
214
|
+
const reference = await store.acquire({ ...identity, ownerId: 'session-123' });
|
|
215
|
+
// Durably persist the exact reference under the host's ownership before process launch.
|
|
216
|
+
const launch = await store.resolveLaunch(reference);
|
|
217
|
+
const providerKey = parseAgentProviderKey('installed-acp-agent');
|
|
218
|
+
const instanceId = parseAgentInstanceId('installed-agent:primary');
|
|
219
|
+
const driver = createNodeAcpProviderDriver({
|
|
220
|
+
providerKey,
|
|
221
|
+
launch: {
|
|
222
|
+
executable: launch.executable,
|
|
223
|
+
args: launch.args,
|
|
224
|
+
env: { HOME: '/var/lib/my-host/agent-home', PATH: '/usr/bin:/bin', ...launch.env },
|
|
225
|
+
cwd: '/work/project',
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
const registry = await createAgentProviderRegistry({
|
|
229
|
+
drivers: [driver],
|
|
230
|
+
definitions: [{ providerKey, instanceId, driverConfiguration: {} }],
|
|
231
|
+
});
|
|
232
|
+
try {
|
|
233
|
+
const instance = registry.requireInstance(instanceId);
|
|
234
|
+
// Open a session and consume startTurn().outputs, answering through the execution handle.
|
|
235
|
+
} finally {
|
|
236
|
+
await registry.dispose();
|
|
237
|
+
// A failed native cleanup throws above and deliberately leaves the reference retained.
|
|
238
|
+
await store.release(reference);
|
|
239
|
+
}
|
|
240
|
+
await store.remove(identity);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Persist the exact reference before launching. Keep it until all processes using the installation
|
|
244
|
+
have stopped, including startup that fails before a registry is returned. Failed startup or cleanup
|
|
245
|
+
does not prove quiescence and must not trigger an unconditional reference release or store deletion.
|
|
246
|
+
Retry owned cleanup or establish quiescence through the host before releasing the exact reference.
|
|
247
|
+
Keep that installation reference throughout an execution's pending human requests and later output.
|
|
248
|
+
An independent `resolveRequest` delivery receipt does not complete the turn or release its native
|
|
249
|
+
process. Retain one `startTurn` execution/output reader until actual terminality and cleanup; route
|
|
250
|
+
human decisions through a separately owned handler. The public
|
|
251
|
+
[registry example](../../examples/registry-install-run.ts) composes the shared session helper, and
|
|
252
|
+
the [live-output example](../../examples/live-request-output.ts) isolates that runtime lifecycle.
|
|
253
|
+
The provider launcher supplies no sandbox, account credentials or filesystem/network authority.
|
|
254
|
+
The registry is also usable without the provider package when another explicit process owner
|
|
255
|
+
consumes the verified launch recipe.
|
|
256
|
+
|
|
257
|
+
Installation and launch require the current OS/architecture to match the plan. The caller supplies
|
|
258
|
+
an absent store directory under an existing owned parent, or reopens a valid owned store. Foreign
|
|
259
|
+
and substituted roots fail closed. The library creates a private stage, verifies every retained file,
|
|
260
|
+
directory and contained symlink, writes a receipt, synchronizes supported filesystems and publishes
|
|
261
|
+
with one same-filesystem rename. POSIX receipts record `synced-rename`; platforms without directory
|
|
262
|
+
synchronization record only `atomic-rename`. Native Windows/macOS qualification is separate from
|
|
263
|
+
Linux source fixtures. Archive paths, links, special files, case collisions, payload size, entry count,
|
|
264
|
+
path depth and decoder output are bounded. Supported binary formats are tar, tar.gz/tgz,
|
|
265
|
+
tar.bz2/tbz2, ZIP and raw executables; ambiguous URLs require `binaryFormat` in the plan. System
|
|
266
|
+
installer formats and implicit shell command expansion are unsupported.
|
|
267
|
+
|
|
268
|
+
Binary input can be an explicitly mapped local file or a bounded download under the plan's origin,
|
|
269
|
+
HTTP and redirect policy. Expected SHA-256 and measured SHA-256 remain separate receipt facts.
|
|
270
|
+
Permissive missing-integrity plans retain their gap after successful measurement. The receipt binds
|
|
271
|
+
the complete payload inventory, plan, launch recipe and verification evidence. Its content identity
|
|
272
|
+
excludes observation time and machine-specific toolchain paths; its receipt digest binds the complete
|
|
273
|
+
record. Hashes establish byte identity, not publisher authenticity or immunity to same-user tampering.
|
|
274
|
+
Use a host-owned store and trusted toolchain roots; the library does not create an OS sandbox.
|
|
275
|
+
|
|
276
|
+
`parseAcpInstallPlan(serializedValue, independentlyRetainedDigest)` validates retained plans without
|
|
277
|
+
I/O. Installation reuses this check, derives launch/policy requirements again and rejects a matching
|
|
278
|
+
self-computed digest whose fields contradict those requirements. `read(identity)` verifies the exact
|
|
279
|
+
receipt and complete current payload. For `synced-rename` receipts, reads also synchronize the
|
|
280
|
+
installation and its parent before returning, reestablishing persistence after an earlier rename
|
|
281
|
+
whose final synchronization may have failed. Reads never download repairs. `resolveLaunch(reference)` also
|
|
282
|
+
checks retained toolchain entry hashes and needs a live exact reference. No registry access, latest
|
|
283
|
+
resolution, `npx` or `uvx` invocation occurs during launch. Removal and publication serialize against
|
|
284
|
+
references; changing a plan creates a distinct installation and leaves older referenced payloads usable.
|
|
285
|
+
|
|
286
|
+
## Explicit package toolchains and artifacts
|
|
287
|
+
|
|
288
|
+
Call `inspectAcpToolchains({ toolchains, workRoot })` with absolute locations for Node plus the npm CLI
|
|
289
|
+
JavaScript file, or Python plus uv. It runs bounded version probes with an explicit temporary home
|
|
290
|
+
and environment and measures the canonical executable entries. Use the resulting kind/version/SHA
|
|
291
|
+
identities in `createAcpInstallPlan`; provide the locations separately to `store.install`. An entry hash
|
|
292
|
+
does not attest an entire external Node/Python installation, standard library, npm dependency tree or
|
|
293
|
+
OS. Those roots remain caller-managed dependencies. Installation and retained launch reject changed
|
|
294
|
+
entry bytes instead of finding another executable on PATH.
|
|
295
|
+
|
|
296
|
+
npm receives verified local tarballs through a transformed private lock and runs real `npm ci` in
|
|
297
|
+
offline mode with lifecycle scripts, bin links, audit and funding disabled. The original logical lock
|
|
298
|
+
is retained. Actual manifests, dependency closure, version constraints and the root-owned JavaScript
|
|
299
|
+
bin are checked. Python receives verified wheels and runs real offline `uv pip install` into a retained
|
|
300
|
+
site-packages directory with hash requirements and no index, configuration, cache or source builds.
|
|
301
|
+
Actual metadata and the root-owned console script are checked without importing installed package
|
|
302
|
+
code. The launch bootstrap uses the exact Python executable and its retained payload path.
|
|
303
|
+
|
|
304
|
+
`localArtifacts` maps approved bytes by URL and/or SHA-256. `packageArtifacts` supplies explicit
|
|
305
|
+
name/version/URL/filename/SHA mappings where required, particularly Python wheel filenames and a
|
|
306
|
+
complete closure for plans without an original lock. Package managers never fetch missing artifacts.
|
|
307
|
+
A plan may explicitly permit the library to download approved artifacts before invoking the offline
|
|
308
|
+
manager. Newly observed closure does not remove a plan's missing-original-lock gap. Enabled scripts,
|
|
309
|
+
Python source distributions, peer/optional/platform-conditioned npm dependencies, and Python
|
|
310
|
+
markers/extras require additional profiles and fail explicitly here. Offline manager flags are not
|
|
311
|
+
network confinement; execution of arbitrary install scripts is deliberately unsupported.
|
|
312
|
+
|
|
313
|
+
## Limits, failures and recovery
|
|
314
|
+
|
|
315
|
+
`DEFAULT_ACP_INSTALLATION_LIMITS` exposes finite configurable resource limits: 256 MiB per artifact,
|
|
316
|
+
1 GiB total artifact and payload bytes, 2 GiB decoded archive bytes, 25,000 entries, depth 32,
|
|
317
|
+
4,096-byte paths, three download redirects, two-minute artifact/tool deadlines, 1 MiB combined tool
|
|
318
|
+
output, 16 MiB receipts and 1,024 active references. Larger values remain bounded by package ceilings.
|
|
319
|
+
Errors use `AcpInstallationError` with stable `code`; malformed plans retain registry validation issues.
|
|
320
|
+
Registry and installation failures remain distinct from Agent Protocol V10 operational failures and
|
|
321
|
+
provider retry observations. This package has no runtime dependency or policy for retrying an agent
|
|
322
|
+
turn; retained installation integrity and process quiescence do not settle uncertain native execution.
|
|
323
|
+
|
|
324
|
+
Identical-plan operations use an exclusive cross-process lock and return `store_busy` when occupied.
|
|
325
|
+
They do not wait indefinitely or replace a lock based on age or PID. Cancellation terminates the directly owned tool and its POSIX process group, closes retained pipes
|
|
326
|
+
and waits for the direct child to exit before cleanup. Processes that deliberately escape that group
|
|
327
|
+
require caller-provided OS containment; Windows does not gain a process-tree sandbox from this API.
|
|
328
|
+
Failed publication never exposes a partial installation. Crash residue remains
|
|
329
|
+
inspectable through `inspectAttempt(planDigest)`. `recoverAttempt({ ...attempt, ownerQuiesced: true })`
|
|
330
|
+
requires the exact retained attempt and caller-established quiescence before cleaning owned residue.
|
|
331
|
+
Unknown or conflicting residue is retained. An active reference blocks removal until its exact owner
|
|
332
|
+
releases it; the library does not infer process lifetime or automatically expire references.
|
|
333
|
+
|
|
334
|
+
## Real installation test prerequisites
|
|
335
|
+
|
|
336
|
+
The package tests execute real npm and uv against committed synthetic local tarballs and wheels.
|
|
337
|
+
They require Node/npm, Python and uv on the test host; missing tools fail with an explicit
|
|
338
|
+
prerequisite error. No registry credentials, real agent accounts or inference are involved. Linux
|
|
339
|
+
CI installs Python `3.14.4` and uv `0.11.28` through immutable action revisions before running these
|
|
340
|
+
tests. The standalone SDK CI runs them on Node 22 and 24; its release workflow uses the same
|
|
341
|
+
Python/uv prerequisites. These fixtures qualify the Linux test lane; they do not establish native
|
|
342
|
+
Windows or macOS installation qualification.
|
|
343
|
+
|
|
344
|
+
Run `pnpm --filter @agen-ai/acp-registry test` after installing those prerequisites. The fixture
|
|
345
|
+
discovery helper measures the actual tool entries and versions before constructing each plan;
|
|
346
|
+
production requires explicit caller-selected paths. `tests/fixtures/install-packages/generate.py`
|
|
347
|
+
recreates the small deterministic fixtures using only the Python standard library. Fixture
|
|
348
|
+
generation never imports package code or invokes package lifecycle scripts.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AcpRegistryEntry, AcpRegistrySnapshot } from './contracts.js';
|
|
2
|
+
/** Verifies serializable normalized metadata and self-consistency; it does not authenticate its publisher. */
|
|
3
|
+
export declare function validateAcpRegistryEntry(input: unknown): AcpRegistryEntry;
|
|
4
|
+
/** A retained rawDigest is provenance evidence, never proof that these bytes came from a trusted publisher. */
|
|
5
|
+
export declare function validateAcpRegistrySnapshot(input: unknown): AcpRegistrySnapshot;
|
|
6
|
+
//# sourceMappingURL=assert.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { MAX_ACP_REGISTRY_LIMITS } from "./limits.js";
|
|
2
|
+
import { acpRegistryEntryToPublished, normalizeAcpRegistryEntry } from "./normalize.js";
|
|
3
|
+
import { acpRegistrySchemas } from "./schemas.js";
|
|
4
|
+
import { acpRegistrySourceIdentity, parseAcpRegistrySource } from "./source.js";
|
|
5
|
+
import { copyBoundedJson } from "../shared/bounds.js";
|
|
6
|
+
import { canonicalDigest } from "../shared/digest.js";
|
|
7
|
+
import { invalidRegistry, registrySchemaError } from "../shared/errors.js";
|
|
8
|
+
import { deepFreeze } from "../shared/immutable.js";
|
|
9
|
+
function validateAcpRegistryEntry(input) {
|
|
10
|
+
const captured = copyBoundedJson(input, MAX_ACP_REGISTRY_LIMITS);
|
|
11
|
+
const schemas = acpRegistrySchemas(MAX_ACP_REGISTRY_LIMITS);
|
|
12
|
+
const result = schemas.normalizedEntry.safeParse(captured);
|
|
13
|
+
if (!result.success) registrySchemaError(result.error, captured);
|
|
14
|
+
assertCanonicalEntry(result.data);
|
|
15
|
+
return deepFreeze(result.data);
|
|
16
|
+
}
|
|
17
|
+
function assertCanonicalEntry(entry) {
|
|
18
|
+
const sourceIdentity = entry.identity.slice(0, entry.identity.lastIndexOf("/"));
|
|
19
|
+
const normalized = normalizeAcpRegistryEntry(acpRegistryEntryToPublished(entry), sourceIdentity, "preview", 0);
|
|
20
|
+
if (canonicalDigest(normalized) !== canonicalDigest(entry)) invalidRegistry("entry_digest_mismatch", ["digest"], "Registry entry metadata, identity or digest is not canonical.");
|
|
21
|
+
}
|
|
22
|
+
function validateAcpRegistrySnapshot(input) {
|
|
23
|
+
const captured = copyBoundedJson(input, MAX_ACP_REGISTRY_LIMITS);
|
|
24
|
+
const result = acpRegistrySchemas(MAX_ACP_REGISTRY_LIMITS).normalizedSnapshot.safeParse(captured);
|
|
25
|
+
if (!result.success) registrySchemaError(result.error, captured);
|
|
26
|
+
const snapshot = result.data;
|
|
27
|
+
const source = parseAcpRegistrySource(snapshot.source);
|
|
28
|
+
if (snapshot.sourceIdentity !== acpRegistrySourceIdentity(source) || canonicalDigest(source) !== canonicalDigest(snapshot.source)) {
|
|
29
|
+
invalidRegistry("source_identity_mismatch", ["sourceIdentity"], "Registry source identity does not match its canonical descriptor.");
|
|
30
|
+
}
|
|
31
|
+
let previous = "";
|
|
32
|
+
for (const [index, entry] of snapshot.entries.entries()) {
|
|
33
|
+
assertCanonicalEntry(entry);
|
|
34
|
+
if (entry.id <= previous) invalidRegistry("duplicate_identity", ["entries", index, "id"], "Registry entries must have unique identities in canonical order.");
|
|
35
|
+
previous = entry.id;
|
|
36
|
+
if (entry.identity !== `${snapshot.sourceIdentity}/${entry.id}`) invalidRegistry("source_identity_mismatch", ["entries", index, "identity"], "Registry entry belongs to a different source.");
|
|
37
|
+
if (source.channel === "stable" && entry.version.includes("-")) invalidRegistry("channel_mismatch", ["entries", index, "version"], "Stable catalogs cannot contain preview release identities.");
|
|
38
|
+
}
|
|
39
|
+
const { digest, ...body } = snapshot;
|
|
40
|
+
if (canonicalDigest(body) !== digest) invalidRegistry("snapshot_digest_mismatch", ["digest"], "Registry normalized snapshot digest does not match its metadata.");
|
|
41
|
+
return deepFreeze(snapshot);
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
validateAcpRegistryEntry,
|
|
45
|
+
validateAcpRegistrySnapshot
|
|
46
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type AcpRegistryTarget = 'darwin-aarch64' | 'darwin-x86_64' | 'linux-aarch64' | 'linux-x86_64' | 'windows-aarch64' | 'windows-x86_64';
|
|
2
|
+
export type AcpRegistryDistributionKind = 'binary' | 'npx' | 'uvx';
|
|
3
|
+
export interface AcpRegistrySource {
|
|
4
|
+
readonly namespace: string;
|
|
5
|
+
readonly kind: 'remote' | 'local';
|
|
6
|
+
readonly channel: 'stable' | 'preview';
|
|
7
|
+
readonly location: string;
|
|
8
|
+
readonly profile: 'published-v1';
|
|
9
|
+
}
|
|
10
|
+
interface AcpRegistryLaunchArguments {
|
|
11
|
+
readonly args: readonly string[];
|
|
12
|
+
readonly env: Readonly<Record<string, string>>;
|
|
13
|
+
}
|
|
14
|
+
export interface AcpRegistryBinaryDistribution extends AcpRegistryLaunchArguments {
|
|
15
|
+
readonly kind: 'binary';
|
|
16
|
+
readonly target: AcpRegistryTarget;
|
|
17
|
+
readonly archive: string;
|
|
18
|
+
readonly sha256?: string;
|
|
19
|
+
/** A contained POSIX-relative payload path, independent of the host platform. */
|
|
20
|
+
readonly executable: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AcpRegistryPackageDistribution extends AcpRegistryLaunchArguments {
|
|
23
|
+
readonly kind: 'npx' | 'uvx';
|
|
24
|
+
/** Literal catalog metadata; an unversioned specifier has no immutable resolution. */
|
|
25
|
+
readonly package: string;
|
|
26
|
+
}
|
|
27
|
+
export type AcpRegistryDistribution = AcpRegistryBinaryDistribution | AcpRegistryPackageDistribution;
|
|
28
|
+
export interface AcpRegistryEntry {
|
|
29
|
+
readonly identity: string;
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly version: string;
|
|
33
|
+
readonly description: string;
|
|
34
|
+
readonly repository?: string;
|
|
35
|
+
readonly website?: string;
|
|
36
|
+
readonly authors: readonly string[];
|
|
37
|
+
readonly license?: string;
|
|
38
|
+
readonly licenseUrl?: string;
|
|
39
|
+
readonly icon?: string;
|
|
40
|
+
/** Presentation metadata only; it grants no installation or execution authority. */
|
|
41
|
+
readonly bundled?: boolean;
|
|
42
|
+
readonly distributions: readonly AcpRegistryDistribution[];
|
|
43
|
+
readonly digest: string;
|
|
44
|
+
}
|
|
45
|
+
export interface AcpRegistrySnapshot {
|
|
46
|
+
readonly format: 'acp-registry-published-v1';
|
|
47
|
+
readonly source: AcpRegistrySource;
|
|
48
|
+
readonly sourceIdentity: string;
|
|
49
|
+
/** SHA-256 of the exact accepted UTF-8 input bytes. */
|
|
50
|
+
readonly rawDigest: string;
|
|
51
|
+
/** Canonical digest binding the normalized snapshot and its raw-byte provenance. */
|
|
52
|
+
readonly digest: string;
|
|
53
|
+
readonly entries: readonly AcpRegistryEntry[];
|
|
54
|
+
}
|
|
55
|
+
export interface AcpRegistryLimits {
|
|
56
|
+
readonly maxSnapshotBytes: number;
|
|
57
|
+
readonly maxEntries: number;
|
|
58
|
+
readonly maxDepth: number;
|
|
59
|
+
readonly maxValues: number;
|
|
60
|
+
readonly maxStringBytes: number;
|
|
61
|
+
readonly maxArgs: number;
|
|
62
|
+
readonly maxEnvEntries: number;
|
|
63
|
+
}
|
|
64
|
+
export interface ParseAcpRegistrySnapshotInput {
|
|
65
|
+
readonly source: AcpRegistrySource;
|
|
66
|
+
readonly content: string | Uint8Array;
|
|
67
|
+
readonly limits?: Partial<AcpRegistryLimits>;
|
|
68
|
+
}
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
File without changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Both separators describe contained archive paths; they never choose a host shell or working directory. */
|
|
2
|
+
export declare function normalizeRegistryExecutable(input: string): string;
|
|
3
|
+
export declare function validateRegistryPackage(input: string, kind: 'npx' | 'uvx'): void;
|
|
4
|
+
//# sourceMappingURL=launch.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { invalidRegistry } from "../shared/errors.js";
|
|
2
|
+
function normalizeRegistryExecutable(input) {
|
|
3
|
+
const path = input.replaceAll("\\", "/");
|
|
4
|
+
if (!path || path.startsWith("/") || /[:\0\p{Cc}\p{Cf}]/u.test(path)) invalidRegistry("unsafe_executable", ["cmd"], "Registry commands must identify contained payload files.");
|
|
5
|
+
const segments = path.split("/");
|
|
6
|
+
if (segments.some((segment) => segment === ".." || segment === "" || /[. ]$/u.test(segment) && segment !== ".")) invalidRegistry("unsafe_executable", ["cmd"], "Registry payload paths cannot traverse, contain empty segments or ambiguous trailing dots/spaces.");
|
|
7
|
+
const normalized = segments.filter((segment) => segment !== ".").join("/");
|
|
8
|
+
if (!normalized) invalidRegistry("unsafe_executable", ["cmd"], "Registry commands must identify a payload file.");
|
|
9
|
+
return normalized;
|
|
10
|
+
}
|
|
11
|
+
function validateRegistryPackage(input, kind) {
|
|
12
|
+
const npm = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@(?:\*|[a-zA-Z0-9][a-zA-Z0-9.+_-]*|[\^~][0-9][a-zA-Z0-9.+_*-]*))?$/u;
|
|
13
|
+
const python = /^[a-zA-Z0-9][a-zA-Z0-9._-]*(?:@[a-zA-Z0-9][a-zA-Z0-9.+!_-]*|(?:(?:==|!=|>=|<=|~=|>|<)[a-zA-Z0-9][a-zA-Z0-9.+!*_-]*)(?:,(?:==|!=|>=|<=|~=|>|<)[a-zA-Z0-9][a-zA-Z0-9.+!*_-]*)*)?$/u;
|
|
14
|
+
if (!(kind === "npx" ? npm : python).test(input)) invalidRegistry("unsafe_package", ["package"], "Registry packages require literal registry names and supported version, tag or range selectors.");
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
normalizeRegistryExecutable,
|
|
18
|
+
validateRegistryPackage
|
|
19
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AcpRegistryLimits } from './contracts.js';
|
|
2
|
+
export declare const DEFAULT_ACP_REGISTRY_LIMITS: AcpRegistryLimits;
|
|
3
|
+
export declare const MAX_ACP_REGISTRY_LIMITS: AcpRegistryLimits;
|
|
4
|
+
export declare function resolveAcpRegistryLimits(input?: Partial<AcpRegistryLimits>): AcpRegistryLimits;
|
|
5
|
+
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { invalidRegistry } from "../shared/errors.js";
|
|
2
|
+
const DEFAULT_ACP_REGISTRY_LIMITS = Object.freeze({
|
|
3
|
+
maxSnapshotBytes: 8 * 1024 * 1024,
|
|
4
|
+
maxEntries: 1024,
|
|
5
|
+
maxDepth: 16,
|
|
6
|
+
maxValues: 2e5,
|
|
7
|
+
maxStringBytes: 16 * 1024,
|
|
8
|
+
maxArgs: 128,
|
|
9
|
+
maxEnvEntries: 128
|
|
10
|
+
});
|
|
11
|
+
const MAX_ACP_REGISTRY_LIMITS = Object.freeze({
|
|
12
|
+
maxSnapshotBytes: 64 * 1024 * 1024,
|
|
13
|
+
maxEntries: 1e4,
|
|
14
|
+
maxDepth: 64,
|
|
15
|
+
maxValues: 1e6,
|
|
16
|
+
maxStringBytes: 1024 * 1024,
|
|
17
|
+
maxArgs: 1024,
|
|
18
|
+
maxEnvEntries: 1024
|
|
19
|
+
});
|
|
20
|
+
function resolveAcpRegistryLimits(input = {}) {
|
|
21
|
+
if (input === null || typeof input !== "object" || Object.getPrototypeOf(input) !== Object.prototype) invalidRegistry("invalid_limits", ["limits"], "Registry limits must be a plain object.");
|
|
22
|
+
const resolved = { ...DEFAULT_ACP_REGISTRY_LIMITS };
|
|
23
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
24
|
+
if (typeof key !== "string" || !Object.hasOwn(MAX_ACP_REGISTRY_LIMITS, key)) invalidRegistry("invalid_limits", ["limits"], "Unknown registry limit.");
|
|
25
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
26
|
+
if (!descriptor?.enumerable || !Object.hasOwn(descriptor, "value")) invalidRegistry("invalid_limits", ["limits", key], "Registry limits require enumerable data properties.");
|
|
27
|
+
const value = descriptor.value;
|
|
28
|
+
const minimum = key === "maxArgs" || key === "maxEnvEntries" ? 0 : 1;
|
|
29
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || value > MAX_ACP_REGISTRY_LIMITS[key]) {
|
|
30
|
+
invalidRegistry("invalid_limits", ["limits", key], "Registry limit is outside its supported finite range.");
|
|
31
|
+
}
|
|
32
|
+
resolved[key] = value;
|
|
33
|
+
}
|
|
34
|
+
return Object.freeze(resolved);
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
DEFAULT_ACP_REGISTRY_LIMITS,
|
|
38
|
+
MAX_ACP_REGISTRY_LIMITS,
|
|
39
|
+
resolveAcpRegistryLimits
|
|
40
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { z } from 'zod/v4';
|
|
2
|
+
import type { AcpRegistryEntry } from './contracts.js';
|
|
3
|
+
import { type acpRegistrySchemas } from './schemas.js';
|
|
4
|
+
type RawEntry = z.infer<ReturnType<typeof acpRegistrySchemas>['rawEntry']>;
|
|
5
|
+
export declare function normalizeAcpRegistryEntry(input: RawEntry, sourceIdentity: string, channel: 'stable' | 'preview', index: number): AcpRegistryEntry;
|
|
6
|
+
/** Reuses the published shape's closure for normalized snapshot admission without trusting retained digests. */
|
|
7
|
+
export declare function acpRegistryEntryToPublished(entry: AcpRegistryEntry): RawEntry;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=normalize.d.ts.map
|