@openai/agents-extensions 0.8.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/ai-sdk-ui/uiMessageStream.d.ts +5 -0
  2. package/dist/ai-sdk-ui/uiMessageStream.js +10 -3
  3. package/dist/ai-sdk-ui/uiMessageStream.js.map +1 -1
  4. package/dist/ai-sdk-ui/uiMessageStream.mjs +9 -3
  5. package/dist/ai-sdk-ui/uiMessageStream.mjs.map +1 -1
  6. package/dist/metadata.js +2 -2
  7. package/dist/metadata.mjs +2 -2
  8. package/dist/sandbox/blaxel/index.d.ts +2 -0
  9. package/dist/sandbox/blaxel/index.js +19 -0
  10. package/dist/sandbox/blaxel/index.js.map +1 -0
  11. package/dist/sandbox/blaxel/index.mjs +3 -0
  12. package/dist/sandbox/blaxel/index.mjs.map +1 -0
  13. package/dist/sandbox/blaxel/mounts.d.ts +101 -0
  14. package/dist/sandbox/blaxel/mounts.js +542 -0
  15. package/dist/sandbox/blaxel/mounts.js.map +1 -0
  16. package/dist/sandbox/blaxel/mounts.mjs +529 -0
  17. package/dist/sandbox/blaxel/mounts.mjs.map +1 -0
  18. package/dist/sandbox/blaxel/sandbox.d.ts +155 -0
  19. package/dist/sandbox/blaxel/sandbox.js +1110 -0
  20. package/dist/sandbox/blaxel/sandbox.js.map +1 -0
  21. package/dist/sandbox/blaxel/sandbox.mjs +1072 -0
  22. package/dist/sandbox/blaxel/sandbox.mjs.map +1 -0
  23. package/dist/sandbox/cloudflare/index.d.ts +2 -0
  24. package/dist/sandbox/cloudflare/index.js +19 -0
  25. package/dist/sandbox/cloudflare/index.js.map +1 -0
  26. package/dist/sandbox/cloudflare/index.mjs +3 -0
  27. package/dist/sandbox/cloudflare/index.mjs.map +1 -0
  28. package/dist/sandbox/cloudflare/mounts.d.ts +28 -0
  29. package/dist/sandbox/cloudflare/mounts.js +150 -0
  30. package/dist/sandbox/cloudflare/mounts.js.map +1 -0
  31. package/dist/sandbox/cloudflare/mounts.mjs +143 -0
  32. package/dist/sandbox/cloudflare/mounts.mjs.map +1 -0
  33. package/dist/sandbox/cloudflare/sandbox.d.ts +91 -0
  34. package/dist/sandbox/cloudflare/sandbox.js +1163 -0
  35. package/dist/sandbox/cloudflare/sandbox.js.map +1 -0
  36. package/dist/sandbox/cloudflare/sandbox.mjs +1125 -0
  37. package/dist/sandbox/cloudflare/sandbox.mjs.map +1 -0
  38. package/dist/sandbox/daytona/index.d.ts +2 -0
  39. package/dist/sandbox/daytona/index.js +19 -0
  40. package/dist/sandbox/daytona/index.js.map +1 -0
  41. package/dist/sandbox/daytona/index.mjs +3 -0
  42. package/dist/sandbox/daytona/index.mjs.map +1 -0
  43. package/dist/sandbox/daytona/mounts.d.ts +11 -0
  44. package/dist/sandbox/daytona/mounts.js +17 -0
  45. package/dist/sandbox/daytona/mounts.js.map +1 -0
  46. package/dist/sandbox/daytona/mounts.mjs +12 -0
  47. package/dist/sandbox/daytona/mounts.mjs.map +1 -0
  48. package/dist/sandbox/daytona/sandbox.d.ts +160 -0
  49. package/dist/sandbox/daytona/sandbox.js +938 -0
  50. package/dist/sandbox/daytona/sandbox.js.map +1 -0
  51. package/dist/sandbox/daytona/sandbox.mjs +900 -0
  52. package/dist/sandbox/daytona/sandbox.mjs.map +1 -0
  53. package/dist/sandbox/e2b/index.d.ts +2 -0
  54. package/dist/sandbox/e2b/index.js +19 -0
  55. package/dist/sandbox/e2b/index.js.map +1 -0
  56. package/dist/sandbox/e2b/index.mjs +3 -0
  57. package/dist/sandbox/e2b/index.mjs.map +1 -0
  58. package/dist/sandbox/e2b/mounts.d.ts +11 -0
  59. package/dist/sandbox/e2b/mounts.js +17 -0
  60. package/dist/sandbox/e2b/mounts.js.map +1 -0
  61. package/dist/sandbox/e2b/mounts.mjs +12 -0
  62. package/dist/sandbox/e2b/mounts.mjs.map +1 -0
  63. package/dist/sandbox/e2b/sandbox.d.ts +169 -0
  64. package/dist/sandbox/e2b/sandbox.js +829 -0
  65. package/dist/sandbox/e2b/sandbox.js.map +1 -0
  66. package/dist/sandbox/e2b/sandbox.mjs +791 -0
  67. package/dist/sandbox/e2b/sandbox.mjs.map +1 -0
  68. package/dist/sandbox/modal/index.d.ts +2 -0
  69. package/dist/sandbox/modal/index.js +19 -0
  70. package/dist/sandbox/modal/index.js.map +1 -0
  71. package/dist/sandbox/modal/index.mjs +3 -0
  72. package/dist/sandbox/modal/index.mjs.map +1 -0
  73. package/dist/sandbox/modal/mounts.d.ts +24 -0
  74. package/dist/sandbox/modal/mounts.js +194 -0
  75. package/dist/sandbox/modal/mounts.js.map +1 -0
  76. package/dist/sandbox/modal/mounts.mjs +188 -0
  77. package/dist/sandbox/modal/mounts.mjs.map +1 -0
  78. package/dist/sandbox/modal/sandbox.d.ts +220 -0
  79. package/dist/sandbox/modal/sandbox.js +1621 -0
  80. package/dist/sandbox/modal/sandbox.js.map +1 -0
  81. package/dist/sandbox/modal/sandbox.mjs +1581 -0
  82. package/dist/sandbox/modal/sandbox.mjs.map +1 -0
  83. package/dist/sandbox/runloop/index.d.ts +2 -0
  84. package/dist/sandbox/runloop/index.js +19 -0
  85. package/dist/sandbox/runloop/index.js.map +1 -0
  86. package/dist/sandbox/runloop/index.mjs +3 -0
  87. package/dist/sandbox/runloop/index.mjs.map +1 -0
  88. package/dist/sandbox/runloop/mounts.d.ts +11 -0
  89. package/dist/sandbox/runloop/mounts.js +17 -0
  90. package/dist/sandbox/runloop/mounts.js.map +1 -0
  91. package/dist/sandbox/runloop/mounts.mjs +12 -0
  92. package/dist/sandbox/runloop/mounts.mjs.map +1 -0
  93. package/dist/sandbox/runloop/sandbox.d.ts +266 -0
  94. package/dist/sandbox/runloop/sandbox.js +1390 -0
  95. package/dist/sandbox/runloop/sandbox.js.map +1 -0
  96. package/dist/sandbox/runloop/sandbox.mjs +1346 -0
  97. package/dist/sandbox/runloop/sandbox.mjs.map +1 -0
  98. package/dist/sandbox/shared/archive.d.ts +34 -0
  99. package/dist/sandbox/shared/archive.js +397 -0
  100. package/dist/sandbox/shared/archive.js.map +1 -0
  101. package/dist/sandbox/shared/archive.mjs +389 -0
  102. package/dist/sandbox/shared/archive.mjs.map +1 -0
  103. package/dist/sandbox/shared/editor.d.ts +17 -0
  104. package/dist/sandbox/shared/editor.js +65 -0
  105. package/dist/sandbox/shared/editor.js.map +1 -0
  106. package/dist/sandbox/shared/editor.mjs +61 -0
  107. package/dist/sandbox/shared/editor.mjs.map +1 -0
  108. package/dist/sandbox/shared/environment.d.ts +1 -0
  109. package/dist/sandbox/shared/environment.js +13 -0
  110. package/dist/sandbox/shared/environment.js.map +1 -0
  111. package/dist/sandbox/shared/environment.mjs +2 -0
  112. package/dist/sandbox/shared/environment.mjs.map +1 -0
  113. package/dist/sandbox/shared/git.d.ts +4 -0
  114. package/dist/sandbox/shared/git.js +20 -0
  115. package/dist/sandbox/shared/git.js.map +1 -0
  116. package/dist/sandbox/shared/git.mjs +17 -0
  117. package/dist/sandbox/shared/git.mjs.map +1 -0
  118. package/dist/sandbox/shared/inContainerMounts.d.ts +40 -0
  119. package/dist/sandbox/shared/inContainerMounts.js +670 -0
  120. package/dist/sandbox/shared/inContainerMounts.js.map +1 -0
  121. package/dist/sandbox/shared/inContainerMounts.mjs +662 -0
  122. package/dist/sandbox/shared/inContainerMounts.mjs.map +1 -0
  123. package/dist/sandbox/shared/index.d.ts +23 -0
  124. package/dist/sandbox/shared/index.js +112 -0
  125. package/dist/sandbox/shared/index.js.map +1 -0
  126. package/dist/sandbox/shared/index.mjs +18 -0
  127. package/dist/sandbox/shared/index.mjs.map +1 -0
  128. package/dist/sandbox/shared/localSources.d.ts +8 -0
  129. package/dist/sandbox/shared/localSources.js +378 -0
  130. package/dist/sandbox/shared/localSources.js.map +1 -0
  131. package/dist/sandbox/shared/localSources.mjs +372 -0
  132. package/dist/sandbox/shared/localSources.mjs.map +1 -0
  133. package/dist/sandbox/shared/manifest.d.ts +38 -0
  134. package/dist/sandbox/shared/manifest.js +254 -0
  135. package/dist/sandbox/shared/manifest.js.map +1 -0
  136. package/dist/sandbox/shared/manifest.mjs +233 -0
  137. package/dist/sandbox/shared/manifest.mjs.map +1 -0
  138. package/dist/sandbox/shared/media.d.ts +1 -0
  139. package/dist/sandbox/shared/media.js +10 -0
  140. package/dist/sandbox/shared/media.js.map +1 -0
  141. package/dist/sandbox/shared/media.mjs +2 -0
  142. package/dist/sandbox/shared/media.mjs.map +1 -0
  143. package/dist/sandbox/shared/metadata.d.ts +7 -0
  144. package/dist/sandbox/shared/metadata.js +87 -0
  145. package/dist/sandbox/shared/metadata.js.map +1 -0
  146. package/dist/sandbox/shared/metadata.mjs +81 -0
  147. package/dist/sandbox/shared/metadata.mjs.map +1 -0
  148. package/dist/sandbox/shared/output.d.ts +1 -0
  149. package/dist/sandbox/shared/output.js +8 -0
  150. package/dist/sandbox/shared/output.js.map +1 -0
  151. package/dist/sandbox/shared/output.mjs +2 -0
  152. package/dist/sandbox/shared/output.mjs.map +1 -0
  153. package/dist/sandbox/shared/paths.d.ts +27 -0
  154. package/dist/sandbox/shared/paths.js +245 -0
  155. package/dist/sandbox/shared/paths.js.map +1 -0
  156. package/dist/sandbox/shared/paths.mjs +236 -0
  157. package/dist/sandbox/shared/paths.mjs.map +1 -0
  158. package/dist/sandbox/shared/ports.d.ts +14 -0
  159. package/dist/sandbox/shared/ports.js +75 -0
  160. package/dist/sandbox/shared/ports.js.map +1 -0
  161. package/dist/sandbox/shared/ports.mjs +69 -0
  162. package/dist/sandbox/shared/ports.mjs.map +1 -0
  163. package/dist/sandbox/shared/process.d.ts +16 -0
  164. package/dist/sandbox/shared/process.js +90 -0
  165. package/dist/sandbox/shared/process.js.map +1 -0
  166. package/dist/sandbox/shared/process.mjs +86 -0
  167. package/dist/sandbox/shared/process.mjs.map +1 -0
  168. package/dist/sandbox/shared/pty.d.ts +81 -0
  169. package/dist/sandbox/shared/pty.js +382 -0
  170. package/dist/sandbox/shared/pty.js.map +1 -0
  171. package/dist/sandbox/shared/pty.mjs +335 -0
  172. package/dist/sandbox/shared/pty.mjs.map +1 -0
  173. package/dist/sandbox/shared/runAs.d.ts +46 -0
  174. package/dist/sandbox/shared/runAs.js +149 -0
  175. package/dist/sandbox/shared/runAs.js.map +1 -0
  176. package/dist/sandbox/shared/runAs.mjs +141 -0
  177. package/dist/sandbox/shared/runAs.mjs.map +1 -0
  178. package/dist/sandbox/shared/session.d.ts +16 -0
  179. package/dist/sandbox/shared/session.js +138 -0
  180. package/dist/sandbox/shared/session.js.map +1 -0
  181. package/dist/sandbox/shared/session.mjs +128 -0
  182. package/dist/sandbox/shared/session.mjs.map +1 -0
  183. package/dist/sandbox/shared/sessionBase.d.ts +90 -0
  184. package/dist/sandbox/shared/sessionBase.js +446 -0
  185. package/dist/sandbox/shared/sessionBase.js.map +1 -0
  186. package/dist/sandbox/shared/sessionBase.mjs +442 -0
  187. package/dist/sandbox/shared/sessionBase.mjs.map +1 -0
  188. package/dist/sandbox/shared/sessionState.d.ts +7 -0
  189. package/dist/sandbox/shared/sessionState.js +35 -0
  190. package/dist/sandbox/shared/sessionState.js.map +1 -0
  191. package/dist/sandbox/shared/sessionState.mjs +31 -0
  192. package/dist/sandbox/shared/sessionState.mjs.map +1 -0
  193. package/dist/sandbox/shared/snapshots.d.ts +9 -0
  194. package/dist/sandbox/shared/snapshots.js +62 -0
  195. package/dist/sandbox/shared/snapshots.js.map +1 -0
  196. package/dist/sandbox/shared/snapshots.mjs +57 -0
  197. package/dist/sandbox/shared/snapshots.mjs.map +1 -0
  198. package/dist/sandbox/shared/typeGuards.d.ts +10 -0
  199. package/dist/sandbox/shared/typeGuards.js +50 -0
  200. package/dist/sandbox/shared/typeGuards.js.map +1 -0
  201. package/dist/sandbox/shared/typeGuards.mjs +38 -0
  202. package/dist/sandbox/shared/typeGuards.mjs.map +1 -0
  203. package/dist/sandbox/shared/types.d.ts +24 -0
  204. package/dist/sandbox/shared/types.js +3 -0
  205. package/dist/sandbox/shared/types.js.map +1 -0
  206. package/dist/sandbox/shared/types.mjs +2 -0
  207. package/dist/sandbox/shared/types.mjs.map +1 -0
  208. package/dist/sandbox/vercel/index.d.ts +1 -0
  209. package/dist/sandbox/vercel/index.js +18 -0
  210. package/dist/sandbox/vercel/index.js.map +1 -0
  211. package/dist/sandbox/vercel/index.mjs +2 -0
  212. package/dist/sandbox/vercel/index.mjs.map +1 -0
  213. package/dist/sandbox/vercel/sandbox.d.ts +135 -0
  214. package/dist/sandbox/vercel/sandbox.js +1001 -0
  215. package/dist/sandbox/vercel/sandbox.js.map +1 -0
  216. package/dist/sandbox/vercel/sandbox.mjs +963 -0
  217. package/dist/sandbox/vercel/sandbox.mjs.map +1 -0
  218. package/package.json +108 -4
@@ -0,0 +1,662 @@
1
+ import { SandboxMountError, SandboxUnsupportedFeatureError, } from '@openai/agents-core/sandbox';
2
+ import { shellQuote } from "./paths.mjs";
3
+ import { readOptionalString } from "./typeGuards.mjs";
4
+ const DEFAULT_PACKAGE_MANAGERS = ['apt'];
5
+ const SAFE_PIDFILE_KILL_FUNCTION = String.raw `openai_agents_kill_pidfile() {
6
+ pidfile=$1
7
+ shift
8
+ pid=$(cat "$pidfile" 2>/dev/null || true)
9
+ case "$pid" in
10
+ ''|0|*[!0-9]*) return 0 ;;
11
+ esac
12
+ cmdline=$(tr '\000' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)
13
+ [ -n "$cmdline" ] || return 0
14
+ for expected in "$@"; do
15
+ case "$cmdline" in
16
+ *"$expected"*) ;;
17
+ *) return 0 ;;
18
+ esac
19
+ done
20
+ kill "$pid" >/dev/null 2>&1 || true
21
+ }`;
22
+ export function isRcloneCloudBucketMountEntry(entry, strategyType) {
23
+ return (isRcloneBucketMount(entry) && entry.mountStrategy?.type === strategyType);
24
+ }
25
+ export function rclonePatternFromMountStrategy(strategy) {
26
+ const pattern = strategy?.pattern;
27
+ if (isRcloneMountPattern(pattern)) {
28
+ return pattern;
29
+ }
30
+ return undefined;
31
+ }
32
+ export async function mountRcloneCloudBucket(options) {
33
+ if (!isRcloneCloudBucketMountEntry(options.entry, options.strategyType)) {
34
+ throw new SandboxUnsupportedFeatureError(`${options.providerName} only supports ${options.strategyType} mount entries for in-container cloud bucket mounts.`, {
35
+ provider: options.providerId,
36
+ feature: 'entry.mountStrategy',
37
+ mountType: options.entry.type,
38
+ strategyType: mountStrategyType(options.entry),
39
+ });
40
+ }
41
+ const pattern = options.pattern ?? { type: 'rclone', mode: 'fuse' };
42
+ if (pattern.type !== 'rclone') {
43
+ throw new SandboxUnsupportedFeatureError(`${options.providerName} cloud bucket mounts require a rclone mount pattern.`, {
44
+ provider: options.providerId,
45
+ feature: 'entry.mountStrategy.pattern',
46
+ mountType: options.entry.type,
47
+ patternType: pattern.type,
48
+ });
49
+ }
50
+ const mode = pattern.mode ?? 'fuse';
51
+ if (mode !== 'fuse' && mode !== 'nfs') {
52
+ throw new SandboxUnsupportedFeatureError(`${options.providerName} cloud bucket mounts support rclone fuse and nfs modes only.`, {
53
+ provider: options.providerId,
54
+ feature: 'entry.mountStrategy.pattern.mode',
55
+ mountType: options.entry.type,
56
+ mode: pattern.mode,
57
+ });
58
+ }
59
+ const remoteName = resolveRcloneRemoteName(pattern, options);
60
+ if (mode === 'fuse') {
61
+ await ensureFuseSupport(options);
62
+ }
63
+ await ensureRclone(options);
64
+ const config = await resolveRcloneMountConfig(options, pattern, remoteName);
65
+ const configPath = `/tmp/openai-agents-${options.providerId}-${config.remoteName}.conf`;
66
+ await runRequiredMountCommand(options, {
67
+ label: 'prepare rclone config',
68
+ command: [
69
+ `mkdir -p -- ${shellQuote(options.mountPath)}`,
70
+ `rm -f -- ${shellQuote(configPath)}`,
71
+ `touch ${shellQuote(configPath)}`,
72
+ `chmod 600 ${shellQuote(configPath)}`,
73
+ ].join(' && '),
74
+ timeoutMs: 30_000,
75
+ });
76
+ await options.writeFile(configPath, config.configText);
77
+ await runRequiredMountCommand(options, {
78
+ label: 'protect rclone config',
79
+ command: `chmod 600 ${shellQuote(configPath)}`,
80
+ timeoutMs: 30_000,
81
+ });
82
+ if (mode === 'nfs') {
83
+ await mountRcloneNfs(options, pattern, config, configPath);
84
+ return;
85
+ }
86
+ await mountRcloneFuse(options, pattern, config, configPath);
87
+ }
88
+ export async function unmountRcloneMount(args) {
89
+ const pidPathGlob = `/tmp/openai-agents-${args.providerId}-*.nfs.pid`;
90
+ const unmountCommand = [
91
+ `fusermount3 -u ${shellQuote(args.mountPath)} >/dev/null 2>&1`,
92
+ `fusermount -u ${shellQuote(args.mountPath)} >/dev/null 2>&1`,
93
+ `umount ${shellQuote(args.mountPath)} >/dev/null 2>&1`,
94
+ `umount -l ${shellQuote(args.mountPath)} >/dev/null 2>&1`,
95
+ 'true',
96
+ ].join(' || ');
97
+ const command = [
98
+ unmountCommand,
99
+ safePidFileKillFunctionCommand(),
100
+ `for pidfile in ${pidPathGlob}; do [ -e "$pidfile" ] || continue; ${safeKillPidFileCommand({
101
+ pidFile: '$pidfile',
102
+ expectedCmdlineFragments: ['rclone', 'serve', 'nfs'],
103
+ })}; rm -f -- "$pidfile"; done`,
104
+ ].join(' ; ');
105
+ await args.runCommand(command, { timeoutMs: 30_000 });
106
+ }
107
+ export function safePidFileKillFunctionCommand() {
108
+ return SAFE_PIDFILE_KILL_FUNCTION;
109
+ }
110
+ export function safeKillPidFileCommand(args) {
111
+ return [
112
+ 'openai_agents_kill_pidfile',
113
+ args.pidFile,
114
+ ...args.expectedCmdlineFragments.map(shellQuote),
115
+ ].join(' ');
116
+ }
117
+ async function mountRcloneFuse(options, pattern, config, configPath) {
118
+ const userIds = await defaultUserIds(options.runCommand);
119
+ // rclone FUSE needs stable uid/gid options so files appear owned by the sandbox's
120
+ // default user instead of root when the provider image supports id lookup.
121
+ const mountArgs = [
122
+ 'rclone',
123
+ 'mount',
124
+ `${config.remoteName}:${config.remotePath}`,
125
+ options.mountPath,
126
+ '--config',
127
+ configPath,
128
+ '--daemon',
129
+ '--allow-other',
130
+ ...(userIds ? ['--uid', userIds.uid, '--gid', userIds.gid] : []),
131
+ ...(config.readOnly ? ['--read-only'] : []),
132
+ ...rclonePatternArgs(pattern),
133
+ ];
134
+ try {
135
+ // The config file can contain credentials, so delete it immediately after the
136
+ // daemonized mount command has consumed it.
137
+ await runRequiredMountCommand(options, {
138
+ label: 'rclone mount',
139
+ command: joinShellArgs(mountArgs),
140
+ timeoutMs: 60_000,
141
+ });
142
+ }
143
+ finally {
144
+ await options
145
+ .runCommand(`rm -f -- ${shellQuote(configPath)}`, {
146
+ timeoutMs: 30_000,
147
+ })
148
+ .catch(() => { });
149
+ }
150
+ }
151
+ async function mountRcloneNfs(options, pattern, config, configPath) {
152
+ await runRequiredMountCommand(options, {
153
+ label: 'check rclone nfs server support',
154
+ command: '/usr/local/bin/rclone serve nfs --help >/dev/null 2>&1 || rclone serve nfs --help >/dev/null 2>&1',
155
+ timeoutMs: 30_000,
156
+ });
157
+ const nfsAddr = rclonePatternString(pattern, 'nfsAddr') ?? '127.0.0.1:2049';
158
+ const pidPath = `/tmp/openai-agents-${options.providerId}-${config.remoteName}.nfs.pid`;
159
+ const logPath = `/tmp/openai-agents-${options.providerId}-${config.remoteName}.nfs.log`;
160
+ const serverArgs = [
161
+ 'rclone',
162
+ 'serve',
163
+ 'nfs',
164
+ `${config.remoteName}:${config.remotePath}`,
165
+ '--addr',
166
+ nfsAddr,
167
+ '--config',
168
+ configPath,
169
+ ...(config.readOnly ? ['--read-only'] : []),
170
+ ...rclonePatternArgs(pattern),
171
+ ];
172
+ await runRequiredMountCommand(options, {
173
+ label: 'start rclone nfs server',
174
+ command: `(${joinShellArgs(serverArgs)} > ${shellQuote(logPath)} 2>&1 & pid=$!; printf %s "$pid" > ${shellQuote(pidPath)}; (sleep 2; rm -f -- ${shellQuote(configPath)}) >/dev/null 2>&1 &)`,
175
+ timeoutMs: 30_000,
176
+ });
177
+ try {
178
+ // Keep parity with Python: attempt the mount even when /proc/filesystems does
179
+ // not advertise NFS support because some sandbox images expose it late.
180
+ await options.runCommand('grep -qw nfs /proc/filesystems', {
181
+ timeoutMs: 30_000,
182
+ });
183
+ const timeoutCheck = await options.runCommand('command -v timeout >/dev/null 2>&1', { timeoutMs: 30_000 });
184
+ const timeoutPrefix = timeoutCheck.status === 0 ? 'timeout 10s ' : '';
185
+ const { host, port } = splitNfsAddr(nfsAddr);
186
+ const mountOptions = rclonePatternStringArray(pattern, 'nfsMountOptions') ?? ['vers=4.1', 'tcp', `port=${port}`, 'soft', 'timeo=50', 'retrans=1'];
187
+ await runRequiredMountCommand(options, {
188
+ label: 'mount rclone nfs client',
189
+ command: [
190
+ 'for i in 1 2 3; do',
191
+ `${timeoutPrefix}mount -v -t nfs -o ${shellQuote(mountOptions.join(','))} ${shellQuote(`${host}:/`)} ${shellQuote(options.mountPath)}`,
192
+ '&& exit 0; sleep 1; done; exit 1',
193
+ ].join(' '),
194
+ timeoutMs: 60_000,
195
+ user: 'root',
196
+ });
197
+ }
198
+ catch (error) {
199
+ await stopRcloneNfsServer(options, pidPath, configPath);
200
+ throw error;
201
+ }
202
+ }
203
+ function buildRcloneMountConfig(entry, options) {
204
+ switch (entry.type) {
205
+ case 's3_mount':
206
+ return {
207
+ remoteName: options.remoteName,
208
+ remotePath: joinRemotePath(entry.bucket, entry.prefix),
209
+ configText: [
210
+ `[${options.remoteName}]`,
211
+ 'type = s3',
212
+ `provider = ${entry.s3Provider ?? 'AWS'}`,
213
+ ...(entry.endpointUrl ? [`endpoint = ${entry.endpointUrl}`] : []),
214
+ ...(entry.region ? [`region = ${entry.region}`] : []),
215
+ ...s3CredentialLines(entry),
216
+ '',
217
+ ].join('\n'),
218
+ readOnly: entry.readOnly ?? true,
219
+ mountType: entry.type,
220
+ };
221
+ case 'r2_mount':
222
+ validateCredentialPair({
223
+ provider: 'R2',
224
+ mountType: entry.type,
225
+ accessKeyId: readOptionalString(entry, 'accessKeyId'),
226
+ secretAccessKey: readOptionalString(entry, 'secretAccessKey'),
227
+ });
228
+ if (!entry.accountId) {
229
+ throw new SandboxMountError('R2 cloud bucket mounts require accountId.', {
230
+ mountType: entry.type,
231
+ });
232
+ }
233
+ return {
234
+ remoteName: options.remoteName,
235
+ remotePath: joinRemotePath(entry.bucket, entry.prefix),
236
+ configText: [
237
+ `[${options.remoteName}]`,
238
+ 'type = s3',
239
+ 'provider = Cloudflare',
240
+ `endpoint = ${readOptionalString(entry, 'customDomain') ??
241
+ `https://${entry.accountId}.r2.cloudflarestorage.com`}`,
242
+ 'acl = private',
243
+ ...r2CredentialLines(entry),
244
+ '',
245
+ ].join('\n'),
246
+ readOnly: entry.readOnly ?? true,
247
+ mountType: entry.type,
248
+ };
249
+ case 'gcs_mount':
250
+ return buildGcsRcloneMountConfig(entry, options.remoteName);
251
+ case 'azure_blob_mount':
252
+ return buildAzureBlobRcloneMountConfig(entry, options.remoteName);
253
+ case 'box_mount':
254
+ return buildBoxRcloneMountConfig(entry, options.remoteName);
255
+ default:
256
+ throw new SandboxUnsupportedFeatureError('Unsupported rclone cloud bucket mount type.', {
257
+ mountType: entry.type,
258
+ });
259
+ }
260
+ }
261
+ async function resolveRcloneMountConfig(options, pattern, remoteName) {
262
+ const config = buildRcloneMountConfig(options.entry, {
263
+ remoteName,
264
+ });
265
+ const configFilePath = rclonePatternString(pattern, 'configFilePath');
266
+ if (!configFilePath) {
267
+ return config;
268
+ }
269
+ const result = await options.runCommand(`cat -- ${shellQuote(configFilePath)}`, {
270
+ timeoutMs: 30_000,
271
+ });
272
+ if (result.status !== 0) {
273
+ throw new SandboxMountError(`${options.providerName} failed to read rclone config file.`, {
274
+ provider: options.providerId,
275
+ mountType: config.mountType,
276
+ path: configFilePath,
277
+ stderr: result.stderr,
278
+ });
279
+ }
280
+ return {
281
+ ...config,
282
+ configText: supplementRcloneConfigText(result.stdout ?? '', remoteName, config.configText, config.mountType, options),
283
+ };
284
+ }
285
+ function supplementRcloneConfigText(configText, remoteName, requiredConfigText, mountType, options) {
286
+ const escapedRemote = remoteName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
287
+ const sectionPattern = new RegExp(`^\\s*\\[${escapedRemote}\\]\\s*$`, 'mu');
288
+ const match = sectionPattern.exec(configText);
289
+ if (!match) {
290
+ throw new SandboxMountError(`${options.providerName} rclone config file is missing the required remote section.`, {
291
+ provider: options.providerId,
292
+ mountType,
293
+ remoteName,
294
+ });
295
+ }
296
+ const sectionStart = match.index;
297
+ const sectionEnd = match.index + match[0].length;
298
+ const nextSection = /^\s*\[.+\]\s*$/mu.exec(configText.slice(sectionEnd));
299
+ const sectionBodyEnd = nextSection
300
+ ? sectionEnd + nextSection.index
301
+ : configText.length;
302
+ const before = configText.slice(0, sectionStart);
303
+ const sectionBody = configText.slice(sectionStart, sectionBodyEnd).trimEnd();
304
+ const after = configText.slice(sectionBodyEnd);
305
+ const requiredLines = requiredConfigText.trimEnd().split('\n').slice(1);
306
+ const supplement = requiredLines.length > 0 ? `\n${requiredLines.join('\n')}` : '';
307
+ return `${before}${sectionBody}${supplement}\n${after}`;
308
+ }
309
+ function buildGcsRcloneMountConfig(entry, remoteName) {
310
+ const accessId = readOptionalString(entry, 'accessId');
311
+ const secretAccessKey = readOptionalString(entry, 'secretAccessKey');
312
+ if (accessId && secretAccessKey) {
313
+ return {
314
+ remoteName,
315
+ remotePath: joinRemotePath(entry.bucket, entry.prefix),
316
+ configText: [
317
+ `[${remoteName}]`,
318
+ 'type = s3',
319
+ 'provider = GCS',
320
+ 'env_auth = false',
321
+ `access_key_id = ${accessId}`,
322
+ `secret_access_key = ${secretAccessKey}`,
323
+ `endpoint = ${entry.endpointUrl ?? 'https://storage.googleapis.com'}`,
324
+ ...(entry.region ? [`region = ${entry.region}`] : []),
325
+ '',
326
+ ].join('\n'),
327
+ readOnly: entry.readOnly ?? true,
328
+ mountType: entry.type,
329
+ };
330
+ }
331
+ return {
332
+ remoteName,
333
+ remotePath: joinRemotePath(entry.bucket, entry.prefix),
334
+ configText: [
335
+ `[${remoteName}]`,
336
+ 'type = google cloud storage',
337
+ ...(entry.serviceAccountFile
338
+ ? [`service_account_file = ${entry.serviceAccountFile}`]
339
+ : []),
340
+ ...(entry.serviceAccountCredentials
341
+ ? [`service_account_credentials = ${entry.serviceAccountCredentials}`]
342
+ : []),
343
+ ...(entry.accessToken ? [`access_token = ${entry.accessToken}`] : []),
344
+ entry.serviceAccountFile ||
345
+ entry.serviceAccountCredentials ||
346
+ entry.accessToken
347
+ ? 'env_auth = false'
348
+ : 'env_auth = true',
349
+ '',
350
+ ].join('\n'),
351
+ readOnly: entry.readOnly ?? true,
352
+ mountType: entry.type,
353
+ };
354
+ }
355
+ function buildAzureBlobRcloneMountConfig(entry, remoteName) {
356
+ const account = entry.account ?? entry.accountName;
357
+ if (!account) {
358
+ throw new SandboxMountError('Azure Blob mounts require account or accountName.', {
359
+ mountType: entry.type,
360
+ });
361
+ }
362
+ return {
363
+ remoteName,
364
+ remotePath: joinRemotePath(entry.container, entry.prefix),
365
+ configText: [
366
+ `[${remoteName}]`,
367
+ 'type = azureblob',
368
+ `account = ${account}`,
369
+ ...(azureBlobEndpoint(entry)
370
+ ? [`endpoint = ${azureBlobEndpoint(entry)}`]
371
+ : []),
372
+ ...(entry.accountKey
373
+ ? [`key = ${entry.accountKey}`]
374
+ : [
375
+ 'use_msi = true',
376
+ ...(entry.identityClientId
377
+ ? [`msi_client_id = ${entry.identityClientId}`]
378
+ : []),
379
+ ]),
380
+ '',
381
+ ].join('\n'),
382
+ readOnly: entry.readOnly ?? true,
383
+ mountType: entry.type,
384
+ };
385
+ }
386
+ function azureBlobEndpoint(entry) {
387
+ return entry.endpointUrl ?? entry.endpoint;
388
+ }
389
+ function buildBoxRcloneMountConfig(entry, remoteName) {
390
+ return {
391
+ remoteName,
392
+ remotePath: normalizeBoxRemotePath(entry.path),
393
+ configText: [
394
+ `[${remoteName}]`,
395
+ 'type = box',
396
+ ...boxCredentialLines(entry),
397
+ ...(entry.boxSubType && entry.boxSubType !== 'user'
398
+ ? [`box_sub_type = ${entry.boxSubType}`]
399
+ : []),
400
+ ...(entry.rootFolderId ? [`root_folder_id = ${entry.rootFolderId}`] : []),
401
+ ...(entry.impersonate ? [`impersonate = ${entry.impersonate}`] : []),
402
+ ...(entry.ownedBy ? [`owned_by = ${entry.ownedBy}`] : []),
403
+ '',
404
+ ].join('\n'),
405
+ readOnly: entry.readOnly ?? true,
406
+ mountType: entry.type,
407
+ };
408
+ }
409
+ function s3CredentialLines(entry) {
410
+ const accessKeyId = readOptionalString(entry, 'accessKeyId');
411
+ const secretAccessKey = readOptionalString(entry, 'secretAccessKey');
412
+ if (!accessKeyId || !secretAccessKey) {
413
+ return ['env_auth = true'];
414
+ }
415
+ return [
416
+ 'env_auth = false',
417
+ `access_key_id = ${accessKeyId}`,
418
+ `secret_access_key = ${secretAccessKey}`,
419
+ ...(entry.sessionToken ? [`session_token = ${entry.sessionToken}`] : []),
420
+ ];
421
+ }
422
+ function r2CredentialLines(entry) {
423
+ const accessKeyId = readOptionalString(entry, 'accessKeyId');
424
+ const secretAccessKey = readOptionalString(entry, 'secretAccessKey');
425
+ if (!accessKeyId || !secretAccessKey) {
426
+ return ['env_auth = true'];
427
+ }
428
+ return [
429
+ 'env_auth = false',
430
+ `access_key_id = ${accessKeyId}`,
431
+ `secret_access_key = ${secretAccessKey}`,
432
+ ];
433
+ }
434
+ function boxCredentialLines(entry) {
435
+ return [
436
+ ...(entry.clientId ? [`client_id = ${entry.clientId}`] : []),
437
+ ...(entry.clientSecret ? [`client_secret = ${entry.clientSecret}`] : []),
438
+ ...(entry.boxConfigFile
439
+ ? [`box_config_file = ${entry.boxConfigFile}`]
440
+ : []),
441
+ ...(entry.configCredentials
442
+ ? [`config_credentials = ${entry.configCredentials}`]
443
+ : []),
444
+ ...(entry.accessToken ? [`access_token = ${entry.accessToken}`] : []),
445
+ ...(entry.token ? [`token = ${entry.token}`] : []),
446
+ ];
447
+ }
448
+ async function ensureFuseSupport(options) {
449
+ await runRequiredMountCommand(options, {
450
+ label: 'check /dev/fuse',
451
+ command: 'test -c /dev/fuse',
452
+ timeoutMs: 30_000,
453
+ });
454
+ await runRequiredMountCommand(options, {
455
+ label: 'check fuse filesystem support',
456
+ command: 'grep -qw fuse /proc/filesystems',
457
+ timeoutMs: 30_000,
458
+ });
459
+ const fusermount = await options.runCommand('command -v fusermount3 >/dev/null 2>&1 || command -v fusermount >/dev/null 2>&1', { timeoutMs: 30_000 });
460
+ if (fusermount.status !== 0) {
461
+ await installPackage(options, 'fuse3', 'fusermount');
462
+ await runRequiredMountCommand(options, {
463
+ label: 'check fusermount after install',
464
+ command: 'command -v fusermount3 >/dev/null 2>&1 || command -v fusermount >/dev/null 2>&1',
465
+ timeoutMs: 30_000,
466
+ });
467
+ }
468
+ await runRequiredMountCommand(options, {
469
+ label: 'enable FUSE allow_other',
470
+ command: "chmod a+rw /dev/fuse && touch /etc/fuse.conf && (grep -qxF user_allow_other /etc/fuse.conf || printf '\\nuser_allow_other\\n' >> /etc/fuse.conf)",
471
+ timeoutMs: 30_000,
472
+ user: 'root',
473
+ });
474
+ }
475
+ async function ensureRclone(options) {
476
+ const check = await options.runCommand('command -v rclone >/dev/null 2>&1 || test -x /usr/local/bin/rclone', { timeoutMs: 30_000 });
477
+ if (check.status === 0) {
478
+ return;
479
+ }
480
+ if (options.installRcloneViaScript) {
481
+ await installRcloneViaScript(options);
482
+ }
483
+ else {
484
+ await installPackage(options, 'rclone', 'rclone');
485
+ }
486
+ await runRequiredMountCommand(options, {
487
+ label: 'check rclone after install',
488
+ command: 'command -v rclone >/dev/null 2>&1 || test -x /usr/local/bin/rclone',
489
+ timeoutMs: 30_000,
490
+ });
491
+ }
492
+ async function installRcloneViaScript(options) {
493
+ await runRequiredMountCommand(options, {
494
+ label: 'check apt-get for rclone install',
495
+ command: 'command -v apt-get >/dev/null 2>&1',
496
+ timeoutMs: 30_000,
497
+ });
498
+ await runRequiredMountCommand(options, {
499
+ label: 'install rclone prerequisites',
500
+ command: 'DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes apt-get -o Dpkg::Use-Pty=0 update -qq && DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes apt-get -o Dpkg::Use-Pty=0 install -y -qq curl unzip ca-certificates',
501
+ timeoutMs: 300_000,
502
+ user: 'root',
503
+ });
504
+ await runRequiredMountCommand(options, {
505
+ label: 'install rclone',
506
+ command: 'curl -fsSL https://rclone.org/install.sh | bash',
507
+ timeoutMs: 300_000,
508
+ user: 'root',
509
+ });
510
+ }
511
+ async function installPackage(options, packageName, label) {
512
+ const managers = options.packageManagers ?? DEFAULT_PACKAGE_MANAGERS;
513
+ if (managers.includes('apt')) {
514
+ const apt = await options.runCommand('command -v apt-get >/dev/null 2>&1', {
515
+ timeoutMs: 30_000,
516
+ });
517
+ if (apt.status === 0) {
518
+ await runRequiredMountCommand(options, {
519
+ label: `install ${label}`,
520
+ command: `apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ${shellQuote(packageName)}`,
521
+ timeoutMs: 300_000,
522
+ user: 'root',
523
+ });
524
+ return;
525
+ }
526
+ }
527
+ if (managers.includes('apk')) {
528
+ const apk = await options.runCommand('command -v apk >/dev/null 2>&1', {
529
+ timeoutMs: 30_000,
530
+ });
531
+ if (apk.status === 0) {
532
+ await runRequiredMountCommand(options, {
533
+ label: `install ${label}`,
534
+ command: `apk add --no-cache ${shellQuote(packageName)}`,
535
+ timeoutMs: 300_000,
536
+ user: 'root',
537
+ });
538
+ return;
539
+ }
540
+ }
541
+ throw new SandboxMountError(`${options.providerName} cloud bucket mounts require ${label}, but no supported package manager is available.`, {
542
+ provider: options.providerId,
543
+ package: packageName,
544
+ });
545
+ }
546
+ async function defaultUserIds(runCommand) {
547
+ const result = await runCommand('id -u; id -g', { timeoutMs: 30_000 });
548
+ if (result.status !== 0) {
549
+ return undefined;
550
+ }
551
+ const [uid, gid] = (result.stdout ?? '').trim().split(/\r?\n/u);
552
+ if (!uid || !gid || !/^\d+$/u.test(uid) || !/^\d+$/u.test(gid)) {
553
+ return undefined;
554
+ }
555
+ return { uid, gid };
556
+ }
557
+ async function runRequiredMountCommand(options, command) {
558
+ const result = await options.runCommand(command.command, {
559
+ timeoutMs: command.timeoutMs,
560
+ user: command.user,
561
+ });
562
+ if (result.status !== 0) {
563
+ throw new SandboxMountError(`${options.providerName} cloud bucket mount failed while trying to ${command.label}.`, {
564
+ provider: options.providerId,
565
+ status: result.status,
566
+ stderr: result.stderr,
567
+ stdout: result.stdout,
568
+ });
569
+ }
570
+ return result;
571
+ }
572
+ function joinShellArgs(args) {
573
+ return args.map(shellQuote).join(' ');
574
+ }
575
+ function joinRemotePath(base, prefix) {
576
+ const normalizedPrefix = prefix?.replace(/^\/+|\/+$/gu, '');
577
+ return normalizedPrefix ? `${base}/${normalizedPrefix}` : base;
578
+ }
579
+ function resolveRcloneRemoteName(pattern, options) {
580
+ const remoteName = rclonePatternString(pattern, 'remoteName') ??
581
+ rclonePatternString(pattern, 'remote');
582
+ if (typeof remoteName !== 'undefined') {
583
+ if (typeof remoteName !== 'string' ||
584
+ !/^[A-Za-z0-9_-]+$/u.test(remoteName)) {
585
+ throw new SandboxMountError(`${options.providerName} cloud bucket mounts require remoteName to contain only letters, numbers, underscores, and hyphens.`, {
586
+ provider: options.providerId,
587
+ remoteName,
588
+ });
589
+ }
590
+ return remoteName;
591
+ }
592
+ return `sandbox_${sanitizeRemoteName(options.providerId)}_${sanitizeRemoteName(options.mountPath)}`;
593
+ }
594
+ async function stopRcloneNfsServer(options, pidPath, configPath) {
595
+ await options
596
+ .runCommand([
597
+ safePidFileKillFunctionCommand(),
598
+ `if [ -f ${shellQuote(pidPath)} ]; then ${safeKillPidFileCommand({
599
+ pidFile: shellQuote(pidPath),
600
+ expectedCmdlineFragments: ['rclone', 'serve', 'nfs'],
601
+ })}; fi`,
602
+ `rm -f -- ${shellQuote(pidPath)} ${shellQuote(configPath)}`,
603
+ ].join(' ; '), { timeoutMs: 30_000 })
604
+ .catch(() => { });
605
+ }
606
+ function splitNfsAddr(addr) {
607
+ if (addr === '::') {
608
+ return { host: '127.0.0.1', port: '2049' };
609
+ }
610
+ const index = addr.lastIndexOf(':');
611
+ const rawHost = index === -1 ? addr : addr.slice(0, index);
612
+ const port = index === -1 ? '2049' : addr.slice(index + 1) || '2049';
613
+ const host = rawHost === '0.0.0.0' || rawHost === '::' ? '127.0.0.1' : rawHost;
614
+ return { host: host || '127.0.0.1', port };
615
+ }
616
+ function rclonePatternString(pattern, key) {
617
+ const value = pattern[key];
618
+ return typeof value === 'string' && value ? value : undefined;
619
+ }
620
+ function rclonePatternStringArray(pattern, key) {
621
+ const value = pattern[key];
622
+ return Array.isArray(value) &&
623
+ value.every((item) => typeof item === 'string')
624
+ ? value
625
+ : undefined;
626
+ }
627
+ function rclonePatternArgs(pattern) {
628
+ return [
629
+ ...(rclonePatternStringArray(pattern, 'args') ?? []),
630
+ ...(rclonePatternStringArray(pattern, 'extraArgs') ?? []),
631
+ ];
632
+ }
633
+ function isRcloneBucketMount(entry) {
634
+ return (entry.type === 's3_mount' ||
635
+ entry.type === 'r2_mount' ||
636
+ entry.type === 'gcs_mount' ||
637
+ entry.type === 'azure_blob_mount' ||
638
+ entry.type === 'box_mount');
639
+ }
640
+ function isRcloneMountPattern(value) {
641
+ return (typeof value === 'object' &&
642
+ value !== null &&
643
+ value.type === 'rclone');
644
+ }
645
+ function validateCredentialPair(args) {
646
+ if (Boolean(args.accessKeyId) !== Boolean(args.secretAccessKey)) {
647
+ throw new SandboxMountError(`${args.provider} cloud bucket mounts require both accessKeyId and secretAccessKey when either is provided.`, {
648
+ mountType: args.mountType,
649
+ });
650
+ }
651
+ }
652
+ function mountStrategyType(entry) {
653
+ return entry.mountStrategy?.type;
654
+ }
655
+ function sanitizeRemoteName(value) {
656
+ const safe = value.replace(/[^A-Za-z0-9_-]+/gu, '_').replace(/^_+|_+$/gu, '');
657
+ return safe || 'mount';
658
+ }
659
+ function normalizeBoxRemotePath(path) {
660
+ return path?.replace(/^\/+/gu, '') ?? '';
661
+ }
662
+ //# sourceMappingURL=inContainerMounts.mjs.map