MCPcopy
hub / github.com/TanStack/ai / buildSandbox

Function buildSandbox

examples/ts-react-chat/src/sandbox-triage.ts:349–423  ·  view source on GitHub ↗
(opts: {
  harness: HarnessName
  provider: ProviderName
  repo: string
  threadId: string
  /** Keep the sandbox alive after the run instead of destroying it (default: destroy). */
  keepAlive?: boolean
  /** Local Claude Code only: use the host's subscription login instead of an API key. */
  useSubscription?: boolean
})

Source from the content-addressed store, hash-verified

347
348/** One sandbox per (harness, provider, thread): switching a picker → fresh sandbox. */
349export function buildSandbox(opts: {
350 harness: HarnessName
351 provider: ProviderName
352 repo: string
353 threadId: string
354 /** Keep the sandbox alive after the run instead of destroying it (default: destroy). */
355 keepAlive?: boolean
356 /** Local Claude Code only: use the host's subscription login instead of an API key. */
357 useSubscription?: boolean
358}): SandboxDefinition {
359 const harness = HARNESSES[opts.harness]
360 const subscription = usesSubscription(
361 opts.harness,
362 opts.provider,
363 opts.useSubscription,
364 )
365
366 // Subscription mode scrubs ANTHROPIC_API_KEY from the host claude's env (via the
367 // provider's `scrubEnv` flag) so it falls back to the logged-in subscription.
368 // Ports the in-sandbox CLI needs reachable from the host (e.g. opencode's serve port).
369 const ports = harness.exposePort !== undefined ? [harness.exposePort] : []
370 const provider = subscription
371 ? localProcessSandbox({ scrubEnv: ['ANTHROPIC_API_KEY'] })
372 : PROVIDERS[opts.provider].make(ports)
373
374 // Inject auth secrets only for sandboxed providers — local-process inherits the
375 // host's own env (API key, or a `claude login`/`codex login`), so nothing to inject.
376 const secretEnv: Record<string, string> = {}
377 if (opts.provider !== 'local') {
378 // Harness auth: a custom mapping (e.g. codex → CODEX_API_KEY) if provided,
379 // otherwise inject whichever of its requiredEnv vars are set.
380 if (harness.sandboxSecrets) {
381 Object.assign(secretEnv, harness.sandboxSecrets())
382 } else {
383 for (const key of harness.requiredEnv) {
384 const value = process.env[key]
385 if (value) secretEnv[key] = value
386 }
387 }
388 // Provider auth (e.g. DAYTONA_API_KEY) — used host-side, harmless in-sandbox.
389 for (const key of PROVIDERS[opts.provider].requiredEnv) {
390 const value = process.env[key]
391 if (value) secretEnv[key] = value
392 }
393 }
394 return defineSandbox({
395 id: `triage-${opts.harness}-${opts.provider}-${opts.threadId}`,
396 provider,
397 workspace: defineWorkspace({
398 source: githubRepo({ repo: opts.repo }),
399 setup: ({ serial }) => {
400 // Install the harness CLI into the fresh sandbox for every provider
401 // EXCEPT local-process, which uses the host's CLI already on PATH.
402 // Remote/container images (docker/vercel/daytona) don't ship it.
403 if (opts.provider !== 'local' && harness.installCommand) {
404 // Some images (e.g. Daytona) run as a non-root user with a root-owned
405 // global npm dir → `npm install -g` fails EACCES. Fall back to
406 // passwordless sudo, preserving PATH so nvm's npm/node still resolve.

Callers 1

triagePostFunction · 0.50

Calls 6

localProcessSandboxFunction · 0.90
defineSandboxFunction · 0.90
defineWorkspaceFunction · 0.90
githubRepoFunction · 0.90
createSecretsFunction · 0.90
usesSubscriptionFunction · 0.85

Tested by

no test coverage detected