-
-
Notifications
You must be signed in to change notification settings - Fork 8
Vercel Sandbox Integration #640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
58859ab
d41ece3
ecbd4c3
8fc5dda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| 'use client' | ||
|
|
||
| import { useEffect, useRef } from 'react' | ||
| import { cn } from '@/lib/utils' | ||
|
|
||
| interface LogEntry { | ||
| type: 'stdout' | 'stderr' | ||
| content: string | ||
| } | ||
|
|
||
| interface SandboxLogsProps { | ||
| logs: LogEntry[] | ||
| isExecuting?: boolean | ||
| } | ||
|
|
||
| export function SandboxLogs({ logs, isExecuting }: SandboxLogsProps) { | ||
| const scrollRef = useRef<HTMLDivElement>(null) | ||
|
|
||
| useEffect(() => { | ||
| if (scrollRef.current) { | ||
| scrollRef.current.scrollTop = scrollRef.current.scrollHeight | ||
| } | ||
| }, [logs]) | ||
|
|
||
| return ( | ||
| <div | ||
| ref={scrollRef} | ||
| className="bg-zinc-950 text-zinc-50 font-mono text-sm p-4 rounded-md overflow-auto max-h-[300px] border border-zinc-800" | ||
| > | ||
| {logs.map((log, index) => ( | ||
| <div | ||
| key={index} | ||
| className={cn( | ||
| "whitespace-pre-wrap mb-1", | ||
| log.type === 'stderr' ? "text-red-400" : "text-zinc-300" | ||
| )} | ||
| > | ||
| {log.content} | ||
| </div> | ||
| ))} | ||
| {isExecuting && ( | ||
| <div className="flex items-center gap-2 text-zinc-500 mt-2"> | ||
| <span className="w-1.5 h-1.5 bg-zinc-500 rounded-full animate-pulse" /> | ||
| <span>Executing...</span> | ||
| </div> | ||
| )} | ||
| {logs.length === 0 && !isExecuting && ( | ||
| <div className="text-zinc-500 italic">No output</div> | ||
| )} | ||
| </div> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| 'use client' | ||
|
|
||
| import { useState } from 'react' | ||
| import { ExternalLink, Loader2, RefreshCw } from 'lucide-react' | ||
| import { Button } from '@/components/ui/button' | ||
|
|
||
| interface SandboxPreviewProps { | ||
| url: string | ||
| } | ||
|
|
||
| export function SandboxPreview({ url }: SandboxPreviewProps) { | ||
| const [isLoading, setIsLoading] = useState(true) | ||
| const [key, setKey] = useState(0) | ||
|
|
||
| const reload = () => { | ||
| setIsLoading(true) | ||
| setKey(prev => prev + 1) | ||
| } | ||
|
|
||
| return ( | ||
| <div className="flex flex-col border rounded-md overflow-hidden bg-background"> | ||
| <div className="flex items-center justify-between px-4 py-2 border-b bg-muted/50"> | ||
| <div className="text-xs font-mono truncate mr-4">{url}</div> | ||
| <div className="flex items-center gap-1"> | ||
| <Button variant="ghost" size="icon" className="h-7 w-7" onClick={reload}> | ||
| <RefreshCw className="h-3.5 w-3.5" /> | ||
| </Button> | ||
| <Button variant="ghost" size="icon" className="h-7 w-7" asChild> | ||
| <a href={url} target="_blank" rel="noopener noreferrer"> | ||
| <ExternalLink className="h-3.5 w-3.5" /> | ||
| </a> | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| <div className="relative w-full aspect-video bg-white"> | ||
| {isLoading && ( | ||
| <div className="absolute inset-0 flex items-center justify-center bg-muted/20"> | ||
| <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /> | ||
| </div> | ||
| )} | ||
| <iframe | ||
| key={key} | ||
| src={url} | ||
| className="w-full h-full border-none" | ||
| sandbox="allow-scripts allow-same-origin allow-forms" | ||
| onLoad={() => setIsLoading(false)} | ||
| /> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| 'use client' | ||
|
|
||
| import { useStreamableValue, StreamableValue } from 'ai/rsc' | ||
| import { Section } from '@/components/section' | ||
| import { SandboxLogs } from './sandbox-logs' | ||
| import { SandboxPreview } from './sandbox-preview' | ||
| import { Terminal } from 'lucide-react' | ||
|
|
||
| interface LogEntry { | ||
| type: 'stdout' | 'stderr' | ||
| content: string | ||
| } | ||
|
|
||
| interface SandboxSectionProps { | ||
| logs: StreamableValue<LogEntry[]> | ||
| previewUrl?: string | ||
| exitCode?: number | ||
| error?: string | ||
| } | ||
|
|
||
| export function SandboxSection({ logs, previewUrl, exitCode, error }: SandboxSectionProps) { | ||
| const [data, errorFromStream] = useStreamableValue(logs) | ||
| const isExecuting = data === undefined | ||
|
|
||
| return ( | ||
| <Section title="Sandbox" isCollapsed={false}> | ||
| <div className="space-y-4"> | ||
| <SandboxLogs logs={data || []} isExecuting={isExecuting} /> | ||
|
|
||
| {previewUrl && ( | ||
| <div className="mt-4"> | ||
| <h4 className="text-xs font-semibold uppercase text-muted-foreground mb-2 px-1">Live Preview</h4> | ||
| <SandboxPreview url={previewUrl} /> | ||
| </div> | ||
| )} | ||
|
|
||
| {(error || !!errorFromStream) && ( | ||
| <div className="p-3 text-xs bg-red-50 text-red-600 rounded border border-red-100 font-mono"> | ||
| <strong>Error:</strong> {error || (errorFromStream as any)?.message || 'Execution failed'} | ||
| </div> | ||
| )} | ||
|
|
||
| {exitCode !== undefined && !previewUrl && ( | ||
| <div className="text-[10px] text-muted-foreground font-mono px-1"> | ||
| Process exited with code {exitCode} | ||
| </div> | ||
| )} | ||
| </div> | ||
| </Section> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,7 +47,13 @@ Use these user-drawn areas/lines as primary areas of interest for your analysis | |
| ONLY when the user explicitly provides one or more URLs and asks you to read, summarize, or extract content from them. | ||
| - **Never use** this tool proactively. | ||
|
|
||
| #### **3. Location, Geography, Navigation, and Mapping Queries** | ||
| #### **3. Code Execution and Data Transformation** | ||
| - **Tool**: \`sandbox\` | ||
| - **When to use**: | ||
| Use this to execute JavaScript or TypeScript code snippets, perform complex data transformations, generate dynamic HTML reports, or visualize data. The sandbox provides an isolated environment with network access and can host web servers for live previews. | ||
| - **Features**: Supports installing npm dependencies and provides a public preview URL if a web server (e.g., Express) is detected. | ||
|
|
||
|
Comment on lines
+50
to
+55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider documenting network access security implications. The prompt states the sandbox provides "network access," which enables powerful data-fetching use cases but also introduces SSRF (Server-Side Request Forgery) risk. While this is inherent to the feature's design, consider documenting the security implications:
If the sandbox runs in a controlled environment (e.g., isolated network namespace, egress filtering), mention this in documentation. Otherwise, ensure users understand the security boundaries. 🤖 Prompt for AI Agents |
||
| #### **4. Location, Geography, Navigation, and Mapping Queries** | ||
| - **Tool**: \`geospatialQueryTool\` → **MUST be used (no exceptions)** for: | ||
| • Finding places, businesses, "near me", distances, directions | ||
| • Travel times, routes, traffic, map generation | ||
|
|
@@ -68,9 +74,10 @@ Use these user-drawn areas/lines as primary areas of interest for your analysis | |
|
|
||
| #### **Summary of Decision Flow** | ||
| 1. User gave explicit URLs? → \`retrieve\` | ||
| 2. Location/distance/direction/maps? → \`geospatialQueryTool\` (mandatory) | ||
| 3. Everything else needing external data? → \`search\` | ||
| 4. Otherwise → answer from knowledge | ||
| 2. Need to execute code or transform data? → \`sandbox\` | ||
| 3. Location/distance/direction/maps? → \`geospatialQueryTool\` (mandatory) | ||
| 4. Everything else needing external data? → \`search\` | ||
| 5. Otherwise → answer from knowledge | ||
|
|
||
| These rules override all previous instructions. | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add accessible labels to preview controls and iframe.
Line 25 and Line 28 render icon-only actions without accessible names, and Line 41 renders an unlabeled iframe. This makes the preview controls/frame hard to use with assistive tech.
Suggested fix
Also applies to: 41-47
🤖 Prompt for AI Agents