@nestm/storage 0.1.0-alpha.5 → 0.1.0-alpha.7
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 +26 -0
- package/README.md +158 -92
- package/SECURITY.md +40 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts +63 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts.map +1 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.js +383 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.js.map +1 -0
- package/dist/ai-sdk/index.d.ts +2 -0
- package/dist/ai-sdk/index.d.ts.map +1 -0
- package/dist/ai-sdk/index.js +2 -0
- package/dist/ai-sdk/index.js.map +1 -0
- package/dist/files-sdk/files-sdk.driver.d.ts +16 -2
- package/dist/files-sdk/files-sdk.driver.d.ts.map +1 -1
- package/dist/files-sdk/files-sdk.driver.js +159 -0
- package/dist/files-sdk/files-sdk.driver.js.map +1 -1
- package/dist/files-sdk/fs/index.d.ts +21 -2
- package/dist/files-sdk/fs/index.d.ts.map +1 -1
- package/dist/files-sdk/fs/index.js +600 -1
- 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.map +1 -1
- package/dist/files-sdk/provider/index.js +7 -0
- package/dist/files-sdk/provider/index.js.map +1 -1
- package/dist/files-sdk/s3/index.d.ts +12 -3
- package/dist/files-sdk/s3/index.d.ts.map +1 -1
- package/dist/files-sdk/s3/index.js +203 -37
- package/dist/files-sdk/s3/index.js.map +1 -1
- package/dist/storage.client.d.ts +5 -1
- package/dist/storage.client.d.ts.map +1 -1
- package/dist/storage.client.js +49 -0
- package/dist/storage.client.js.map +1 -1
- package/dist/storage.driver.d.ts +3 -1
- package/dist/storage.driver.d.ts.map +1 -1
- package/dist/storage.driver.js.map +1 -1
- package/dist/storage.service.d.ts.map +1 -1
- package/dist/storage.service.js +28 -3
- package/dist/storage.service.js.map +1 -1
- package/dist/storage.types.d.ts +36 -0
- package/dist/storage.types.d.ts.map +1 -1
- package/dist/storage.types.js.map +1 -1
- package/dist/workspace/index.d.ts +4 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +4 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/storage-workspace.cursor.d.ts +6 -0
- package/dist/workspace/storage-workspace.cursor.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.cursor.js +45 -0
- package/dist/workspace/storage-workspace.cursor.js.map +1 -0
- package/dist/workspace/storage-workspace.d.ts +6 -0
- package/dist/workspace/storage-workspace.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.error.d.ts +28 -0
- package/dist/workspace/storage-workspace.error.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.error.js +67 -0
- package/dist/workspace/storage-workspace.error.js.map +1 -0
- package/dist/workspace/storage-workspace.js +747 -0
- package/dist/workspace/storage-workspace.js.map +1 -0
- package/dist/workspace/storage-workspace.path.d.ts +9 -0
- package/dist/workspace/storage-workspace.path.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.path.js +68 -0
- package/dist/workspace/storage-workspace.path.js.map +1 -0
- package/dist/workspace/storage-workspace.types.d.ts +97 -0
- package/dist/workspace/storage-workspace.types.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.types.js +20 -0
- package/dist/workspace/storage-workspace.types.js.map +1 -0
- package/package.json +23 -20
- package/dist/artifacts/artifact-storage.d.ts +0 -161
- package/dist/artifacts/artifact-storage.d.ts.map +0 -1
- package/dist/artifacts/artifact-storage.js +0 -452
- package/dist/artifacts/artifact-storage.js.map +0 -1
- package/dist/artifacts/crypto/codec.d.ts +0 -44
- package/dist/artifacts/crypto/codec.d.ts.map +0 -1
- package/dist/artifacts/crypto/codec.js +0 -273
- package/dist/artifacts/crypto/codec.js.map +0 -1
- package/dist/artifacts/crypto/context.d.ts +0 -48
- package/dist/artifacts/crypto/context.d.ts.map +0 -1
- package/dist/artifacts/crypto/context.js +0 -37
- package/dist/artifacts/crypto/context.js.map +0 -1
- package/dist/artifacts/crypto/dek-cache.d.ts +0 -38
- package/dist/artifacts/crypto/dek-cache.d.ts.map +0 -1
- package/dist/artifacts/crypto/dek-cache.js +0 -137
- package/dist/artifacts/crypto/dek-cache.js.map +0 -1
- package/dist/artifacts/crypto/index.d.ts +0 -25
- package/dist/artifacts/crypto/index.d.ts.map +0 -1
- package/dist/artifacts/crypto/index.js +0 -29
- package/dist/artifacts/crypto/index.js.map +0 -1
- package/dist/artifacts/crypto/key-provider.d.ts +0 -44
- package/dist/artifacts/crypto/key-provider.d.ts.map +0 -1
- package/dist/artifacts/crypto/key-provider.js +0 -36
- package/dist/artifacts/crypto/key-provider.js.map +0 -1
- package/dist/artifacts/crypto/kms-provider.d.ts +0 -16
- package/dist/artifacts/crypto/kms-provider.d.ts.map +0 -1
- package/dist/artifacts/crypto/kms-provider.js +0 -90
- package/dist/artifacts/crypto/kms-provider.js.map +0 -1
- package/dist/artifacts/crypto/local-provider.d.ts +0 -17
- package/dist/artifacts/crypto/local-provider.d.ts.map +0 -1
- package/dist/artifacts/crypto/local-provider.js +0 -58
- package/dist/artifacts/crypto/local-provider.js.map +0 -1
- package/dist/artifacts/env-config.d.ts +0 -15
- package/dist/artifacts/env-config.d.ts.map +0 -1
- package/dist/artifacts/env-config.js +0 -95
- package/dist/artifacts/env-config.js.map +0 -1
- package/dist/artifacts/index.d.ts +0 -6
- package/dist/artifacts/index.d.ts.map +0 -1
- package/dist/artifacts/index.js +0 -6
- package/dist/artifacts/index.js.map +0 -1
- package/dist/artifacts/nest/index.d.ts +0 -29
- package/dist/artifacts/nest/index.d.ts.map +0 -1
- package/dist/artifacts/nest/index.js +0 -129
- package/dist/artifacts/nest/index.js.map +0 -1
- package/dist/artifacts/object-store.d.ts +0 -27
- package/dist/artifacts/object-store.d.ts.map +0 -1
- package/dist/artifacts/object-store.js +0 -198
- package/dist/artifacts/object-store.js.map +0 -1
- package/dist/artifacts/storage-driver.d.ts +0 -48
- package/dist/artifacts/storage-driver.d.ts.map +0 -1
- package/dist/artifacts/storage-driver.js +0 -89
- package/dist/artifacts/storage-driver.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @nestm/storage
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.7
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 842ff34: Add a backend-neutral `StorageWorkspace` capability and optional AI SDK 7 tool
|
|
8
|
+
adapter. Workspaces expose only canonical mount-relative paths, enforce
|
|
9
|
+
permissions and resource limits, hide provider coordinates and cursors, and use
|
|
10
|
+
atomic create/ETag mutation preconditions. S3 now advertises and implements the
|
|
11
|
+
conditional mutation primitives used by writable workspaces.
|
|
12
|
+
|
|
13
|
+
Harden local filesystem workspace reads and conditional mutations against
|
|
14
|
+
symlink aliases. Moves retain their create-only destination whenever source
|
|
15
|
+
deletion cannot be confirmed, avoiding data loss after provider or
|
|
16
|
+
post-operation hook ambiguity.
|
|
17
|
+
|
|
18
|
+
Fix cross-store sync so `destinationPrefix` is applied to uploaded keys as well
|
|
19
|
+
as pruning, keeping every mutation inside the selected destination scope.
|
|
20
|
+
|
|
21
|
+
## 0.1.0-alpha.6
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- 5bb646e: Remove the product-specific artifact protocol, encryption codec, and Nest composition entry
|
|
26
|
+
points. `@nestm/storage` remains a generic storage library; applications should compose domain
|
|
27
|
+
protocols over its clients and provider drivers in their own packages.
|
|
28
|
+
|
|
3
29
|
## 0.1.0-alpha.5
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @nestm/storage
|
|
2
2
|
|
|
3
3
|
Framework-neutral storage clients with NestJS 12 integration, named stores,
|
|
4
|
-
explicit streaming I/O,
|
|
5
|
-
gateway.
|
|
4
|
+
explicit streaming I/O, capability-scoped agent workspaces, cross-store
|
|
5
|
+
workflows, and an optional guarded HTTP gateway.
|
|
6
6
|
|
|
7
7
|
The package uses [`files-sdk`](https://github.com/haydenbleasel/files-sdk) as
|
|
8
8
|
its provider engine, but owns the API injected into Nest applications. Provider
|
|
@@ -43,9 +43,6 @@ Install only the native SDKs required by the chosen provider. For example:
|
|
|
43
43
|
pnpm add @aws-sdk/client-s3 @aws-sdk/s3-presigned-post \
|
|
44
44
|
@aws-sdk/s3-request-presigner @aws-sdk/lib-storage
|
|
45
45
|
|
|
46
|
-
# Encrypted artifact storage with AWS KMS
|
|
47
|
-
pnpm add @aws-sdk/client-kms
|
|
48
|
-
|
|
49
46
|
# Google Cloud Storage
|
|
50
47
|
pnpm add @google-cloud/storage google-auth-library
|
|
51
48
|
|
|
@@ -91,116 +88,185 @@ storage errors and operation types, and `StorageUploadControl`. It has no NestJS
|
|
|
91
88
|
runtime or declaration imports. Provider adapters remain available through
|
|
92
89
|
`@nestm/storage/files-sdk`.
|
|
93
90
|
|
|
94
|
-
##
|
|
91
|
+
## Mounted agent workspaces
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
content type so moving ciphertext to another address fails closed.
|
|
93
|
+
`@nestm/storage/workspace` turns a `StorageClient` into a narrow capability for
|
|
94
|
+
one logical directory. The mount is virtual: the same API works over S3, a
|
|
95
|
+
filesystem driver, or another storage backend without exposing the provider,
|
|
96
|
+
bucket, filesystem root, raw cursor, or internal prefix to its caller.
|
|
101
97
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
```mermaid
|
|
99
|
+
flowchart LR
|
|
100
|
+
A["Trusted application context"] -->|"store + opaque prefix + policy"| W["StorageWorkspace"]
|
|
101
|
+
W --> C["StorageClient"]
|
|
102
|
+
C --> D["S3 / filesystem / other driver"]
|
|
103
|
+
W --> T["AI SDK workspace tools"]
|
|
104
|
+
T --> G["ToolLoopAgent"]
|
|
105
|
+
```
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
writes.
|
|
107
|
+
Only trusted application code chooses the mount prefix. Every path accepted by
|
|
108
|
+
the workspace is a canonical, relative POSIX path. Absolute paths, backslashes,
|
|
109
|
+
control characters, repeated separators, and `.` or `..` segments are rejected
|
|
110
|
+
rather than normalized. Keys and provider cursors returned by a driver are also
|
|
111
|
+
checked before they are converted back to logical paths.
|
|
112
112
|
|
|
113
113
|
```ts
|
|
114
|
-
import {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
import { mountStorageWorkspace } from '@nestm/storage/workspace';
|
|
115
|
+
|
|
116
|
+
const workspace = mountStorageWorkspace(agentFiles, {
|
|
117
|
+
// Use an opaque server-derived run id, never a value selected by the model.
|
|
118
|
+
prefix: `workspaces/${runId}`,
|
|
119
|
+
permissions: [
|
|
120
|
+
'list',
|
|
121
|
+
'read',
|
|
122
|
+
'search',
|
|
123
|
+
'create',
|
|
124
|
+
'replace',
|
|
125
|
+
'copy',
|
|
126
|
+
'move',
|
|
127
|
+
'delete',
|
|
128
|
+
],
|
|
129
|
+
limits: {
|
|
130
|
+
maxReadBytes: 1024 * 1024,
|
|
131
|
+
maxWriteBytes: 1024 * 1024,
|
|
132
|
+
maxPageSize: 100,
|
|
133
|
+
maxSearchResults: 100,
|
|
134
|
+
maxSearchScan: 1000,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const created = await workspace.writeFile(
|
|
139
|
+
'src/main.ts',
|
|
140
|
+
'export const ready = true;\n',
|
|
141
|
+
{ mode: 'create', contentType: 'text/typescript' },
|
|
124
142
|
);
|
|
125
143
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
});
|
|
144
|
+
if (created.etag === undefined) {
|
|
145
|
+
throw new Error('This backend cannot safely replace the object.');
|
|
146
|
+
}
|
|
130
147
|
|
|
131
|
-
await
|
|
132
|
-
|
|
148
|
+
await workspace.writeFile('src/main.ts', 'export const ready = false;\n', {
|
|
149
|
+
mode: 'replace',
|
|
150
|
+
etag: created.etag,
|
|
151
|
+
contentType: 'text/typescript',
|
|
133
152
|
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Create, replace, and delete are conditional operations. A driver that cannot
|
|
156
|
+
enforce the requested not-exists or ETag precondition fails with
|
|
157
|
+
`NOT_SUPPORTED`; the workspace never substitutes an `exists()`/`head()` check
|
|
158
|
+
followed by an unconditional mutation. Reads enforce their byte ceiling while
|
|
159
|
+
consuming the stream, and list/search results are bounded. Search supports
|
|
160
|
+
exact, substring, and workspace-coordinate glob matching, but no caller-supplied
|
|
161
|
+
regular expressions.
|
|
162
|
+
|
|
163
|
+
Move is implemented as create-only copy followed by ETag-conditional source
|
|
164
|
+
delete. If source deletion cannot be confirmed, the destination is retained and
|
|
165
|
+
the call returns `CONFLICT`; inspect both logical paths before retrying. This
|
|
166
|
+
preserves at least one copy across provider timeouts and post-operation hook
|
|
167
|
+
failures, but does not pretend a multi-object move is transactionally atomic.
|
|
168
|
+
|
|
169
|
+
A child mount may further restrict a directory, permissions, or limits, but it
|
|
170
|
+
cannot widen any of them:
|
|
134
171
|
|
|
135
|
-
|
|
136
|
-
|
|
172
|
+
```ts
|
|
173
|
+
const readOnlySource = workspace.mount('src', {
|
|
174
|
+
permissions: ['list', 'read', 'search'],
|
|
175
|
+
limits: { maxReadBytes: 256 * 1024 },
|
|
137
176
|
});
|
|
138
177
|
```
|
|
139
178
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
preference. On case-insensitive filesystems, use case-stable artifact ids,
|
|
159
|
-
version ids, and object keys (Concepta's lowercase UUID ids satisfy this).
|
|
160
|
-
|
|
161
|
-
The CAE1 framing and its `{ scope, artifactId, version, path }` context are a
|
|
162
|
-
compatibility contract. Existing envelopes remain readable. Filesystems retain
|
|
163
|
-
the layouts `<root>/<artifactId>/<path>` and `<root>/_objects/<key>`; S3 and
|
|
164
|
-
other object-store providers retain their deployed unprefixed object keys, so a
|
|
165
|
-
rolling upgrade does not split old and new writers across keyspaces. Artifact
|
|
166
|
-
and version ids must each be one safe storage segment. Legacy plaintext reads
|
|
167
|
-
are available only through the explicit
|
|
168
|
-
`ARTIFACT_ENCRYPTION_READ_LEGACY=true` migration flag; writes are always
|
|
169
|
-
encrypted.
|
|
170
|
-
|
|
171
|
-
The convenience `createArtifactStorage()` and `createObjectStore()` factories
|
|
172
|
-
are ideal for scripts. Long-running framework-neutral processes that need an
|
|
173
|
-
explicit shutdown path should create the raw clients with
|
|
174
|
-
`createArtifactStorageClient()` / `createObjectStorageClient()`, compose them
|
|
175
|
-
with the corresponding `create*WithClient()` function, call each client's
|
|
176
|
-
`onApplicationShutdown()`, and finally call `crypto.keyProvider.clear()`. The
|
|
177
|
-
Nest module owns that lifecycle automatically.
|
|
178
|
-
|
|
179
|
-
Nest applications can register both adapters with one dynamic module:
|
|
179
|
+
### AI SDK and NestJS composition
|
|
180
|
+
|
|
181
|
+
Install AI SDK 7 and Zod only in applications that use the optional adapter:
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
pnpm add ai@^7 zod@^4
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`files-sdk` 2.2.x still declares an optional `ai@^6` peer for its own adapter,
|
|
188
|
+
so some package managers may print a peer warning when AI SDK 7 is installed.
|
|
189
|
+
This package does not import that adapter; `@nestm/storage/ai-sdk` targets AI
|
|
190
|
+
SDK 7 directly.
|
|
191
|
+
|
|
192
|
+
`@nestm/storage/ai-sdk` converts an already-mounted workspace to an ordinary
|
|
193
|
+
upstream `ToolSet`. It does not import NestJS or `@nestm/ai-sdk`; the application
|
|
194
|
+
composes the tool set through the AI module's existing named-toolset factory.
|
|
195
|
+
For a tenant or run selected per request, make both factories request-scoped and
|
|
196
|
+
derive the mount coordinate from authenticated host context:
|
|
180
197
|
|
|
181
198
|
```ts
|
|
182
|
-
import { Module } from '@nestjs/common';
|
|
183
|
-
import {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
} from '@nestm/storage/
|
|
187
|
-
import {
|
|
199
|
+
import { Module, Scope } from '@nestjs/common';
|
|
200
|
+
import { AiSdkModule, AiSdkService, getAiToolsetToken } from '@nestm/ai-sdk';
|
|
201
|
+
import { getStorageToken, type StorageClient } from '@nestm/storage';
|
|
202
|
+
import { createAiSdkWorkspaceTools } from '@nestm/storage/ai-sdk';
|
|
203
|
+
import { mountStorageWorkspace } from '@nestm/storage/workspace';
|
|
204
|
+
import type { ToolSet } from 'ai';
|
|
188
205
|
|
|
189
206
|
@Module({
|
|
190
207
|
imports: [
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
208
|
+
AppStorageModule,
|
|
209
|
+
WorkspaceContextModule,
|
|
210
|
+
AiSdkModule.forFeature({
|
|
211
|
+
imports: [AppStorageModule, WorkspaceContextModule],
|
|
212
|
+
toolsets: [
|
|
213
|
+
{
|
|
214
|
+
name: 'workspace',
|
|
215
|
+
scope: Scope.REQUEST,
|
|
216
|
+
inject: [getStorageToken('agent-files'), WorkspaceContext],
|
|
217
|
+
useFactory: (storage: StorageClient, context: WorkspaceContext) =>
|
|
218
|
+
createAiSdkWorkspaceTools({
|
|
219
|
+
workspace: mountStorageWorkspace(storage, {
|
|
220
|
+
// A validated, opaque coordinate from trusted auth/run state.
|
|
221
|
+
// It is never accepted from a prompt or tool input.
|
|
222
|
+
prefix: context.storagePrefix,
|
|
223
|
+
permissions: [
|
|
224
|
+
'list',
|
|
225
|
+
'read',
|
|
226
|
+
'search',
|
|
227
|
+
'create',
|
|
228
|
+
'replace',
|
|
229
|
+
'copy',
|
|
230
|
+
'move',
|
|
231
|
+
'delete',
|
|
232
|
+
],
|
|
233
|
+
}),
|
|
234
|
+
}),
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
agents: [
|
|
238
|
+
{
|
|
239
|
+
name: 'workspace-agent',
|
|
240
|
+
scope: Scope.REQUEST,
|
|
241
|
+
inject: [AiSdkService, getAiToolsetToken('workspace')],
|
|
242
|
+
useFactory: (ai: AiSdkService, tools: ToolSet) => ({
|
|
243
|
+
model: ai.languageModel(),
|
|
244
|
+
instructions:
|
|
245
|
+
'Use only the mounted workspace tools for file operations.',
|
|
246
|
+
tools,
|
|
247
|
+
}),
|
|
248
|
+
},
|
|
249
|
+
],
|
|
194
250
|
}),
|
|
195
251
|
],
|
|
196
252
|
})
|
|
197
|
-
export class
|
|
253
|
+
export class WorkspaceAgentModule {}
|
|
198
254
|
```
|
|
199
255
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
256
|
+
The generated set contains only tools allowed by the workspace permissions:
|
|
257
|
+
bounded list, stat, UTF-8 read, and search tools plus conditional create,
|
|
258
|
+
replace, copy, move, and delete tools when granted. Mutation tools require AI
|
|
259
|
+
SDK user approval by default; approval can be configured per tool, but the
|
|
260
|
+
workspace capability remains the authorization boundary even when approval is
|
|
261
|
+
disabled. The module's `AiSdkService.files()` API is the model provider's file
|
|
262
|
+
upload facility and is unrelated to storage workspaces.
|
|
263
|
+
|
|
264
|
+
This logical confinement is sufficient for a `ToolLoopAgent` whose only file
|
|
265
|
+
capabilities are these tools. It cannot constrain a coding harness that already
|
|
266
|
+
has shell, `node:fs`, or subprocess access. For Codex/Claude-style harnesses,
|
|
267
|
+
materialize the workspace into a per-session container or VM, mount only that
|
|
268
|
+
directory, run the harness there, and synchronize reviewed changes back through
|
|
269
|
+
`StorageWorkspace`. A working directory alone is not a sandbox.
|
|
204
270
|
|
|
205
271
|
## Configure named stores
|
|
206
272
|
|
package/SECURITY.md
CHANGED
|
@@ -46,3 +46,43 @@ the validated `head` or with an immutable provider version. Ordinary `copy`
|
|
|
46
46
|
does not protect against a staging-key replay between validation and copy.
|
|
47
47
|
Conditional promotion keeps the staging source; delete it only after the
|
|
48
48
|
application's metadata transaction commits.
|
|
49
|
+
|
|
50
|
+
## Workspace security boundary
|
|
51
|
+
|
|
52
|
+
`StorageWorkspace` is a narrowing capability for storage operations. Construct
|
|
53
|
+
it from trusted application context, keep the underlying `StorageClient` and
|
|
54
|
+
mount prefix private, and pass only the workspace or its AI tool set to
|
|
55
|
+
untrusted agent code. A tenant id, run id, prefix, provider cursor, snapshot id,
|
|
56
|
+
or fork id supplied by a model is not a safe mount coordinate.
|
|
57
|
+
|
|
58
|
+
The workspace accepts only canonical mount-relative POSIX paths and rechecks
|
|
59
|
+
every key returned by a driver before unscoping it. Its cursors are opaque and
|
|
60
|
+
bound to the mount and query. Permissions, byte limits, result limits, and
|
|
61
|
+
conditional mutation preconditions are enforced inside the capability; tool
|
|
62
|
+
omission and user approval are additional workflow controls, not the
|
|
63
|
+
authorization boundary.
|
|
64
|
+
|
|
65
|
+
Conditional mutations can still have an ambiguous outcome when a remote
|
|
66
|
+
provider commits and then loses or violates its response, or when a configured
|
|
67
|
+
post-operation plugin fails after the driver has committed. The API fails
|
|
68
|
+
closed in that case: inspect the logical destination and reconcile it before
|
|
69
|
+
retrying. Create-only and ETag preconditions prevent a blind retry from
|
|
70
|
+
silently overwriting a different object, but they cannot make a multi-object
|
|
71
|
+
move transactionally atomic.
|
|
72
|
+
|
|
73
|
+
This guarantee covers calls made through `StorageWorkspace`. It does not
|
|
74
|
+
confine arbitrary `node:fs`, shell, subprocess, or native-code access in the
|
|
75
|
+
same process. A coding harness with built-in shell or filesystem tools must run
|
|
76
|
+
inside an OS sandbox (container, VM, or equivalent) that exposes only a
|
|
77
|
+
materialized workspace. Setting `cwd` to a workspace directory is not
|
|
78
|
+
isolation.
|
|
79
|
+
|
|
80
|
+
For local filesystem storage, use a dedicated service-owned root and do not let
|
|
81
|
+
another untrusted process mutate its directory tree concurrently. High-level
|
|
82
|
+
Node filesystem checks reject symlinks and hard-linked object files in existing
|
|
83
|
+
workspace read and mutation paths, including metadata sidecars, but cannot
|
|
84
|
+
provide a race-proof boundary against an actor that can replace path components
|
|
85
|
+
between validation and use. For that threat model, mount only the workspace
|
|
86
|
+
into a separate UID/container/VM and synchronize approved results back through
|
|
87
|
+
storage. The local adapter also commits the body and metadata sidecar as two
|
|
88
|
+
files; a process crash between their atomic renames can require reconciliation.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ToolSet } from 'ai';
|
|
2
|
+
import { type StorageWorkspace } from '../workspace/index.js';
|
|
3
|
+
import { type StorageErrorCode as StorageErrorCodeValue } from '../storage.error.js';
|
|
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'];
|
|
5
|
+
export type AiSdkWorkspaceToolName = (typeof AI_SDK_WORKSPACE_TOOL_NAMES)[number];
|
|
6
|
+
export type AiSdkWorkspaceMutationToolName = Extract<AiSdkWorkspaceToolName, 'workspace_write_file' | 'workspace_copy_file' | 'workspace_move_file' | 'workspace_delete_file'>;
|
|
7
|
+
/**
|
|
8
|
+
* Approval policy for mutation tools. An omitted entry defaults to requiring
|
|
9
|
+
* approval, matching an omitted policy.
|
|
10
|
+
*/
|
|
11
|
+
export type AiSdkWorkspaceApprovalConfig = boolean | Partial<Record<AiSdkWorkspaceMutationToolName, boolean>>;
|
|
12
|
+
export interface CreateAiSdkWorkspaceToolsOptions {
|
|
13
|
+
/** The already-mounted, policy-enforcing workspace exposed to the tools. */
|
|
14
|
+
workspace: StorageWorkspace;
|
|
15
|
+
/**
|
|
16
|
+
* Optional tighter read ceiling. Values above the workspace ceiling are
|
|
17
|
+
* clamped; the model cannot choose or raise this value.
|
|
18
|
+
*/
|
|
19
|
+
maxReadBytes?: number;
|
|
20
|
+
/** Mutation tools require approval by default. */
|
|
21
|
+
requireApproval?: AiSdkWorkspaceApprovalConfig;
|
|
22
|
+
}
|
|
23
|
+
export type AiSdkWorkspaceToolErrorCode = StorageErrorCodeValue;
|
|
24
|
+
/**
|
|
25
|
+
* Safe error exposed at the AI tool boundary. It deliberately carries no
|
|
26
|
+
* provider error, storage key, mount prefix, store name, or cause.
|
|
27
|
+
*/
|
|
28
|
+
export declare class AiSdkWorkspaceToolError extends Error {
|
|
29
|
+
readonly code: AiSdkWorkspaceToolErrorCode;
|
|
30
|
+
constructor(code: AiSdkWorkspaceToolErrorCode);
|
|
31
|
+
}
|
|
32
|
+
export interface AiSdkWorkspaceFileResult {
|
|
33
|
+
kind: 'file';
|
|
34
|
+
path: string;
|
|
35
|
+
name: string;
|
|
36
|
+
size: number;
|
|
37
|
+
contentType: string;
|
|
38
|
+
etag?: string;
|
|
39
|
+
lastModified?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface AiSdkWorkspaceDirectoryResult {
|
|
42
|
+
kind: 'directory';
|
|
43
|
+
path: string;
|
|
44
|
+
name: string;
|
|
45
|
+
}
|
|
46
|
+
export type AiSdkWorkspaceEntryResult = AiSdkWorkspaceFileResult | AiSdkWorkspaceDirectoryResult;
|
|
47
|
+
export interface AiSdkWorkspaceTextFileResult extends AiSdkWorkspaceFileResult {
|
|
48
|
+
text: string;
|
|
49
|
+
}
|
|
50
|
+
export interface AiSdkWorkspacePageResult {
|
|
51
|
+
entries: AiSdkWorkspaceEntryResult[];
|
|
52
|
+
cursor?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Creates Vercel AI SDK tools backed only by a mounted StorageWorkspace.
|
|
56
|
+
*
|
|
57
|
+
* A tool is omitted unless the workspace grants its required permission.
|
|
58
|
+
* The workspace remains the enforcing boundary if a retained tool reference
|
|
59
|
+
* is invoked after further narrowing.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createAiSdkWorkspaceTools({ workspace, maxReadBytes: requestedMaxReadBytes, requireApproval, }: CreateAiSdkWorkspaceToolsOptions): ToolSet;
|
|
62
|
+
export declare function isAiSdkWorkspaceMutationToolName(value: string): value is AiSdkWorkspaceMutationToolName;
|
|
63
|
+
//# sourceMappingURL=ai-sdk-workspace-tools.d.ts.map
|
|
@@ -0,0 +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;AAGxC,OAAO,EAEL,KAAK,gBAAgB,EAItB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,KAAK,gBAAgB,IAAI,qBAAqB,EAC/C,MAAM,qBAAqB,CAAC;AAE7B,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,gCAAgC;IAC/C,4EAA4E;IAC5E,SAAS,EAAE,gBAAgB,CAAC;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,eAAe,CAAC,EAAE,4BAA4B,CAAC;CAChD;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;AA2MD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,SAAS,EACT,YAAY,EAAE,qBAAqB,EACnC,eAAsB,GACvB,EAAE,gCAAgC,GAAG,OAAO,CA2R5C;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,MAAM,GACZ,KAAK,IAAI,8BAA8B,CAEzC"}
|