@nestm/storage 0.1.0-alpha.6 → 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.
Files changed (87) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/README.md +398 -19
  3. package/SECURITY.md +69 -0
  4. package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts +74 -0
  5. package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts.map +1 -0
  6. package/dist/ai-sdk/ai-sdk-workspace-tools.js +414 -0
  7. package/dist/ai-sdk/ai-sdk-workspace-tools.js.map +1 -0
  8. package/dist/ai-sdk/index.d.ts +2 -0
  9. package/dist/ai-sdk/index.d.ts.map +1 -0
  10. package/dist/ai-sdk/index.js +2 -0
  11. package/dist/ai-sdk/index.js.map +1 -0
  12. package/dist/files-sdk/files-sdk.driver.d.ts +64 -6
  13. package/dist/files-sdk/files-sdk.driver.d.ts.map +1 -1
  14. package/dist/files-sdk/files-sdk.driver.js +918 -38
  15. package/dist/files-sdk/files-sdk.driver.js.map +1 -1
  16. package/dist/files-sdk/fs/index.d.ts +21 -2
  17. package/dist/files-sdk/fs/index.d.ts.map +1 -1
  18. package/dist/files-sdk/fs/index.js +833 -1
  19. package/dist/files-sdk/fs/index.js.map +1 -1
  20. package/dist/files-sdk/index.d.ts +1 -1
  21. package/dist/files-sdk/index.d.ts.map +1 -1
  22. package/dist/files-sdk/index.js.map +1 -1
  23. package/dist/files-sdk/provider/index.d.ts +9 -5
  24. package/dist/files-sdk/provider/index.d.ts.map +1 -1
  25. package/dist/files-sdk/provider/index.js +60 -15
  26. package/dist/files-sdk/provider/index.js.map +1 -1
  27. package/dist/files-sdk/s3/construction-metadata.d.ts +7 -0
  28. package/dist/files-sdk/s3/construction-metadata.d.ts.map +1 -0
  29. package/dist/files-sdk/s3/construction-metadata.js +13 -0
  30. package/dist/files-sdk/s3/construction-metadata.js.map +1 -0
  31. package/dist/files-sdk/s3/index.d.ts +43 -14
  32. package/dist/files-sdk/s3/index.d.ts.map +1 -1
  33. package/dist/files-sdk/s3/index.js +1046 -57
  34. package/dist/files-sdk/s3/index.js.map +1 -1
  35. package/dist/gateway/storage-gateway.controller.d.ts.map +1 -1
  36. package/dist/gateway/storage-gateway.controller.js +25 -5
  37. package/dist/gateway/storage-gateway.controller.js.map +1 -1
  38. package/dist/storage-etag.d.ts +13 -0
  39. package/dist/storage-etag.d.ts.map +1 -0
  40. package/dist/storage-etag.js +31 -0
  41. package/dist/storage-etag.js.map +1 -0
  42. package/dist/storage.client.d.ts +7 -1
  43. package/dist/storage.client.d.ts.map +1 -1
  44. package/dist/storage.client.js +124 -10
  45. package/dist/storage.client.js.map +1 -1
  46. package/dist/storage.driver.d.ts +23 -1
  47. package/dist/storage.driver.d.ts.map +1 -1
  48. package/dist/storage.driver.js.map +1 -1
  49. package/dist/storage.service.d.ts.map +1 -1
  50. package/dist/storage.service.js +28 -3
  51. package/dist/storage.service.js.map +1 -1
  52. package/dist/storage.types.d.ts +113 -6
  53. package/dist/storage.types.d.ts.map +1 -1
  54. package/dist/storage.types.js.map +1 -1
  55. package/dist/testing/index.d.ts +1 -0
  56. package/dist/testing/index.d.ts.map +1 -1
  57. package/dist/testing/index.js +1 -0
  58. package/dist/testing/index.js.map +1 -1
  59. package/dist/testing/provider-conformance.d.ts +65 -0
  60. package/dist/testing/provider-conformance.d.ts.map +1 -0
  61. package/dist/testing/provider-conformance.js +853 -0
  62. package/dist/testing/provider-conformance.js.map +1 -0
  63. package/dist/workspace/index.d.ts +5 -0
  64. package/dist/workspace/index.d.ts.map +1 -0
  65. package/dist/workspace/index.js +5 -0
  66. package/dist/workspace/index.js.map +1 -0
  67. package/dist/workspace/storage-workspace.cursor.d.ts +71 -0
  68. package/dist/workspace/storage-workspace.cursor.d.ts.map +1 -0
  69. package/dist/workspace/storage-workspace.cursor.js +408 -0
  70. package/dist/workspace/storage-workspace.cursor.js.map +1 -0
  71. package/dist/workspace/storage-workspace.d.ts +6 -0
  72. package/dist/workspace/storage-workspace.d.ts.map +1 -0
  73. package/dist/workspace/storage-workspace.error.d.ts +28 -0
  74. package/dist/workspace/storage-workspace.error.d.ts.map +1 -0
  75. package/dist/workspace/storage-workspace.error.js +67 -0
  76. package/dist/workspace/storage-workspace.error.js.map +1 -0
  77. package/dist/workspace/storage-workspace.js +755 -0
  78. package/dist/workspace/storage-workspace.js.map +1 -0
  79. package/dist/workspace/storage-workspace.path.d.ts +9 -0
  80. package/dist/workspace/storage-workspace.path.d.ts.map +1 -0
  81. package/dist/workspace/storage-workspace.path.js +68 -0
  82. package/dist/workspace/storage-workspace.path.js.map +1 -0
  83. package/dist/workspace/storage-workspace.types.d.ts +105 -0
  84. package/dist/workspace/storage-workspace.types.d.ts.map +1 -0
  85. package/dist/workspace/storage-workspace.types.js +21 -0
  86. package/dist/workspace/storage-workspace.types.js.map +1 -0
  87. package/package.json +24 -5
package/SECURITY.md CHANGED
@@ -46,3 +46,72 @@ 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. 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.
93
+
94
+ Conditional mutations can still have an ambiguous outcome when a remote
95
+ provider commits and then loses or violates its response, or when a configured
96
+ post-operation plugin fails after the driver has committed. The API fails
97
+ closed in that case: inspect the logical destination and reconcile it before
98
+ retrying. Create-only and ETag preconditions prevent a blind retry from
99
+ silently overwriting a different object, but they cannot make a multi-object
100
+ move transactionally atomic.
101
+
102
+ This guarantee covers calls made through `StorageWorkspace`. It does not
103
+ confine arbitrary `node:fs`, shell, subprocess, or native-code access in the
104
+ same process. A coding harness with built-in shell or filesystem tools must run
105
+ inside an OS sandbox (container, VM, or equivalent) that exposes only a
106
+ materialized workspace. Setting `cwd` to a workspace directory is not
107
+ isolation.
108
+
109
+ For local filesystem storage, use a dedicated service-owned root and do not let
110
+ another untrusted process mutate its directory tree concurrently. High-level
111
+ Node filesystem checks reject symlinks and hard-linked object files in existing
112
+ workspace read and mutation paths, including metadata sidecars, but cannot
113
+ provide a race-proof boundary against an actor that can replace path components
114
+ between validation and use. For that threat model, mount only the workspace
115
+ into a separate UID/container/VM and synchronize approved results back through
116
+ storage. The local adapter also commits the body and metadata sidecar as two
117
+ files; a process crash between their atomic renames can require reconciliation.
@@ -0,0 +1,74 @@
1
+ import { type JSONValue, 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 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> {
18
+ /** The already-mounted, policy-enforcing workspace exposed to the tools. */
19
+ workspace: StorageWorkspace;
20
+ /**
21
+ * Optional tighter read ceiling. Values above the workspace ceiling are
22
+ * clamped; the model cannot choose or raise this value.
23
+ */
24
+ maxReadBytes?: number;
25
+ /** Mutation tools require approval by default. */
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>;
33
+ }
34
+ export type AiSdkWorkspaceToolErrorCode = StorageErrorCodeValue;
35
+ /**
36
+ * Safe error exposed at the AI tool boundary. It deliberately carries no
37
+ * provider error, storage key, mount prefix, store name, or cause.
38
+ */
39
+ export declare class AiSdkWorkspaceToolError extends Error {
40
+ readonly code: AiSdkWorkspaceToolErrorCode;
41
+ constructor(code: AiSdkWorkspaceToolErrorCode);
42
+ }
43
+ export interface AiSdkWorkspaceFileResult {
44
+ kind: 'file';
45
+ path: string;
46
+ name: string;
47
+ size: number;
48
+ contentType: string;
49
+ etag?: string;
50
+ lastModified?: string;
51
+ }
52
+ export interface AiSdkWorkspaceDirectoryResult {
53
+ kind: 'directory';
54
+ path: string;
55
+ name: string;
56
+ }
57
+ export type AiSdkWorkspaceEntryResult = AiSdkWorkspaceFileResult | AiSdkWorkspaceDirectoryResult;
58
+ export interface AiSdkWorkspaceTextFileResult extends AiSdkWorkspaceFileResult {
59
+ text: string;
60
+ }
61
+ export interface AiSdkWorkspacePageResult {
62
+ entries: AiSdkWorkspaceEntryResult[];
63
+ cursor?: string;
64
+ }
65
+ /**
66
+ * Creates Vercel AI SDK tools backed only by a mounted StorageWorkspace.
67
+ *
68
+ * A tool is omitted unless the workspace grants its required permission.
69
+ * The workspace remains the enforcing boundary if a retained tool reference
70
+ * is invoked after further narrowing.
71
+ */
72
+ export declare function createAiSdkWorkspaceTools<CreateConflictResult extends JSONValue = never>({ workspace, maxReadBytes: requestedMaxReadBytes, requireApproval, mapCreateConflict, }: CreateAiSdkWorkspaceToolsOptions<CreateConflictResult>): ToolSet;
73
+ export declare function isAiSdkWorkspaceMutationToolName(value: string): value is AiSdkWorkspaceMutationToolName;
74
+ //# 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,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"}
@@ -0,0 +1,414 @@
1
+ import { tool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { STORAGE_WORKSPACE_MAX_CURSOR_BYTES, isStorageWorkspaceError, } from '../workspace/index.js';
4
+ import { StorageErrorCode, isStorageError, } from '../storage.error.js';
5
+ import { isCanonicalStorageEtag } from '../storage-etag.js';
6
+ export const AI_SDK_WORKSPACE_TOOL_NAMES = [
7
+ 'workspace_list',
8
+ 'workspace_stat',
9
+ 'workspace_read_file',
10
+ 'workspace_search',
11
+ 'workspace_write_file',
12
+ 'workspace_copy_file',
13
+ 'workspace_move_file',
14
+ 'workspace_delete_file',
15
+ ];
16
+ const SAFE_ERROR_MESSAGES = {
17
+ [StorageErrorCode.NOT_FOUND]: 'The requested workspace path was not found.',
18
+ [StorageErrorCode.UNAUTHORIZED]: 'This operation is not permitted in the workspace.',
19
+ [StorageErrorCode.CONFLICT]: 'The operation conflicts with current workspace state. Refresh metadata and retry with the current ETag or a new destination.',
20
+ [StorageErrorCode.READ_ONLY]: 'This operation is not permitted in the workspace.',
21
+ [StorageErrorCode.INVALID_ARGUMENT]: 'The workspace tool input was rejected.',
22
+ [StorageErrorCode.NOT_SUPPORTED]: 'This workspace operation is not supported by the configured storage.',
23
+ [StorageErrorCode.ABORTED]: 'The workspace operation was aborted.',
24
+ [StorageErrorCode.TIMEOUT]: 'The workspace operation timed out.',
25
+ [StorageErrorCode.LIMIT_EXCEEDED]: 'The workspace operation exceeded a configured limit.',
26
+ [StorageErrorCode.PROVIDER]: 'The workspace operation failed.',
27
+ };
28
+ /**
29
+ * Safe error exposed at the AI tool boundary. It deliberately carries no
30
+ * provider error, storage key, mount prefix, store name, or cause.
31
+ */
32
+ export class AiSdkWorkspaceToolError extends Error {
33
+ code;
34
+ constructor(code) {
35
+ super(SAFE_ERROR_MESSAGES[code]);
36
+ this.name = 'AiSdkWorkspaceToolError';
37
+ this.code = code;
38
+ }
39
+ }
40
+ const mutationToolNames = new Set([
41
+ 'workspace_write_file',
42
+ 'workspace_copy_file',
43
+ 'workspace_move_file',
44
+ 'workspace_delete_file',
45
+ ]);
46
+ const utf8Encoder = new TextEncoder();
47
+ const forbiddenUnicodeCharacter = /\p{C}/u;
48
+ const workspaceCursor = /^[A-Za-z0-9._-]+$/u;
49
+ const windowsDeviceName = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu;
50
+ function isLogicalPath(value) {
51
+ if (value.length === 0 ||
52
+ value.startsWith('/') ||
53
+ value.endsWith('/') ||
54
+ value !== value.normalize('NFC') ||
55
+ value.includes('\\') ||
56
+ forbiddenUnicodeCharacter.test(value)) {
57
+ return false;
58
+ }
59
+ return value
60
+ .split('/')
61
+ .every((segment) => segment.length > 0 &&
62
+ segment !== '.' &&
63
+ segment !== '..' &&
64
+ !segment.includes(':') &&
65
+ !segment.endsWith('.') &&
66
+ !segment.endsWith(' ') &&
67
+ !windowsDeviceName.test(segment));
68
+ }
69
+ function logicalPath(label, maxBytes) {
70
+ return z
71
+ .string()
72
+ .min(1)
73
+ .refine(isLogicalPath, {
74
+ message: `${label} must be a portable, NFC-normalized, relative POSIX workspace path.`,
75
+ })
76
+ .refine((value) => utf8Encoder.encode(value).byteLength <= maxBytes, {
77
+ message: `${label} exceeds the ${maxBytes}-byte workspace path limit.`,
78
+ })
79
+ .describe(`${label}. Use an NFC-normalized relative POSIX path inside the mounted workspace. Empty, dot, parent, colon, trailing dot or space, and Windows reserved device segments are forbidden.`);
80
+ }
81
+ function searchQuery(maxBytes) {
82
+ return z
83
+ .string()
84
+ .min(1)
85
+ .refine((value) => !value.includes('\\') && !forbiddenUnicodeCharacter.test(value), { message: 'Search query contains forbidden characters.' })
86
+ .refine((value) => utf8Encoder.encode(value).byteLength <= maxBytes, {
87
+ message: `Search query exceeds the ${maxBytes}-byte workspace limit.`,
88
+ })
89
+ .describe('Path pattern or text to find inside the mounted workspace.');
90
+ }
91
+ function boundedEtag() {
92
+ return z
93
+ .string()
94
+ .refine(isCanonicalStorageEtag, {
95
+ message: 'ETag must be a canonical bare value of at most 1,024 bytes.',
96
+ })
97
+ .describe('Exact canonical bare ETag returned by the latest workspace stat or read.');
98
+ }
99
+ function continuationCursor(description, maxBytes) {
100
+ return z
101
+ .string()
102
+ .min(1)
103
+ .max(Math.min(STORAGE_WORKSPACE_MAX_CURSOR_BYTES, maxBytes))
104
+ .regex(workspaceCursor, 'Cursor must be the bounded opaque token returned by the workspace.')
105
+ .optional()
106
+ .describe(description);
107
+ }
108
+ function positiveIntegerAtMost(maximum, description) {
109
+ return z
110
+ .number()
111
+ .int()
112
+ .positive()
113
+ .max(maximum)
114
+ .optional()
115
+ .describe(description);
116
+ }
117
+ function operationOptions(signal) {
118
+ return signal === undefined ? {} : { signal };
119
+ }
120
+ function resolveApproval(toolName, config) {
121
+ return typeof config === 'boolean' ? config : (config[toolName] ?? true);
122
+ }
123
+ function resolveReadLimit(workspace, requested) {
124
+ const workspaceLimit = workspace.limits.maxReadBytes;
125
+ if (!Number.isSafeInteger(workspaceLimit) || workspaceLimit <= 0) {
126
+ throw new RangeError('workspace.limits.maxReadBytes must be a positive safe integer.');
127
+ }
128
+ if (requested !== undefined &&
129
+ (!Number.isSafeInteger(requested) || requested <= 0)) {
130
+ throw new RangeError('maxReadBytes must be a positive safe integer.');
131
+ }
132
+ return Math.min(requested ?? workspaceLimit, workspaceLimit);
133
+ }
134
+ function sanitizeToolError(error, signal) {
135
+ if (error instanceof AiSdkWorkspaceToolError) {
136
+ return error;
137
+ }
138
+ if (signal?.aborted === true) {
139
+ return new AiSdkWorkspaceToolError(StorageErrorCode.ABORTED);
140
+ }
141
+ if (isStorageWorkspaceError(error)) {
142
+ return new AiSdkWorkspaceToolError(error.code);
143
+ }
144
+ if (isStorageError(error)) {
145
+ return new AiSdkWorkspaceToolError(error.code);
146
+ }
147
+ return new AiSdkWorkspaceToolError(StorageErrorCode.PROVIDER);
148
+ }
149
+ async function executeSafely(signal, operation) {
150
+ try {
151
+ return await operation();
152
+ }
153
+ catch (error) {
154
+ throw sanitizeToolError(error, signal);
155
+ }
156
+ }
157
+ async function executeCreateAware(signal, input, operation, mapCreateConflict) {
158
+ try {
159
+ return await operation();
160
+ }
161
+ catch (error) {
162
+ const safeError = sanitizeToolError(error, signal);
163
+ if (input.mode === 'create' &&
164
+ safeError.code === StorageErrorCode.CONFLICT &&
165
+ mapCreateConflict !== undefined) {
166
+ try {
167
+ return await mapCreateConflict({ path: input.path });
168
+ }
169
+ catch {
170
+ throw new AiSdkWorkspaceToolError(StorageErrorCode.PROVIDER);
171
+ }
172
+ }
173
+ throw safeError;
174
+ }
175
+ }
176
+ function serializeFile(file) {
177
+ if (file.etag !== undefined && !isCanonicalStorageEtag(file.etag)) {
178
+ throw new Error('Workspace returned a malformed ETag.');
179
+ }
180
+ return {
181
+ kind: 'file',
182
+ path: file.path,
183
+ name: file.name,
184
+ size: file.size,
185
+ contentType: file.contentType,
186
+ ...(file.etag === undefined ? {} : { etag: file.etag }),
187
+ ...(file.lastModified === undefined
188
+ ? {}
189
+ : { lastModified: file.lastModified.toISOString() }),
190
+ };
191
+ }
192
+ function serializeEntry(entry) {
193
+ return entry.kind === 'file'
194
+ ? serializeFile(entry)
195
+ : { kind: 'directory', path: entry.path, name: entry.name };
196
+ }
197
+ function serializeTextFile(file) {
198
+ return { ...serializeFile(file), text: file.text };
199
+ }
200
+ function serializePage(page) {
201
+ return {
202
+ entries: page.entries.map(serializeEntry),
203
+ ...(page.cursor === undefined ? {} : { cursor: page.cursor }),
204
+ };
205
+ }
206
+ /**
207
+ * Creates Vercel AI SDK tools backed only by a mounted StorageWorkspace.
208
+ *
209
+ * A tool is omitted unless the workspace grants its required permission.
210
+ * The workspace remains the enforcing boundary if a retained tool reference
211
+ * is invoked after further narrowing.
212
+ */
213
+ export function createAiSdkWorkspaceTools({ workspace, maxReadBytes: requestedMaxReadBytes, requireApproval = true, mapCreateConflict, }) {
214
+ const maxReadBytes = resolveReadLimit(workspace, requestedMaxReadBytes);
215
+ const tools = {};
216
+ const pathSchema = logicalPath('File path', workspace.limits.maxPathBytes);
217
+ const directorySchema = logicalPath('Directory path', workspace.limits.maxPathBytes);
218
+ const etagSchema = boundedEtag();
219
+ if (workspace.allows('list')) {
220
+ tools.workspace_list = tool({
221
+ description: 'List files and directories inside the mounted workspace. Omit directory to list the workspace root.',
222
+ // Optional pagination and directory inputs are not compatible with
223
+ // OpenAI strict function schemas, which require every property.
224
+ strict: false,
225
+ inputSchema: z
226
+ .object({
227
+ directory: directorySchema.optional(),
228
+ recursive: z
229
+ .boolean()
230
+ .optional()
231
+ .describe('Whether to include descendants recursively.'),
232
+ limit: positiveIntegerAtMost(workspace.limits.maxPageSize, `Maximum entries to return, up to ${workspace.limits.maxPageSize}.`),
233
+ cursor: continuationCursor('Opaque cursor returned by a preceding list call. It may be replayed before expiry while its provider continuation remains valid; repeat the same directory, recursive, and limit options when continuing.', workspace.limits.maxCursorBytes),
234
+ })
235
+ .strict(),
236
+ execute: (input, { abortSignal }) => executeSafely(abortSignal, async () => serializePage(await workspace.list({
237
+ ...(input.directory === undefined
238
+ ? {}
239
+ : { directory: input.directory }),
240
+ ...(input.recursive === undefined
241
+ ? {}
242
+ : { recursive: input.recursive }),
243
+ ...(input.limit === undefined ? {} : { limit: input.limit }),
244
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
245
+ ...operationOptions(abortSignal),
246
+ }))),
247
+ });
248
+ }
249
+ if (workspace.allows('read')) {
250
+ tools.workspace_stat = tool({
251
+ description: 'Inspect a file inside the mounted workspace without reading its contents. Returns the ETag required for safe replace, move, and delete operations.',
252
+ strict: true,
253
+ inputSchema: z.object({ path: pathSchema }).strict(),
254
+ execute: ({ path }, { abortSignal }) => executeSafely(abortSignal, async () => serializeFile(await workspace.stat(path, operationOptions(abortSignal)))),
255
+ });
256
+ tools.workspace_read_file = tool({
257
+ description: `Read a UTF-8 text file inside the mounted workspace. The result is bounded to ${maxReadBytes} bytes.`,
258
+ strict: true,
259
+ inputSchema: z.object({ path: pathSchema }).strict(),
260
+ execute: ({ path }, { abortSignal }) => executeSafely(abortSignal, async () => serializeTextFile(await workspace.readText(path, {
261
+ maxBytes: maxReadBytes,
262
+ ...operationOptions(abortSignal),
263
+ }))),
264
+ });
265
+ }
266
+ if (workspace.allows('search')) {
267
+ tools.workspace_search = tool({
268
+ description: 'Search logical paths inside the mounted workspace using a bounded glob, substring, or exact match. Omit directory to search from the workspace root.',
269
+ // Search intentionally has optional filters and cursor inputs.
270
+ strict: false,
271
+ inputSchema: z
272
+ .object({
273
+ query: searchQuery(workspace.limits.maxPathBytes),
274
+ directory: directorySchema.optional(),
275
+ match: z
276
+ .enum(['glob', 'substring', 'exact'])
277
+ .optional()
278
+ .describe('Matching strategy; defaults to glob.'),
279
+ caseInsensitive: z.boolean().optional(),
280
+ limit: positiveIntegerAtMost(workspace.limits.maxSearchResults, `Maximum matches to return, up to ${workspace.limits.maxSearchResults}.`),
281
+ cursor: continuationCursor('Opaque cursor returned by a preceding search call. It may be replayed before expiry while its provider continuation remains valid; repeat the same query, directory, match, caseInsensitive, and limit options when continuing.', workspace.limits.maxCursorBytes),
282
+ })
283
+ .strict(),
284
+ execute: ({ query, directory, match, caseInsensitive, limit, cursor }, { abortSignal }) => executeSafely(abortSignal, async () => serializePage(await workspace.search(query, {
285
+ ...(directory === undefined ? {} : { directory }),
286
+ ...(match === undefined ? {} : { match }),
287
+ ...(caseInsensitive === undefined ? {} : { caseInsensitive }),
288
+ ...(limit === undefined ? {} : { limit }),
289
+ ...(cursor === undefined ? {} : { cursor }),
290
+ ...operationOptions(abortSignal),
291
+ }))),
292
+ });
293
+ }
294
+ const canCreate = workspace.allows('create');
295
+ const canReplace = workspace.allows('replace');
296
+ if (canCreate || canReplace) {
297
+ const commonWriteShape = {
298
+ path: pathSchema,
299
+ content: z
300
+ .string()
301
+ .refine((value) => utf8Encoder.encode(value).byteLength <=
302
+ workspace.limits.maxWriteBytes, {
303
+ message: `Content exceeds the ${workspace.limits.maxWriteBytes}-byte workspace write limit.`,
304
+ })
305
+ .describe(`UTF-8 text to write. The workspace enforces its ${workspace.limits.maxWriteBytes}-byte write limit.`),
306
+ };
307
+ const createSchema = z
308
+ .object({ ...commonWriteShape, mode: z.literal('create') })
309
+ .strict();
310
+ const replaceSchema = z
311
+ .object({
312
+ ...commonWriteShape,
313
+ mode: z.literal('replace'),
314
+ etag: etagSchema,
315
+ })
316
+ .strict();
317
+ const inputSchema = canCreate && canReplace
318
+ ? z.discriminatedUnion('mode', [createSchema, replaceSchema])
319
+ : canCreate
320
+ ? createSchema
321
+ : replaceSchema;
322
+ tools.workspace_write_file = tool({
323
+ description: canCreate && canReplace
324
+ ? 'Create a new UTF-8 text file or replace an existing file inside the mounted workspace. Create fails if the destination exists; replace requires its current ETag.'
325
+ : canCreate
326
+ ? 'Create a new UTF-8 text file inside the mounted workspace. The operation fails if the destination already exists.'
327
+ : 'Replace an existing UTF-8 text file inside the mounted workspace using its current ETag.',
328
+ // The combined create/replace schema is a discriminated union. OpenAI
329
+ // strict function tools reject its root-level oneOf, while the runtime
330
+ // Zod schema continues to validate every tool call in non-strict mode.
331
+ strict: !(canCreate && canReplace),
332
+ inputSchema,
333
+ needsApproval: resolveApproval('workspace_write_file', requireApproval),
334
+ execute: (input, { abortSignal }) => executeCreateAware(abortSignal, input, async () => serializeFile(input.mode === 'create'
335
+ ? await workspace.writeFile(input.path, input.content, {
336
+ mode: 'create',
337
+ ...operationOptions(abortSignal),
338
+ })
339
+ : await workspace.writeFile(input.path, input.content, {
340
+ mode: 'replace',
341
+ etag: input.etag,
342
+ ...operationOptions(abortSignal),
343
+ })), mapCreateConflict),
344
+ });
345
+ }
346
+ const canCopy = workspace.allows('copy') &&
347
+ workspace.allows('read') &&
348
+ workspace.allows('create');
349
+ if (canCopy) {
350
+ tools.workspace_copy_file = tool({
351
+ description: 'Copy an exact observed version of a file inside the mounted workspace. The source remains intact, and the operation fails if the source changed or the destination already exists.',
352
+ strict: true,
353
+ inputSchema: z
354
+ .object({
355
+ source: logicalPath('Source file path', workspace.limits.maxPathBytes),
356
+ destination: logicalPath('Destination file path', workspace.limits.maxPathBytes),
357
+ etag: etagSchema.describe('Exact ETag of the source returned by the latest workspace stat or read.'),
358
+ })
359
+ .strict(),
360
+ needsApproval: resolveApproval('workspace_copy_file', requireApproval),
361
+ execute: ({ source, destination, etag }, { abortSignal }) => executeSafely(abortSignal, async () => serializeFile(await workspace.copyFile(source, destination, {
362
+ etag,
363
+ ...operationOptions(abortSignal),
364
+ }))),
365
+ });
366
+ }
367
+ const canMove = workspace.allows('move') &&
368
+ workspace.allows('read') &&
369
+ workspace.allows('create') &&
370
+ workspace.allows('delete');
371
+ if (canMove) {
372
+ tools.workspace_move_file = tool({
373
+ description: "Move a file inside the mounted workspace using the source's current ETag. The operation fails if the destination already exists. If source deletion cannot be confirmed, the destination is retained and the tool reports a conflict; inspect both paths before retrying.",
374
+ strict: true,
375
+ inputSchema: z
376
+ .object({
377
+ source: logicalPath('Source file path', workspace.limits.maxPathBytes),
378
+ destination: logicalPath('Destination file path', workspace.limits.maxPathBytes),
379
+ etag: etagSchema.describe('Exact ETag of the source returned by the latest workspace stat or read.'),
380
+ })
381
+ .strict(),
382
+ needsApproval: resolveApproval('workspace_move_file', requireApproval),
383
+ execute: ({ source, destination, etag }, { abortSignal }) => executeSafely(abortSignal, async () => serializeFile(await workspace.moveFile(source, destination, {
384
+ etag,
385
+ ...operationOptions(abortSignal),
386
+ }))),
387
+ });
388
+ }
389
+ if (workspace.allows('delete')) {
390
+ tools.workspace_delete_file = tool({
391
+ description: 'Delete a file inside the mounted workspace using its current ETag.',
392
+ strict: true,
393
+ inputSchema: z
394
+ .object({
395
+ path: pathSchema,
396
+ etag: etagSchema,
397
+ })
398
+ .strict(),
399
+ needsApproval: resolveApproval('workspace_delete_file', requireApproval),
400
+ execute: ({ path, etag }, { abortSignal }) => executeSafely(abortSignal, async () => {
401
+ await workspace.deleteFile(path, {
402
+ etag,
403
+ ...operationOptions(abortSignal),
404
+ });
405
+ return { deleted: true, path };
406
+ }),
407
+ });
408
+ }
409
+ return tools;
410
+ }
411
+ export function isAiSdkWorkspaceMutationToolName(value) {
412
+ return mutationToolNames.has(value);
413
+ }
414
+ //# sourceMappingURL=ai-sdk-workspace-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-sdk-workspace-tools.js","sourceRoot":"","sources":["../../src/ai-sdk/ai-sdk-workspace-tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { tool, type JSONValue, type ToolSet } from 'ai';\nimport { z } from 'zod';\n\nimport {\n STORAGE_WORKSPACE_MAX_CURSOR_BYTES,\n isStorageWorkspaceError,\n type StorageWorkspace,\n type StorageWorkspaceEntry,\n type StorageWorkspaceFile,\n type StorageWorkspaceTextFile,\n} from '../workspace/index.js';\nimport {\n StorageErrorCode,\n isStorageError,\n type StorageErrorCode as StorageErrorCodeValue,\n} from '../storage.error.js';\nimport { isCanonicalStorageEtag } from '../storage-etag.js';\n\nexport const AI_SDK_WORKSPACE_TOOL_NAMES = [\n 'workspace_list',\n 'workspace_stat',\n 'workspace_read_file',\n 'workspace_search',\n 'workspace_write_file',\n 'workspace_copy_file',\n 'workspace_move_file',\n 'workspace_delete_file',\n] as const;\n\nexport type AiSdkWorkspaceToolName =\n (typeof AI_SDK_WORKSPACE_TOOL_NAMES)[number];\n\nexport type AiSdkWorkspaceMutationToolName = Extract<\n AiSdkWorkspaceToolName,\n | 'workspace_write_file'\n | 'workspace_copy_file'\n | 'workspace_move_file'\n | 'workspace_delete_file'\n>;\n\n/**\n * Approval policy for mutation tools. An omitted entry defaults to requiring\n * approval, matching an omitted policy.\n */\nexport type AiSdkWorkspaceApprovalConfig =\n boolean | Partial<Record<AiSdkWorkspaceMutationToolName, boolean>>;\n\nexport interface AiSdkWorkspaceCreateConflict {\n /** The logical destination inside the mounted workspace. */\n readonly path: string;\n}\n\nexport type AiSdkWorkspaceCreateConflictMapper<Result extends JSONValue> = (\n conflict: AiSdkWorkspaceCreateConflict,\n) => PromiseLike<Result> | Result;\n\nexport interface CreateAiSdkWorkspaceToolsOptions<\n CreateConflictResult extends JSONValue = never,\n> {\n /** The already-mounted, policy-enforcing workspace exposed to the tools. */\n workspace: StorageWorkspace;\n /**\n * Optional tighter read ceiling. Values above the workspace ceiling are\n * clamped; the model cannot choose or raise this value.\n */\n maxReadBytes?: number;\n /** Mutation tools require approval by default. */\n requireApproval?: AiSdkWorkspaceApprovalConfig;\n /**\n * Maps an atomic create collision to an application result. When omitted,\n * the collision remains an AiSdkWorkspaceToolError like every other storage\n * failure. Replace conflicts are never mapped by this hook.\n */\n mapCreateConflict?: AiSdkWorkspaceCreateConflictMapper<CreateConflictResult>;\n}\n\nexport type AiSdkWorkspaceToolErrorCode = StorageErrorCodeValue;\n\nconst SAFE_ERROR_MESSAGES: Readonly<\n Record<AiSdkWorkspaceToolErrorCode, string>\n> = {\n [StorageErrorCode.NOT_FOUND]: 'The requested workspace path was not found.',\n [StorageErrorCode.UNAUTHORIZED]:\n 'This operation is not permitted in the workspace.',\n [StorageErrorCode.CONFLICT]:\n 'The operation conflicts with current workspace state. Refresh metadata and retry with the current ETag or a new destination.',\n [StorageErrorCode.READ_ONLY]:\n 'This operation is not permitted in the workspace.',\n [StorageErrorCode.INVALID_ARGUMENT]: 'The workspace tool input was rejected.',\n [StorageErrorCode.NOT_SUPPORTED]:\n 'This workspace operation is not supported by the configured storage.',\n [StorageErrorCode.ABORTED]: 'The workspace operation was aborted.',\n [StorageErrorCode.TIMEOUT]: 'The workspace operation timed out.',\n [StorageErrorCode.LIMIT_EXCEEDED]:\n 'The workspace operation exceeded a configured limit.',\n [StorageErrorCode.PROVIDER]: 'The workspace operation failed.',\n};\n\n/**\n * Safe error exposed at the AI tool boundary. It deliberately carries no\n * provider error, storage key, mount prefix, store name, or cause.\n */\nexport class AiSdkWorkspaceToolError extends Error {\n readonly code: AiSdkWorkspaceToolErrorCode;\n\n constructor(code: AiSdkWorkspaceToolErrorCode) {\n super(SAFE_ERROR_MESSAGES[code]);\n this.name = 'AiSdkWorkspaceToolError';\n this.code = code;\n }\n}\n\nexport interface AiSdkWorkspaceFileResult {\n kind: 'file';\n path: string;\n name: string;\n size: number;\n contentType: string;\n etag?: string;\n lastModified?: string;\n}\n\nexport interface AiSdkWorkspaceDirectoryResult {\n kind: 'directory';\n path: string;\n name: string;\n}\n\nexport type AiSdkWorkspaceEntryResult =\n AiSdkWorkspaceFileResult | AiSdkWorkspaceDirectoryResult;\n\nexport interface AiSdkWorkspaceTextFileResult extends AiSdkWorkspaceFileResult {\n text: string;\n}\n\nexport interface AiSdkWorkspacePageResult {\n entries: AiSdkWorkspaceEntryResult[];\n cursor?: string;\n}\n\nconst mutationToolNames = new Set<AiSdkWorkspaceMutationToolName>([\n 'workspace_write_file',\n 'workspace_copy_file',\n 'workspace_move_file',\n 'workspace_delete_file',\n]);\nconst utf8Encoder = new TextEncoder();\nconst forbiddenUnicodeCharacter = /\\p{C}/u;\nconst workspaceCursor = /^[A-Za-z0-9._-]+$/u;\nconst windowsDeviceName = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\\..*)?$/iu;\n\nfunction isLogicalPath(value: string): boolean {\n if (\n value.length === 0 ||\n value.startsWith('/') ||\n value.endsWith('/') ||\n value !== value.normalize('NFC') ||\n value.includes('\\\\') ||\n forbiddenUnicodeCharacter.test(value)\n ) {\n return false;\n }\n\n return value\n .split('/')\n .every(\n (segment) =>\n segment.length > 0 &&\n segment !== '.' &&\n segment !== '..' &&\n !segment.includes(':') &&\n !segment.endsWith('.') &&\n !segment.endsWith(' ') &&\n !windowsDeviceName.test(segment),\n );\n}\n\nfunction logicalPath(label: string, maxBytes: number) {\n return z\n .string()\n .min(1)\n .refine(isLogicalPath, {\n message: `${label} must be a portable, NFC-normalized, relative POSIX workspace path.`,\n })\n .refine((value) => utf8Encoder.encode(value).byteLength <= maxBytes, {\n message: `${label} exceeds the ${maxBytes}-byte workspace path limit.`,\n })\n .describe(\n `${label}. Use an NFC-normalized relative POSIX path inside the mounted workspace. Empty, dot, parent, colon, trailing dot or space, and Windows reserved device segments are forbidden.`,\n );\n}\n\nfunction searchQuery(maxBytes: number) {\n return z\n .string()\n .min(1)\n .refine(\n (value) =>\n !value.includes('\\\\') && !forbiddenUnicodeCharacter.test(value),\n { message: 'Search query contains forbidden characters.' },\n )\n .refine((value) => utf8Encoder.encode(value).byteLength <= maxBytes, {\n message: `Search query exceeds the ${maxBytes}-byte workspace limit.`,\n })\n .describe('Path pattern or text to find inside the mounted workspace.');\n}\n\nfunction boundedEtag() {\n return z\n .string()\n .refine(isCanonicalStorageEtag, {\n message: 'ETag must be a canonical bare value of at most 1,024 bytes.',\n })\n .describe(\n 'Exact canonical bare ETag returned by the latest workspace stat or read.',\n );\n}\n\nfunction continuationCursor(description: string, maxBytes: number) {\n return z\n .string()\n .min(1)\n .max(Math.min(STORAGE_WORKSPACE_MAX_CURSOR_BYTES, maxBytes))\n .regex(\n workspaceCursor,\n 'Cursor must be the bounded opaque token returned by the workspace.',\n )\n .optional()\n .describe(description);\n}\n\nfunction positiveIntegerAtMost(maximum: number, description: string) {\n return z\n .number()\n .int()\n .positive()\n .max(maximum)\n .optional()\n .describe(description);\n}\n\nfunction operationOptions(signal: AbortSignal | undefined): {\n signal?: AbortSignal;\n} {\n return signal === undefined ? {} : { signal };\n}\n\nfunction resolveApproval(\n toolName: AiSdkWorkspaceMutationToolName,\n config: AiSdkWorkspaceApprovalConfig,\n): boolean {\n return typeof config === 'boolean' ? config : (config[toolName] ?? true);\n}\n\nfunction resolveReadLimit(\n workspace: StorageWorkspace,\n requested: number | undefined,\n): number {\n const workspaceLimit = workspace.limits.maxReadBytes;\n if (!Number.isSafeInteger(workspaceLimit) || workspaceLimit <= 0) {\n throw new RangeError(\n 'workspace.limits.maxReadBytes must be a positive safe integer.',\n );\n }\n if (\n requested !== undefined &&\n (!Number.isSafeInteger(requested) || requested <= 0)\n ) {\n throw new RangeError('maxReadBytes must be a positive safe integer.');\n }\n return Math.min(requested ?? workspaceLimit, workspaceLimit);\n}\n\nfunction sanitizeToolError(\n error: unknown,\n signal: AbortSignal | undefined,\n): AiSdkWorkspaceToolError {\n if (error instanceof AiSdkWorkspaceToolError) {\n return error;\n }\n if (signal?.aborted === true) {\n return new AiSdkWorkspaceToolError(StorageErrorCode.ABORTED);\n }\n if (isStorageWorkspaceError(error)) {\n return new AiSdkWorkspaceToolError(error.code);\n }\n if (isStorageError(error)) {\n return new AiSdkWorkspaceToolError(error.code);\n }\n return new AiSdkWorkspaceToolError(StorageErrorCode.PROVIDER);\n}\n\nasync function executeSafely<Result>(\n signal: AbortSignal | undefined,\n operation: () => Promise<Result>,\n): Promise<Result> {\n try {\n return await operation();\n } catch (error) {\n throw sanitizeToolError(error, signal);\n }\n}\n\nasync function executeCreateAware<\n Result,\n CreateConflictResult extends JSONValue,\n>(\n signal: AbortSignal | undefined,\n input: { mode: 'create' | 'replace'; path: string },\n operation: () => Promise<Result>,\n mapCreateConflict:\n AiSdkWorkspaceCreateConflictMapper<CreateConflictResult> | undefined,\n): Promise<CreateConflictResult | Result> {\n try {\n return await operation();\n } catch (error) {\n const safeError = sanitizeToolError(error, signal);\n if (\n input.mode === 'create' &&\n safeError.code === StorageErrorCode.CONFLICT &&\n mapCreateConflict !== undefined\n ) {\n try {\n return await mapCreateConflict({ path: input.path });\n } catch {\n throw new AiSdkWorkspaceToolError(StorageErrorCode.PROVIDER);\n }\n }\n throw safeError;\n }\n}\n\nfunction serializeFile(file: StorageWorkspaceFile): AiSdkWorkspaceFileResult {\n if (file.etag !== undefined && !isCanonicalStorageEtag(file.etag)) {\n throw new Error('Workspace returned a malformed ETag.');\n }\n return {\n kind: 'file',\n path: file.path,\n name: file.name,\n size: file.size,\n contentType: file.contentType,\n ...(file.etag === undefined ? {} : { etag: file.etag }),\n ...(file.lastModified === undefined\n ? {}\n : { lastModified: file.lastModified.toISOString() }),\n };\n}\n\nfunction serializeEntry(\n entry: StorageWorkspaceEntry,\n): AiSdkWorkspaceEntryResult {\n return entry.kind === 'file'\n ? serializeFile(entry)\n : { kind: 'directory', path: entry.path, name: entry.name };\n}\n\nfunction serializeTextFile(\n file: StorageWorkspaceTextFile,\n): AiSdkWorkspaceTextFileResult {\n return { ...serializeFile(file), text: file.text };\n}\n\nfunction serializePage(page: {\n entries: StorageWorkspaceEntry[];\n cursor?: string;\n}): AiSdkWorkspacePageResult {\n return {\n entries: page.entries.map(serializeEntry),\n ...(page.cursor === undefined ? {} : { cursor: page.cursor }),\n };\n}\n\n/**\n * Creates Vercel AI SDK tools backed only by a mounted StorageWorkspace.\n *\n * A tool is omitted unless the workspace grants its required permission.\n * The workspace remains the enforcing boundary if a retained tool reference\n * is invoked after further narrowing.\n */\nexport function createAiSdkWorkspaceTools<\n CreateConflictResult extends JSONValue = never,\n>({\n workspace,\n maxReadBytes: requestedMaxReadBytes,\n requireApproval = true,\n mapCreateConflict,\n}: CreateAiSdkWorkspaceToolsOptions<CreateConflictResult>): ToolSet {\n const maxReadBytes = resolveReadLimit(workspace, requestedMaxReadBytes);\n const tools: ToolSet = {};\n const pathSchema = logicalPath('File path', workspace.limits.maxPathBytes);\n const directorySchema = logicalPath(\n 'Directory path',\n workspace.limits.maxPathBytes,\n );\n const etagSchema = boundedEtag();\n\n if (workspace.allows('list')) {\n tools.workspace_list = tool({\n description:\n 'List files and directories inside the mounted workspace. Omit directory to list the workspace root.',\n // Optional pagination and directory inputs are not compatible with\n // OpenAI strict function schemas, which require every property.\n strict: false,\n inputSchema: z\n .object({\n directory: directorySchema.optional(),\n recursive: z\n .boolean()\n .optional()\n .describe('Whether to include descendants recursively.'),\n limit: positiveIntegerAtMost(\n workspace.limits.maxPageSize,\n `Maximum entries to return, up to ${workspace.limits.maxPageSize}.`,\n ),\n cursor: continuationCursor(\n 'Opaque cursor returned by a preceding list call. It may be replayed before expiry while its provider continuation remains valid; repeat the same directory, recursive, and limit options when continuing.',\n workspace.limits.maxCursorBytes,\n ),\n })\n .strict(),\n execute: (input, { abortSignal }) =>\n executeSafely(abortSignal, async () =>\n serializePage(\n await workspace.list({\n ...(input.directory === undefined\n ? {}\n : { directory: input.directory }),\n ...(input.recursive === undefined\n ? {}\n : { recursive: input.recursive }),\n ...(input.limit === undefined ? {} : { limit: input.limit }),\n ...(input.cursor === undefined ? {} : { cursor: input.cursor }),\n ...operationOptions(abortSignal),\n }),\n ),\n ),\n });\n }\n\n if (workspace.allows('read')) {\n tools.workspace_stat = tool({\n description:\n 'Inspect a file inside the mounted workspace without reading its contents. Returns the ETag required for safe replace, move, and delete operations.',\n strict: true,\n inputSchema: z.object({ path: pathSchema }).strict(),\n execute: ({ path }, { abortSignal }) =>\n executeSafely(abortSignal, async () =>\n serializeFile(\n await workspace.stat(path, operationOptions(abortSignal)),\n ),\n ),\n });\n\n tools.workspace_read_file = tool({\n description: `Read a UTF-8 text file inside the mounted workspace. The result is bounded to ${maxReadBytes} bytes.`,\n strict: true,\n inputSchema: z.object({ path: pathSchema }).strict(),\n execute: ({ path }, { abortSignal }) =>\n executeSafely(abortSignal, async () =>\n serializeTextFile(\n await workspace.readText(path, {\n maxBytes: maxReadBytes,\n ...operationOptions(abortSignal),\n }),\n ),\n ),\n });\n }\n\n if (workspace.allows('search')) {\n tools.workspace_search = tool({\n description:\n 'Search logical paths inside the mounted workspace using a bounded glob, substring, or exact match. Omit directory to search from the workspace root.',\n // Search intentionally has optional filters and cursor inputs.\n strict: false,\n inputSchema: z\n .object({\n query: searchQuery(workspace.limits.maxPathBytes),\n directory: directorySchema.optional(),\n match: z\n .enum(['glob', 'substring', 'exact'])\n .optional()\n .describe('Matching strategy; defaults to glob.'),\n caseInsensitive: z.boolean().optional(),\n limit: positiveIntegerAtMost(\n workspace.limits.maxSearchResults,\n `Maximum matches to return, up to ${workspace.limits.maxSearchResults}.`,\n ),\n cursor: continuationCursor(\n 'Opaque cursor returned by a preceding search call. It may be replayed before expiry while its provider continuation remains valid; repeat the same query, directory, match, caseInsensitive, and limit options when continuing.',\n workspace.limits.maxCursorBytes,\n ),\n })\n .strict(),\n execute: (\n { query, directory, match, caseInsensitive, limit, cursor },\n { abortSignal },\n ) =>\n executeSafely(abortSignal, async () =>\n serializePage(\n await workspace.search(query, {\n ...(directory === undefined ? {} : { directory }),\n ...(match === undefined ? {} : { match }),\n ...(caseInsensitive === undefined ? {} : { caseInsensitive }),\n ...(limit === undefined ? {} : { limit }),\n ...(cursor === undefined ? {} : { cursor }),\n ...operationOptions(abortSignal),\n }),\n ),\n ),\n });\n }\n\n const canCreate = workspace.allows('create');\n const canReplace = workspace.allows('replace');\n if (canCreate || canReplace) {\n const commonWriteShape = {\n path: pathSchema,\n content: z\n .string()\n .refine(\n (value) =>\n utf8Encoder.encode(value).byteLength <=\n workspace.limits.maxWriteBytes,\n {\n message: `Content exceeds the ${workspace.limits.maxWriteBytes}-byte workspace write limit.`,\n },\n )\n .describe(\n `UTF-8 text to write. The workspace enforces its ${workspace.limits.maxWriteBytes}-byte write limit.`,\n ),\n };\n const createSchema = z\n .object({ ...commonWriteShape, mode: z.literal('create') })\n .strict();\n const replaceSchema = z\n .object({\n ...commonWriteShape,\n mode: z.literal('replace'),\n etag: etagSchema,\n })\n .strict();\n const inputSchema =\n canCreate && canReplace\n ? z.discriminatedUnion('mode', [createSchema, replaceSchema])\n : canCreate\n ? createSchema\n : replaceSchema;\n\n tools.workspace_write_file = tool<\n z.infer<typeof inputSchema>,\n unknown,\n Record<string, unknown>\n >({\n description:\n canCreate && canReplace\n ? 'Create a new UTF-8 text file or replace an existing file inside the mounted workspace. Create fails if the destination exists; replace requires its current ETag.'\n : canCreate\n ? 'Create a new UTF-8 text file inside the mounted workspace. The operation fails if the destination already exists.'\n : 'Replace an existing UTF-8 text file inside the mounted workspace using its current ETag.',\n // The combined create/replace schema is a discriminated union. OpenAI\n // strict function tools reject its root-level oneOf, while the runtime\n // Zod schema continues to validate every tool call in non-strict mode.\n strict: !(canCreate && canReplace),\n inputSchema,\n needsApproval: resolveApproval('workspace_write_file', requireApproval),\n execute: (input, { abortSignal }) =>\n executeCreateAware(\n abortSignal,\n input,\n async () =>\n serializeFile(\n input.mode === 'create'\n ? await workspace.writeFile(input.path, input.content, {\n mode: 'create',\n ...operationOptions(abortSignal),\n })\n : await workspace.writeFile(input.path, input.content, {\n mode: 'replace',\n etag: input.etag,\n ...operationOptions(abortSignal),\n }),\n ),\n mapCreateConflict,\n ),\n });\n }\n\n const canCopy =\n workspace.allows('copy') &&\n workspace.allows('read') &&\n workspace.allows('create');\n if (canCopy) {\n tools.workspace_copy_file = tool({\n description:\n 'Copy an exact observed version of a file inside the mounted workspace. The source remains intact, and the operation fails if the source changed or the destination already exists.',\n strict: true,\n inputSchema: z\n .object({\n source: logicalPath(\n 'Source file path',\n workspace.limits.maxPathBytes,\n ),\n destination: logicalPath(\n 'Destination file path',\n workspace.limits.maxPathBytes,\n ),\n etag: etagSchema.describe(\n 'Exact ETag of the source returned by the latest workspace stat or read.',\n ),\n })\n .strict(),\n needsApproval: resolveApproval('workspace_copy_file', requireApproval),\n execute: ({ source, destination, etag }, { abortSignal }) =>\n executeSafely(abortSignal, async () =>\n serializeFile(\n await workspace.copyFile(source, destination, {\n etag,\n ...operationOptions(abortSignal),\n }),\n ),\n ),\n });\n }\n\n const canMove =\n workspace.allows('move') &&\n workspace.allows('read') &&\n workspace.allows('create') &&\n workspace.allows('delete');\n if (canMove) {\n tools.workspace_move_file = tool({\n description:\n \"Move a file inside the mounted workspace using the source's current ETag. The operation fails if the destination already exists. If source deletion cannot be confirmed, the destination is retained and the tool reports a conflict; inspect both paths before retrying.\",\n strict: true,\n inputSchema: z\n .object({\n source: logicalPath(\n 'Source file path',\n workspace.limits.maxPathBytes,\n ),\n destination: logicalPath(\n 'Destination file path',\n workspace.limits.maxPathBytes,\n ),\n etag: etagSchema.describe(\n 'Exact ETag of the source returned by the latest workspace stat or read.',\n ),\n })\n .strict(),\n needsApproval: resolveApproval('workspace_move_file', requireApproval),\n execute: ({ source, destination, etag }, { abortSignal }) =>\n executeSafely(abortSignal, async () =>\n serializeFile(\n await workspace.moveFile(source, destination, {\n etag,\n ...operationOptions(abortSignal),\n }),\n ),\n ),\n });\n }\n\n if (workspace.allows('delete')) {\n tools.workspace_delete_file = tool({\n description:\n 'Delete a file inside the mounted workspace using its current ETag.',\n strict: true,\n inputSchema: z\n .object({\n path: pathSchema,\n etag: etagSchema,\n })\n .strict(),\n needsApproval: resolveApproval('workspace_delete_file', requireApproval),\n execute: ({ path, etag }, { abortSignal }) =>\n executeSafely(abortSignal, async () => {\n await workspace.deleteFile(path, {\n etag,\n ...operationOptions(abortSignal),\n });\n return { deleted: true as const, path };\n }),\n });\n }\n\n return tools;\n}\n\nexport function isAiSdkWorkspaceMutationToolName(\n value: string,\n): value is AiSdkWorkspaceMutationToolName {\n return mutationToolNames.has(value as AiSdkWorkspaceMutationToolName);\n}\n"]}