@nestm/storage 0.1.0-alpha.7 → 0.1.0-alpha.8
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 +110 -0
- package/README.md +217 -18
- package/SECURITY.md +34 -5
- package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts +14 -3
- package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts.map +1 -1
- package/dist/ai-sdk/ai-sdk-workspace-tools.js +54 -23
- package/dist/ai-sdk/ai-sdk-workspace-tools.js.map +1 -1
- package/dist/ai-sdk/index.d.ts +1 -1
- package/dist/ai-sdk/index.d.ts.map +1 -1
- package/dist/ai-sdk/index.js.map +1 -1
- package/dist/files-sdk/files-sdk.driver.d.ts +54 -10
- package/dist/files-sdk/files-sdk.driver.d.ts.map +1 -1
- package/dist/files-sdk/files-sdk.driver.js +784 -63
- package/dist/files-sdk/files-sdk.driver.js.map +1 -1
- package/dist/files-sdk/fs/index.d.ts +4 -4
- package/dist/files-sdk/fs/index.d.ts.map +1 -1
- package/dist/files-sdk/fs/index.js +242 -9
- package/dist/files-sdk/fs/index.js.map +1 -1
- package/dist/files-sdk/index.d.ts +1 -1
- package/dist/files-sdk/index.d.ts.map +1 -1
- package/dist/files-sdk/index.js.map +1 -1
- package/dist/files-sdk/provider/index.d.ts +9 -5
- package/dist/files-sdk/provider/index.d.ts.map +1 -1
- package/dist/files-sdk/provider/index.js +53 -15
- package/dist/files-sdk/provider/index.js.map +1 -1
- package/dist/files-sdk/s3/construction-metadata.d.ts +7 -0
- package/dist/files-sdk/s3/construction-metadata.d.ts.map +1 -0
- package/dist/files-sdk/s3/construction-metadata.js +13 -0
- package/dist/files-sdk/s3/construction-metadata.js.map +1 -0
- package/dist/files-sdk/s3/index.d.ts +39 -19
- package/dist/files-sdk/s3/index.d.ts.map +1 -1
- package/dist/files-sdk/s3/index.js +936 -113
- package/dist/files-sdk/s3/index.js.map +1 -1
- package/dist/gateway/storage-gateway.controller.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.controller.js +25 -5
- package/dist/gateway/storage-gateway.controller.js.map +1 -1
- package/dist/storage-etag.d.ts +13 -0
- package/dist/storage-etag.d.ts.map +1 -0
- package/dist/storage-etag.js +31 -0
- package/dist/storage-etag.js.map +1 -0
- package/dist/storage.client.d.ts +3 -1
- package/dist/storage.client.d.ts.map +1 -1
- package/dist/storage.client.js +87 -22
- package/dist/storage.client.js.map +1 -1
- package/dist/storage.driver.d.ts +21 -1
- package/dist/storage.driver.d.ts.map +1 -1
- package/dist/storage.driver.js.map +1 -1
- package/dist/storage.types.d.ts +90 -19
- package/dist/storage.types.d.ts.map +1 -1
- package/dist/storage.types.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/provider-conformance.d.ts +65 -0
- package/dist/testing/provider-conformance.d.ts.map +1 -0
- package/dist/testing/provider-conformance.js +853 -0
- package/dist/testing/provider-conformance.js.map +1 -0
- package/dist/workspace/index.d.ts +1 -0
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +1 -0
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/storage-workspace.cursor.d.ts +68 -3
- package/dist/workspace/storage-workspace.cursor.d.ts.map +1 -1
- package/dist/workspace/storage-workspace.cursor.js +402 -39
- package/dist/workspace/storage-workspace.cursor.js.map +1 -1
- package/dist/workspace/storage-workspace.d.ts.map +1 -1
- package/dist/workspace/storage-workspace.js +54 -46
- package/dist/workspace/storage-workspace.js.map +1 -1
- package/dist/workspace/storage-workspace.types.d.ts +10 -2
- package/dist/workspace/storage-workspace.types.d.ts.map +1 -1
- package/dist/workspace/storage-workspace.types.js +1 -0
- package/dist/workspace/storage-workspace.types.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,115 @@
|
|
|
1
1
|
# @nestm/storage
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.8
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d81c6f4: Add a typed `mapCreateConflict` hook to the AI SDK workspace adapter so
|
|
8
|
+
applications can represent atomic create collisions as domain results without
|
|
9
|
+
mutating generated tools. Keep replace/ETag conflicts fail-closed and sanitize
|
|
10
|
+
mapper failures at the tool boundary.
|
|
11
|
+
|
|
12
|
+
Mark workspace tools with optional inputs or a combined create/replace union as
|
|
13
|
+
non-strict for provider schema generation while retaining strict Zod runtime
|
|
14
|
+
validation.
|
|
15
|
+
|
|
16
|
+
- a0ea392: Add injectable, replica-safe workspace pagination cursors. The workspace now
|
|
17
|
+
binds versioned cursor payloads to stable store, mount, tenant/workspace, prefix,
|
|
18
|
+
operation, query, limit, and expiry context; authorizes non-consuming replay
|
|
19
|
+
before that expiry; and rejects altered or cross-context continuations.
|
|
20
|
+
Successful continuation still depends on the embedded provider cursor remaining
|
|
21
|
+
valid and available.
|
|
22
|
+
|
|
23
|
+
Export an AES-256-GCM key-ring codec for stateless multi-replica deployments and
|
|
24
|
+
an asynchronous byte-payload codec contract for shared durable opaque-token
|
|
25
|
+
stores. Cursor payloads and tokens are bounded, provider continuations remain
|
|
26
|
+
opaque, and pagination fails closed when no cursor mechanism is configured.
|
|
27
|
+
Compatible replicas rely on the universal driver contract for non-consuming,
|
|
28
|
+
instance-portable provider cursors whose position is independent of page size
|
|
29
|
+
while the provider token remains valid. Cursor expiry is not a provider-token
|
|
30
|
+
retention, snapshot-isolation, or uptime promise; provider invalidation remains
|
|
31
|
+
an operational list failure.
|
|
32
|
+
|
|
33
|
+
- d996b92: Split the aggregate S3 conditional-mutation and copy declarations into exact
|
|
34
|
+
create, replace, delete, read, source-copy, destination-copy, atomic-promotion,
|
|
35
|
+
and multipart-completion capabilities. Add independent AWS S3, Cloudflare R2,
|
|
36
|
+
and fail-closed custom-endpoint profiles that force unverified drivers
|
|
37
|
+
read-only; enforce complete physical-key byte budgets; normalize provider
|
|
38
|
+
errors without retaining raw provider payloads or causes; and publish a reusable
|
|
39
|
+
provider conformance contract with gated filesystem, AWS, R2, and custom suites.
|
|
40
|
+
|
|
41
|
+
Normalize every provider ETag to a canonical bare 1–1024-byte visible ASCII
|
|
42
|
+
token and serialize exactly one HTTP quote pair at S3 request boundaries.
|
|
43
|
+
Quoted, weak, wildcard, list-shaped, control-bearing, non-ASCII, and otherwise
|
|
44
|
+
unsafe values now fail closed instead of being accepted as arbitrary non-empty
|
|
45
|
+
strings. Applications that persisted quoted ETags must refresh them from
|
|
46
|
+
provider metadata before conditional mutation; this prevents wildcard/list and
|
|
47
|
+
header-ambiguity inputs from widening an exact-match precondition.
|
|
48
|
+
|
|
49
|
+
Specify and test provider list cursors as non-consuming replayable tokens for
|
|
50
|
+
unchanged provider state, so higher-level replayable pagination can fail closed
|
|
51
|
+
when a provider cannot meet that contract.
|
|
52
|
+
|
|
53
|
+
Exercise the complete advertised source-condition by destination-condition
|
|
54
|
+
promotion matrix, including stale-state preservation and competing stale/valid
|
|
55
|
+
requests. Provider documentation or audited implementation evidence remains
|
|
56
|
+
required for the internal one-linearization-point claim.
|
|
57
|
+
|
|
58
|
+
Ordinary and conditional provider failures now expose stable public messages
|
|
59
|
+
and preserve only normalized codes and retry flags; raw provider bodies,
|
|
60
|
+
request metadata, and nested causes are not retained in loggable error shapes.
|
|
61
|
+
|
|
62
|
+
Raise the `@aws-sdk/client-s3` peer floor to 3.919.0, the first release that
|
|
63
|
+
serializes destination conditions for `CopyObject`, and verify the real wire
|
|
64
|
+
headers in the packed minimum-peer consumer. Native AWS construction now
|
|
65
|
+
disables environment and shared-config endpoint URL overrides, while the public
|
|
66
|
+
capability helper derives custom-endpoint provenance from the actual SDK client
|
|
67
|
+
instead of a duplicated caller hint. Capability decoration is now single-use
|
|
68
|
+
per raw S3 adapter, preventing broader operations from surviving a later
|
|
69
|
+
narrower profile application.
|
|
70
|
+
|
|
71
|
+
Bind S3 provider authority to package-private raw-client and adapter-method
|
|
72
|
+
identity plus the exact surface snapshot installed by capability decoration.
|
|
73
|
+
Structurally S3-backed raw adapters are rejected until they pass through the
|
|
74
|
+
package helper, regardless of adapter name, proxying, or forged global symbols;
|
|
75
|
+
same-client aliases cannot replace their raw client, ordinary methods, policies,
|
|
76
|
+
or conditional operations. Unverified custom endpoints and noncanonical
|
|
77
|
+
S3-backed provider slugs are forced read-only, while an explicit branded profile
|
|
78
|
+
unlocks only its declared conditional operations. Endpoint and public-URL
|
|
79
|
+
provenance now follows the adapter actually produced by the provider loader,
|
|
80
|
+
including `configJson`.
|
|
81
|
+
|
|
82
|
+
Retain `publicBaseUrl` construction policy in the package-owned `s3()` helper
|
|
83
|
+
so omitted decorator hints cannot re-enable an expiring-download claim. Unknown
|
|
84
|
+
foreign S3 construction conservatively disables that claim. Validate the exact
|
|
85
|
+
physical adapter key without stripping leading slashes, and include configured
|
|
86
|
+
separators plus list/search-derived prefixes in the provider byte budget before
|
|
87
|
+
dispatch. Search uses files-sdk's own inferred glob-prefix and zero-result
|
|
88
|
+
semantics instead of duplicating its matcher logic. The innermost dispatch guard
|
|
89
|
+
repeats these checks after supported in-process plugins have transformed an
|
|
90
|
+
operation; adapters and plugins remain trusted code rather than a sandbox
|
|
91
|
+
boundary.
|
|
92
|
+
|
|
93
|
+
Derive signed-upload policy claims from the branded provider profile instead
|
|
94
|
+
of granting them to every S3-compatible endpoint. Native AWS proves content
|
|
95
|
+
type and POST size-range enforcement; Cloudflare R2 proves content type but not
|
|
96
|
+
POST form size ranges; omitted custom declarations normalize to false/false so
|
|
97
|
+
the gateway fails closed. Profile authority is backed by a package-private
|
|
98
|
+
WeakSet after deep freezing, so reflecting and copying the nominal brand symbol
|
|
99
|
+
cannot forge conformance evidence.
|
|
100
|
+
|
|
101
|
+
Treat the built-in AWS profile as an immutable ceiling for every SDK client
|
|
102
|
+
with native endpoint provenance, independent of mutable adapter display names.
|
|
103
|
+
Explicit profiles may narrow its operations, policy bits, and key limit but
|
|
104
|
+
cannot raise the 1,024-byte physical-key budget or add unsupported claims.
|
|
105
|
+
|
|
106
|
+
Enforce every requested signed-upload constraint at URL creation time.
|
|
107
|
+
Content-type-constrained PUT URLs now sign the `content-type` header; bounded
|
|
108
|
+
AWS uploads use exact POST MIME and byte-range conditions; unsupported profile
|
|
109
|
+
constraints and lower-only S3 ranges fail before signing. Bounded POST uploads
|
|
110
|
+
also reject physical keys ending in AWS's `${filename}` template so an exact
|
|
111
|
+
authorized key cannot be widened into a prefix policy.
|
|
112
|
+
|
|
3
113
|
## 0.1.0-alpha.7
|
|
4
114
|
|
|
5
115
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -36,6 +36,14 @@ such as `files-sdk/gcs`:
|
|
|
36
36
|
pnpm add files-sdk@2.2.3
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
Pass AWS-SDK-backed S3 adapters through the package-owned `s3()` and
|
|
40
|
+
`withS3Capabilities()` helpers (or use `createS3StorageDriver()`).
|
|
41
|
+
`createFilesSdkDriver()` rejects a structurally S3-backed raw adapter that has
|
|
42
|
+
not crossed this provenance boundary, even when a wrapper renames or proxies
|
|
43
|
+
the adapter. This prevents callers from bypassing endpoint and provider-profile
|
|
44
|
+
checks by omitting the capability decorator or replacing the public `raw`
|
|
45
|
+
property while retaining methods closed over the original client.
|
|
46
|
+
|
|
39
47
|
Install only the native SDKs required by the chosen provider. For example:
|
|
40
48
|
|
|
41
49
|
```sh
|
|
@@ -50,6 +58,11 @@ pnpm add @google-cloud/storage google-auth-library
|
|
|
50
58
|
pnpm add @azure/storage-blob @azure/core-auth @azure/identity
|
|
51
59
|
```
|
|
52
60
|
|
|
61
|
+
`@aws-sdk/client-s3` 3.919.0 or newer is required. Earlier releases omit
|
|
62
|
+
destination `If-Match` and `If-None-Match` from the serialized `CopyObject`
|
|
63
|
+
request even when those fields appear in a newer compile-time command shape;
|
|
64
|
+
the package peer range and packed minimum-peer smoke test enforce this floor.
|
|
65
|
+
|
|
53
66
|
`files-sdk` currently declares its optional Nest peer for Nest 10 and 11. This
|
|
54
67
|
library does not import `files-sdk/nestjs`; the Nest 12 integration is entirely
|
|
55
68
|
owned here. A package manager may nevertheless report that temporary optional
|
|
@@ -110,12 +123,46 @@ control characters, repeated separators, and `.` or `..` segments are rejected
|
|
|
110
123
|
rather than normalized. Keys and provider cursors returned by a driver are also
|
|
111
124
|
checked before they are converted back to logical paths.
|
|
112
125
|
|
|
126
|
+
Pagination requires a server-owned cursor configuration. The built-in
|
|
127
|
+
`Aes256GcmStorageWorkspaceCursorCodec` produces versioned, authenticated,
|
|
128
|
+
encrypted tokens that can resume on another request, process, or replica when
|
|
129
|
+
every replica constructs an equivalent codec from the same key ring and uses
|
|
130
|
+
the same stable store identity, physical prefix, mount ID, trusted scope, and
|
|
131
|
+
effective limits. Use one codec instance per process, use a dedicated 32-byte
|
|
132
|
+
key, retain rotated decryption keys for at least one cursor TTL, and derive
|
|
133
|
+
`mountId` and `scope` only from authenticated server context.
|
|
134
|
+
|
|
135
|
+
The underlying driver must also implement the universal replayable list-cursor
|
|
136
|
+
contract against the same logical backend namespace: its cursor cannot be
|
|
137
|
+
consumed or tied to one driver instance. While the provider cursor remains
|
|
138
|
+
valid and available, an outer cursor can be retried until its authenticated
|
|
139
|
+
expiry. That expiry is only an authorization ceiling: it does not extend a
|
|
140
|
+
provider token's lifetime or promise snapshot isolation, provider availability,
|
|
141
|
+
network access, or valid credentials. Provider invalidation is an operational
|
|
142
|
+
list failure, and concurrent object changes remain subject to provider
|
|
143
|
+
continuation semantics. Without a codec, single-page operations still work but
|
|
144
|
+
a continuation fails closed.
|
|
145
|
+
|
|
113
146
|
```ts
|
|
114
|
-
import {
|
|
147
|
+
import {
|
|
148
|
+
Aes256GcmStorageWorkspaceCursorCodec,
|
|
149
|
+
mountStorageWorkspace,
|
|
150
|
+
} from '@nestm/storage/workspace';
|
|
151
|
+
|
|
152
|
+
// cursorKey is a separately validated 32-byte secret from deployment config.
|
|
153
|
+
const cursorCodec = new Aes256GcmStorageWorkspaceCursorCodec({
|
|
154
|
+
activeKeyId: 'v1',
|
|
155
|
+
keys: { v1: cursorKey },
|
|
156
|
+
});
|
|
115
157
|
|
|
116
158
|
const workspace = mountStorageWorkspace(agentFiles, {
|
|
117
159
|
// Use an opaque server-derived run id, never a value selected by the model.
|
|
118
160
|
prefix: `workspaces/${runId}`,
|
|
161
|
+
cursor: {
|
|
162
|
+
codec: cursorCodec,
|
|
163
|
+
mountId: `agent-workspace:${runId}`,
|
|
164
|
+
scope: `organization:${organizationId}/workspace:${workspaceId}`,
|
|
165
|
+
},
|
|
119
166
|
permissions: [
|
|
120
167
|
'list',
|
|
121
168
|
'read',
|
|
@@ -127,6 +174,8 @@ const workspace = mountStorageWorkspace(agentFiles, {
|
|
|
127
174
|
'delete',
|
|
128
175
|
],
|
|
129
176
|
limits: {
|
|
177
|
+
cursorTtlMs: 5 * 60 * 1000,
|
|
178
|
+
maxCursorBytes: 4096,
|
|
130
179
|
maxReadBytes: 1024 * 1024,
|
|
131
180
|
maxWriteBytes: 1024 * 1024,
|
|
132
181
|
maxPageSize: 100,
|
|
@@ -220,6 +269,8 @@ import type { ToolSet } from 'ai';
|
|
|
220
269
|
// A validated, opaque coordinate from trusted auth/run state.
|
|
221
270
|
// It is never accepted from a prompt or tool input.
|
|
222
271
|
prefix: context.storagePrefix,
|
|
272
|
+
// Includes the singleton codec plus stable mountId and scope.
|
|
273
|
+
cursor: context.cursorConfiguration,
|
|
223
274
|
permissions: [
|
|
224
275
|
'list',
|
|
225
276
|
'read',
|
|
@@ -261,6 +312,24 @@ workspace capability remains the authorization boundary even when approval is
|
|
|
261
312
|
disabled. The module's `AiSdkService.files()` API is the model provider's file
|
|
262
313
|
upload facility and is unrelated to storage workspaces.
|
|
263
314
|
|
|
315
|
+
Atomic create collisions remain sanitized tool errors by default. Applications
|
|
316
|
+
that model an existing destination as a normal tool result can map that one
|
|
317
|
+
case while preserving replace/ETag conflicts as failures:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const tools = createAiSdkWorkspaceTools({
|
|
321
|
+
workspace,
|
|
322
|
+
mapCreateConflict: ({ path }) => ({
|
|
323
|
+
kind: 'artifact-conflict' as const,
|
|
324
|
+
path,
|
|
325
|
+
status: 'already-exists' as const,
|
|
326
|
+
}),
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The mapper receives only the logical workspace path; provider errors, object
|
|
331
|
+
keys, and mount coordinates are never exposed.
|
|
332
|
+
|
|
264
333
|
This logical confinement is sufficient for a `ToolLoopAgent` whose only file
|
|
265
334
|
capabilities are these tools. It cannot constrain a coding harness that already
|
|
266
335
|
has shell, `node:fs`, or subprocess access. For Codex/Claude-style harnesses,
|
|
@@ -463,19 +532,36 @@ listStorageProviderSecretEnvVars('s3').map((variable) => variable.key);
|
|
|
463
532
|
The catalog is pure data and pulls in no adapter, so it is safe in config UIs,
|
|
464
533
|
health checks, and startup validation.
|
|
465
534
|
|
|
466
|
-
The `s3` slug additionally carries the
|
|
467
|
-
capabilities described under
|
|
468
|
-
[
|
|
469
|
-
|
|
470
|
-
known at build time, import
|
|
471
|
-
`@nestm/storage/files-sdk/fs` directly and skip
|
|
535
|
+
The `s3` slug additionally carries the verified per-operation profile and
|
|
536
|
+
signed-policy capabilities described under
|
|
537
|
+
[Exact provider conditions and staged-object promotion](#exact-provider-conditions-and-staged-object-promotion);
|
|
538
|
+
every provider not backed by the AWS S3 SDK exposes what its adapter declares.
|
|
539
|
+
When the provider _is_ known at build time, import
|
|
540
|
+
`@nestm/storage/files-sdk/s3` or `@nestm/storage/files-sdk/fs` directly and skip
|
|
541
|
+
the indirection.
|
|
542
|
+
|
|
543
|
+
S3 endpoint and public-URL provenance is resolved from the adapter that
|
|
544
|
+
`files-sdk` actually constructs, including values merged from `configJson`.
|
|
545
|
+
An unaudited endpoint forces the driver read-only, and a `publicBaseUrl` removes
|
|
546
|
+
the signed-download TTL guarantee because the resulting public URL does not
|
|
547
|
+
expire. AWS-SDK-backed noncanonical provider slugs (for example an S3-compatible
|
|
548
|
+
provider wrapper) also default to unverified/read-only; only the canonical
|
|
549
|
+
`s3` provider may infer the native AWS profile, and a custom endpoint becomes
|
|
550
|
+
writable only with an explicit branded `S3ProviderProfile`.
|
|
551
|
+
|
|
552
|
+
Before enabling conditional operations for a custom S3-compatible endpoint,
|
|
553
|
+
run the reusable
|
|
554
|
+
[provider conformance contract](https://github.com/nestm-dev/storage/blob/main/docs/provider-conformance.md)
|
|
555
|
+
against dedicated test credentials. Unknown endpoints are forced read-only and
|
|
556
|
+
receive no inferred conditional capabilities.
|
|
472
557
|
|
|
473
558
|
## Storage API
|
|
474
559
|
|
|
475
560
|
`StorageClient` exposes:
|
|
476
561
|
|
|
477
562
|
- `upload`, `downloadStream`, `head`, `exists`, `delete`, `copy`, and `move`;
|
|
478
|
-
-
|
|
563
|
+
- exact `uploadConditional`, `downloadConditional`, `deleteConditional`, and
|
|
564
|
+
staged-object `promote` operations when the driver advertises each primitive;
|
|
479
565
|
- `list`, cursor-aware `listAll`, and lazy `search`;
|
|
480
566
|
- `signDownload` and discriminated PUT/POST `signUpload`;
|
|
481
567
|
- `uploadMany`, `downloadMany`, `headMany`, `existsMany`, and `deleteMany`;
|
|
@@ -483,6 +569,24 @@ known at build time, import `@nestm/storage/files-sdk/s3` or
|
|
|
483
569
|
- provider capability inspection; and
|
|
484
570
|
- pause/resume/abort through `StorageUploadControl`.
|
|
485
571
|
|
|
572
|
+
Provider list cursors are opaque, non-consuming continuation tokens. Replaying
|
|
573
|
+
the same cursor and page limit against unchanged provider-visible state must
|
|
574
|
+
return an equivalent page and continuation position, even after a descendant
|
|
575
|
+
cursor has been used. A cursor is bound to the logical store, `prefix`, and
|
|
576
|
+
`delimiter`, but not to `limit`, retries, timeout, or abort signal; callers may
|
|
577
|
+
change those transport/page-size options while resuming the same position.
|
|
578
|
+
|
|
579
|
+
The cursor must work through a newly constructed compatible driver targeting
|
|
580
|
+
the same backend namespace while the provider token remains valid and
|
|
581
|
+
available; it cannot depend on process-, client-, or session-local state. An
|
|
582
|
+
adapter for a consuming or instance-bound provider token must materialize a
|
|
583
|
+
stable continuation before it can provide conforming paginated
|
|
584
|
+
`StorageDriver.list` results. This contract lets a caller safely retry, replay,
|
|
585
|
+
or resume pagination on another replica. It does not promise a provider-token
|
|
586
|
+
lifetime, snapshot isolation across concurrent mutations, or provider,
|
|
587
|
+
network, credential, or authorization availability. Provider invalidation is
|
|
588
|
+
an ordinary list-operation failure.
|
|
589
|
+
|
|
486
590
|
Downloads are streaming by default:
|
|
487
591
|
|
|
488
592
|
```ts
|
|
@@ -505,11 +609,54 @@ Node `Readable` uploads are accepted and converted to Web streams without
|
|
|
505
609
|
buffering. Provider capability gaps fail closed with `StorageError` rather than
|
|
506
610
|
silently discarding a range, metadata, or cache-control request.
|
|
507
611
|
|
|
508
|
-
###
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
612
|
+
### Exact provider conditions and staged-object promotion
|
|
613
|
+
|
|
614
|
+
Capabilities distinguish conditional create, replace, delete, read, source
|
|
615
|
+
copy, destination copy, atomic source-and-destination promotion, and multipart
|
|
616
|
+
completion. They also declare the complete physical-key byte budget. Callers
|
|
617
|
+
must check the exact primitive they need; a missing field is unsupported and is
|
|
618
|
+
never widened from another operation.
|
|
619
|
+
|
|
620
|
+
The physical-key budget applies to the exact key sent to the adapter. It
|
|
621
|
+
therefore counts leading slashes for unprefixed drivers, the separator added to
|
|
622
|
+
a configured driver prefix, and provider prefixes derived for `list` or
|
|
623
|
+
`search`. Over-budget object keys and explicit list/search prefixes fail before
|
|
624
|
+
provider dispatch rather than being normalized into a shorter key. A glob's
|
|
625
|
+
provider prefix is the exact prefix inferred by files-sdk itself, and that
|
|
626
|
+
derived list operation passes through the same final guard. A non-positive
|
|
627
|
+
`maxResults` performs no provider walk and therefore dispatches no prefix.
|
|
628
|
+
|
|
629
|
+
Adapters and plugins execute as trusted in-process code; this package does not
|
|
630
|
+
attempt to sandbox a plugin that performs its own network or filesystem I/O.
|
|
631
|
+
For supported plugin pipelines that forward operations through `next`, the
|
|
632
|
+
physical-key guard is the innermost wrapper, after caller plugins and before
|
|
633
|
+
the adapter call. A plugin therefore cannot widen an upload key or list/search
|
|
634
|
+
prefix past the declared byte ceiling while still using the normal dispatch
|
|
635
|
+
pipeline.
|
|
636
|
+
|
|
637
|
+
Storage-facing ETags have one canonical representation: a bare, case-sensitive
|
|
638
|
+
opaque token with no HTTP quotes. Canonical values contain 1–1024 visible
|
|
639
|
+
ASCII bytes and exclude commas, backslashes, whitespace, control characters,
|
|
640
|
+
`DEL`, non-ASCII text, the `*` wildcard, and case-insensitive `W/` weak-tag
|
|
641
|
+
prefixes. Treat the value as opaque: preserve the exact string returned by
|
|
642
|
+
`head`, reads, or writes and pass it back unchanged to a conditional operation.
|
|
643
|
+
Do not add or remove quotes in application code. S3-compatible drivers remove
|
|
644
|
+
exactly one valid provider-owned quote pair on ingress and add exactly one pair
|
|
645
|
+
when serializing the HTTP header.
|
|
646
|
+
|
|
647
|
+
This tightens the precondition boundary. Quoted or otherwise non-canonical
|
|
648
|
+
ETags that older versions happened to accept now fail with `INVALID_ARGUMENT`,
|
|
649
|
+
and an unsafe provider result fails with a sanitized `PROVIDER` error instead
|
|
650
|
+
of being exposed as a usable validator. Applications that persisted quoted
|
|
651
|
+
ETags must refresh them with `head` rather than trimming them heuristically.
|
|
652
|
+
Strict normalization prevents a caller-controlled wildcard, entity-tag list,
|
|
653
|
+
weak validator, or malformed header value from widening an operation that
|
|
654
|
+
promises one exact strong match.
|
|
655
|
+
|
|
656
|
+
The native AWS S3 profile advertises ETag- and version-conditioned server-side
|
|
657
|
+
copy. This lets an application validate a staged object and copy that exact
|
|
658
|
+
source to its final key instead of re-reading whichever bytes occupy the
|
|
659
|
+
staging key later:
|
|
513
660
|
|
|
514
661
|
```ts
|
|
515
662
|
import { StorageError, StorageErrorCode } from '@nestm/storage';
|
|
@@ -532,9 +679,58 @@ await media.delete(stagingKey);
|
|
|
532
679
|
```
|
|
533
680
|
|
|
534
681
|
`sourceVersion` can select an immutable S3 version and may be combined with
|
|
535
|
-
`sourceEtag`. A
|
|
536
|
-
|
|
537
|
-
|
|
682
|
+
`sourceEtag`. A destination condition can independently require create-only or
|
|
683
|
+
replacement of an exact ETag. Combining source and destination predicates also
|
|
684
|
+
requires `capabilities.conditionalCopyDestination.atomicWithSource`; otherwise
|
|
685
|
+
the request fails with `NOT_SUPPORTED`. A promotion must contain at least one
|
|
686
|
+
source or destination predicate.
|
|
687
|
+
|
|
688
|
+
Cloudflare R2 has a separate stable profile: create, replace, ETag-conditioned
|
|
689
|
+
read, and ETag-conditioned source copy are enabled, while conditional delete,
|
|
690
|
+
destination copy, atomic promotion, version predicates, and conditional
|
|
691
|
+
multipart completion remain absent. R2 proves content-type binding for
|
|
692
|
+
presigned PUT requests but not POST-form size ranges, so its signed-upload
|
|
693
|
+
policy is `{ contentType: true, sizeRange: false }` and the gateway refuses to
|
|
694
|
+
mint its POST upload form. Cloudflare documents that presigned `POST` form
|
|
695
|
+
uploads are not supported in its
|
|
696
|
+
[presigned URL contract](https://developers.cloudflare.com/r2/api/s3/presigned-urls/).
|
|
697
|
+
Direct R2 signed-upload calls that request a size bound likewise fail with
|
|
698
|
+
`NOT_SUPPORTED` before signing.
|
|
699
|
+
Custom S3-compatible endpoints start with no conditional operations and the
|
|
700
|
+
entire driver is forced read-only until an explicit conformance-verified
|
|
701
|
+
`S3ProviderProfile` is supplied. Omitting `signedUploadPolicy` while defining a
|
|
702
|
+
custom profile normalizes both policy claims to `false`; providers may opt in
|
|
703
|
+
only to constraints their conformance evidence proves.
|
|
704
|
+
|
|
705
|
+
Successful S3 signed uploads enforce every requested constraint. A request
|
|
706
|
+
with `contentType` and no `maxSize` uses a presigned PUT whose signature
|
|
707
|
+
includes the `content-type` header. A request with `maxSize` uses a POST policy
|
|
708
|
+
with `content-length-range` and, when present, an exact `Content-Type`
|
|
709
|
+
condition. S3 cannot express a lower-only `minSize` through this contract, so
|
|
710
|
+
that shape fails with `NOT_SUPPORTED`; an unclaimed profile constraint also
|
|
711
|
+
fails before credentials are resolved or a URL is minted. Literal physical
|
|
712
|
+
keys ending in AWS's `${filename}` POST template are rejected for bounded
|
|
713
|
+
uploads because the SDK otherwise widens the exact key condition to a prefix.
|
|
714
|
+
|
|
715
|
+
`withS3Capabilities()` decorates a raw S3 adapter in place and may be applied
|
|
716
|
+
only once. Construct a fresh raw adapter when selecting a different profile;
|
|
717
|
+
reapplying the helper is rejected so a previous broader profile cannot survive
|
|
718
|
+
a later narrower declaration. The selected profile is bound to the exact
|
|
719
|
+
reserved capability and operation members installed by that decoration;
|
|
720
|
+
same-client aliases may change display metadata but cannot add or replace those
|
|
721
|
+
members to widen the profile.
|
|
722
|
+
|
|
723
|
+
An explicit profile applied to a native AWS SDK endpoint may only narrow the
|
|
724
|
+
immutable `AWS_S3_PROVIDER_PROFILE`. It cannot raise the complete-key budget
|
|
725
|
+
above 1,024 bytes or claim an operation/policy bit absent from the built-in
|
|
726
|
+
profile. This containment follows actual SDK endpoint provenance even when an
|
|
727
|
+
adapter alias changes its display name.
|
|
728
|
+
|
|
729
|
+
The package-owned `s3()` factory also retains whether `publicBaseUrl` was
|
|
730
|
+
configured even when the second `withS3Capabilities()` options object is
|
|
731
|
+
omitted. In that case `signedDownloadPolicy.expiresIn` is false. Foreign S3
|
|
732
|
+
adapters whose construction metadata is unavailable receive the same
|
|
733
|
+
conservative false value instead of claiming an enforceable TTL.
|
|
538
734
|
|
|
539
735
|
### Resumable uploads
|
|
540
736
|
|
|
@@ -676,8 +872,11 @@ by `maxSignedUploadBytes`, and require an exact lowercase MIME type from
|
|
|
676
872
|
`attachment` or `inline` response disposition; arbitrary response-header text
|
|
677
873
|
and filenames are rejected. A driver must also advertise
|
|
678
874
|
`signedUploadPolicy.contentType` and `signedUploadPolicy.sizeRange`; otherwise
|
|
679
|
-
the gateway refuses to mint the URL.
|
|
680
|
-
|
|
875
|
+
the gateway refuses to mint the URL. Native AWS advertises both and uses S3
|
|
876
|
+
POST policy conditions. R2 advertises content-type enforcement but not a POST
|
|
877
|
+
size range, while omitted custom declarations normalize both claims to false;
|
|
878
|
+
the gateway therefore fails closed for those profiles. Signed downloads
|
|
879
|
+
similarly require
|
|
681
880
|
`signedDownloadPolicy.expiresIn`. The S3 factory advertises it only when no
|
|
682
881
|
permanent `publicBaseUrl` was configured, preventing a configured TTL from
|
|
683
882
|
silently returning a non-expiring public link.
|
package/SECURITY.md
CHANGED
|
@@ -56,11 +56,40 @@ untrusted agent code. A tenant id, run id, prefix, provider cursor, snapshot id,
|
|
|
56
56
|
or fork id supplied by a model is not a safe mount coordinate.
|
|
57
57
|
|
|
58
58
|
The workspace accepts only canonical mount-relative POSIX paths and rechecks
|
|
59
|
-
every key returned by a driver before unscoping it.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
every key returned by a driver before unscoping it. Pagination cursors are bound
|
|
60
|
+
to the store, normalized physical prefix, stable mount identity, trusted
|
|
61
|
+
tenant/workspace scope, operation, complete effective limits, normalized query,
|
|
62
|
+
and expiry. Provider continuations and prefixes stay inside the encrypted or
|
|
63
|
+
server-side payload. Both layers are non-consuming: a durable opaque token
|
|
64
|
+
store must read rather than consume a record, and an embedded provider cursor
|
|
65
|
+
must remain replayable through a fresh compatible driver against the same
|
|
66
|
+
backend namespace while that provider cursor remains valid and available.
|
|
67
|
+
Provider cursors cannot depend on process-local state; an adapter for a
|
|
68
|
+
consuming or instance-bound backend token must materialize a stable continuation
|
|
69
|
+
before exposing paginated `StorageDriver.list` results.
|
|
70
|
+
|
|
71
|
+
Under unchanged provider-visible state, cursors are reusable while their
|
|
72
|
+
provider continuation remains valid and available. The authenticated expiry is
|
|
73
|
+
an authorization ceiling, not a guarantee of provider-token lifetime, snapshot
|
|
74
|
+
isolation, provider/network availability, or valid credentials. Provider
|
|
75
|
+
invalidation remains an operational failure, and concurrent mutations remain
|
|
76
|
+
subject to provider ordering, duplicate, and omission semantics.
|
|
77
|
+
|
|
78
|
+
Production pagination must configure either the built-in AES-256-GCM codec with
|
|
79
|
+
a dedicated shared 32-byte key ring or an authenticated shared durable token
|
|
80
|
+
store. Do not reuse an authentication, session, storage-provider, or encryption
|
|
81
|
+
key from another purpose. All replicas must use the same stable identities and
|
|
82
|
+
key ring, compatible driver configuration, and logical backend namespace. New
|
|
83
|
+
cursors use the active key id; retain prior keys for at least the maximum cursor
|
|
84
|
+
TTL during rotation. Dropping a key, changing a binding field, or restarting
|
|
85
|
+
with a different ephemeral key intentionally invalidates outstanding cursors.
|
|
86
|
+
Tokens and decoded payloads are bounded, and malformed, altered, expired,
|
|
87
|
+
cross-query, cross-operation, cross-workspace, and cross-store cursors fail
|
|
88
|
+
closed.
|
|
89
|
+
|
|
90
|
+
Permissions, byte limits, result limits, and conditional mutation preconditions
|
|
91
|
+
are enforced inside the capability; tool omission and user approval are
|
|
92
|
+
additional workflow controls, not the authorization boundary.
|
|
64
93
|
|
|
65
94
|
Conditional mutations can still have an ambiguous outcome when a remote
|
|
66
95
|
provider commits and then loses or violates its response, or when a configured
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ToolSet } from 'ai';
|
|
1
|
+
import { type JSONValue, type ToolSet } from 'ai';
|
|
2
2
|
import { type StorageWorkspace } from '../workspace/index.js';
|
|
3
3
|
import { type StorageErrorCode as StorageErrorCodeValue } from '../storage.error.js';
|
|
4
4
|
export declare const AI_SDK_WORKSPACE_TOOL_NAMES: readonly ['workspace_list', 'workspace_stat', 'workspace_read_file', 'workspace_search', 'workspace_write_file', 'workspace_copy_file', 'workspace_move_file', 'workspace_delete_file'];
|
|
@@ -9,7 +9,12 @@ export type AiSdkWorkspaceMutationToolName = Extract<AiSdkWorkspaceToolName, 'wo
|
|
|
9
9
|
* approval, matching an omitted policy.
|
|
10
10
|
*/
|
|
11
11
|
export type AiSdkWorkspaceApprovalConfig = boolean | Partial<Record<AiSdkWorkspaceMutationToolName, boolean>>;
|
|
12
|
-
export interface
|
|
12
|
+
export interface AiSdkWorkspaceCreateConflict {
|
|
13
|
+
/** The logical destination inside the mounted workspace. */
|
|
14
|
+
readonly path: string;
|
|
15
|
+
}
|
|
16
|
+
export type AiSdkWorkspaceCreateConflictMapper<Result extends JSONValue> = (conflict: AiSdkWorkspaceCreateConflict) => PromiseLike<Result> | Result;
|
|
17
|
+
export interface CreateAiSdkWorkspaceToolsOptions<CreateConflictResult extends JSONValue = never> {
|
|
13
18
|
/** The already-mounted, policy-enforcing workspace exposed to the tools. */
|
|
14
19
|
workspace: StorageWorkspace;
|
|
15
20
|
/**
|
|
@@ -19,6 +24,12 @@ export interface CreateAiSdkWorkspaceToolsOptions {
|
|
|
19
24
|
maxReadBytes?: number;
|
|
20
25
|
/** Mutation tools require approval by default. */
|
|
21
26
|
requireApproval?: AiSdkWorkspaceApprovalConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Maps an atomic create collision to an application result. When omitted,
|
|
29
|
+
* the collision remains an AiSdkWorkspaceToolError like every other storage
|
|
30
|
+
* failure. Replace conflicts are never mapped by this hook.
|
|
31
|
+
*/
|
|
32
|
+
mapCreateConflict?: AiSdkWorkspaceCreateConflictMapper<CreateConflictResult>;
|
|
22
33
|
}
|
|
23
34
|
export type AiSdkWorkspaceToolErrorCode = StorageErrorCodeValue;
|
|
24
35
|
/**
|
|
@@ -58,6 +69,6 @@ export interface AiSdkWorkspacePageResult {
|
|
|
58
69
|
* The workspace remains the enforcing boundary if a retained tool reference
|
|
59
70
|
* is invoked after further narrowing.
|
|
60
71
|
*/
|
|
61
|
-
export declare function createAiSdkWorkspaceTools({ workspace, maxReadBytes: requestedMaxReadBytes, requireApproval, }: CreateAiSdkWorkspaceToolsOptions): ToolSet;
|
|
72
|
+
export declare function createAiSdkWorkspaceTools<CreateConflictResult extends JSONValue = never>({ workspace, maxReadBytes: requestedMaxReadBytes, requireApproval, mapCreateConflict, }: CreateAiSdkWorkspaceToolsOptions<CreateConflictResult>): ToolSet;
|
|
62
73
|
export declare function isAiSdkWorkspaceMutationToolName(value: string): value is AiSdkWorkspaceMutationToolName;
|
|
63
74
|
//# sourceMappingURL=ai-sdk-workspace-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-sdk-workspace-tools.d.ts","sourceRoot":"","sources":["../../src/ai-sdk/ai-sdk-workspace-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-sdk-workspace-tools.d.ts","sourceRoot":"","sources":["../../src/ai-sdk/ai-sdk-workspace-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,IAAI,CAAC;AAGxD,OAAO,EAGL,KAAK,gBAAgB,EAItB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,KAAK,gBAAgB,IAAI,qBAAqB,EAC/C,MAAM,qBAAqB,CAAC;AAG7B,eAAO,MAAM,2BAA2B,YACtC,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,CACf,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAChC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/C,MAAM,MAAM,8BAA8B,GAAG,OAAO,CAClD,sBAAsB,EACpB,sBAAsB,GACtB,qBAAqB,GACrB,qBAAqB,GACrB,uBAAuB,CAC1B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GACtC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,8BAA8B,EAAE,OAAO,CAAC,CAAC,CAAC;AAErE,MAAM,WAAW,4BAA4B;IAC3C,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,kCAAkC,CAAC,MAAM,SAAS,SAAS,IAAI,CACzE,QAAQ,EAAE,4BAA4B,KACnC,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AAElC,MAAM,WAAW,gCAAgC,CAC/C,oBAAoB,SAAS,SAAS,GAAG,KAAK;IAE9C,4EAA4E;IAC5E,SAAS,EAAE,gBAAgB,CAAC;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,kCAAkC,CAAC,oBAAoB,CAAC,CAAC;CAC9E;AAED,MAAM,MAAM,2BAA2B,GAAG,qBAAqB,CAAC;AAsBhE;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAE3C,YAAY,IAAI,EAAE,2BAA2B,EAI5C;CACF;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,yBAAyB,GACnC,wBAAwB,GAAG,6BAA6B,CAAC;AAE3D,MAAM,WAAW,4BAA6B,SAAQ,wBAAwB;IAC5E,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,yBAAyB,EAAE,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA2OD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,oBAAoB,SAAS,SAAS,GAAG,KAAK,EAC9C,EACA,SAAS,EACT,YAAY,EAAE,qBAAqB,EACnC,eAAsB,EACtB,iBAAiB,GAClB,EAAE,gCAAgC,CAAC,oBAAoB,CAAC,GAAG,OAAO,CA6SlE;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,MAAM,GACZ,KAAK,IAAI,8BAA8B,CAEzC"}
|