Public: anyone can read this; the crew can change it.
Claude/add agent function 011 cu ysw u rf laqy4u srp vyn p #22
Check · Review · Land · Live — Its check is red.
claude/add-agent-function-011CUYswURfLAQY4uSrpVynP → main 11 commits
A guest
This pull comes from a guest: its preview runs without the app's variables or a forge token until a person who can write here approves it.
This pull cannot land as it is, whatever its review says, because it changes guarded paths: package.json (release machinery).
4 files
changed +3994
−2315
package-lock.json +3600 −2314
Diff not shown — 5,914 changed lines (limit 5,000). +3600 −2314
View filepackage.json +9
| @@ -8,7 +8,13 @@ | ||
| 8 | 8 | "start": "next start", |
| 9 | 9 | "lint": "next lint" |
| 10 | 10 | }, |
| 11 | "overrides": { | |
| 12 | "zod": "^4.1.12" | |
| 13 | }, | |
| 11 | 14 | "dependencies": { |
| 15 | "@ai-sdk/anthropic": "^3.0.0-beta.43", | |
| 16 | "@ai-sdk/openai": "^3.0.0-beta.42", | |
| 17 | "@ai-sdk/react": "^3.0.0-beta.81", | |
| 12 | 18 | "@calcom/embed-react": "^1.5.0", |
| 13 | 19 | "@radix-ui/react-avatar": "^1.1.0", |
| 14 | 20 | "@radix-ui/react-dialog": "^1.1.1", |
| @@ -22,10 +28,13 @@ | ||
| 22 | 28 | "@react-three/fiber": "^8.17.7", |
| 23 | 29 | "@supabase/ssr": "^0.5.1", |
| 24 | 30 | "@uiw/react-textarea-code-editor": "^3.0.2", |
| 31 | "@valibot/to-json-schema": "^1.3.0", | |
| 25 | 32 | "@vercel/analytics": "^1.5.0", |
| 26 | 33 | "@vercel/sandbox": "^1.0.2", |
| 34 | "ai": "^6.0.0-beta.81", | |
| 27 | 35 | "class-variance-authority": "^0.7.0", |
| 28 | 36 | "clsx": "^2.1.1", |
| 37 | "effect": "^3.18.4", | |
| 29 | 38 | "framer-motion": "^11.5.4", |
| 30 | 39 | "lucide-react": "^0.439.0", |
| 31 | 40 | "next": "14.2.9", |
src/app/api/agent/code-assistant/route.ts added +154
| @@ -0,0 +1,154 @@ | ||
| 1 | import { anthropic } from '@ai-sdk/anthropic'; | |
| 2 | import { ToolLoopAgent } from 'ai'; | |
| 3 | import { createAgentUIStreamResponse } from 'ai'; | |
| 4 | import { NextRequest } from 'next/server'; | |
| 5 | import { z } from 'zod'; | |
| 6 | import { createClient } from '@/utils/supabase/server'; | |
| 7 | ||
| 8 | export const runtime = 'edge'; | |
| 9 | export const maxDuration = 60; | |
| 10 | ||
| 11 | // Create the code assistant agent | |
| 12 | const codeAssistantAgent = new ToolLoopAgent({ | |
| 13 | model: anthropic('claude-3-5-sonnet-20241022'), | |
| 14 | instructions: `You are a code mentor helping developers write clean, practical utility functions. | |
| 15 | ||
| 16 | ## Your Role | |
| 17 | ||
| 18 | Help users iteratively improve their TypeScript or Python functions through: | |
| 19 | - Clear explanations of what their code does | |
| 20 | - Specific, actionable suggestions for improvement | |
| 21 | - Teaching programming concepts and patterns | |
| 22 | - Writing clean, minimal code that solves real problems | |
| 23 | ||
| 24 | ## Context | |
| 25 | ||
| 26 | Users are working in an isolated sandbox environment: | |
| 27 | - TypeScript runs on Node.js 22 with tsx | |
| 28 | - Python runs on Python 3.13 | |
| 29 | - Functions should be self-contained and executable | |
| 30 | - Output results with console.log() or print() | |
| 31 | ||
| 32 | ## How to Help | |
| 33 | ||
| 34 | **When explaining code:** | |
| 35 | - Break down what each part does | |
| 36 | - Point out patterns (both good and problematic) | |
| 37 | - Explain *why* something works or doesn't work | |
| 38 | - Use simple language, avoid jargon unless teaching a concept | |
| 39 | ||
| 40 | **When suggesting improvements:** | |
| 41 | - Use the updateCode tool to apply changes directly to the editor | |
| 42 | - Explain why the change is better (performance, readability, correctness) | |
| 43 | - One focused improvement at a time unless user asks for multiple changes | |
| 44 | - Always provide complete, runnable code | |
| 45 | ||
| 46 | **When writing new code:** | |
| 47 | - Ask clarifying questions if requirements are vague | |
| 48 | - Write minimal, focused functions | |
| 49 | - Include type annotations and error handling | |
| 50 | - Add brief comments only for complex logic | |
| 51 | - Make it immediately executable with example usage | |
| 52 | - Use updateCode to write it to the editor | |
| 53 | ||
| 54 | ## Response Style | |
| 55 | ||
| 56 | - Be concise and direct | |
| 57 | - Lead with the most important information | |
| 58 | - Explain changes before or after applying them | |
| 59 | - Teach concepts through examples | |
| 60 | - Focus on practical utility over theory | |
| 61 | ||
| 62 | ## Code Quality Principles | |
| 63 | ||
| 64 | 1. **Clarity** - Code should be self-explanatory | |
| 65 | 2. **Minimal** - No unnecessary complexity | |
| 66 | 3. **Robust** - Handle errors gracefully | |
| 67 | 4. **Typed** - Use type hints/annotations | |
| 68 | 5. **Tested** - Include example usage that shows it works | |
| 69 | ||
| 70 | ## Important | |
| 71 | ||
| 72 | Always use the updateCode tool when you want to change the code in the editor. | |
| 73 | Explain what you're changing and why, then use the tool to apply it.`, | |
| 74 | tools: { | |
| 75 | updateCode: { | |
| 76 | description: 'Updates the code in the user\'s editor. Use this whenever you want to modify, improve, or replace the current function. Always explain what you\'re changing before or after calling this tool.', | |
| 77 | inputSchema: z.object({ | |
| 78 | newCode: z.string().describe('The complete updated code to replace what\'s currently in the editor'), | |
| 79 | reason: z.string().describe('Brief explanation of what changed and why (e.g., "Added error handling for async operations")'), | |
| 80 | }), | |
| 81 | execute: async ({ newCode, reason }: { newCode: string; reason: string }) => { | |
| 82 | // Return the new code so it can be applied by the client | |
| 83 | return { | |
| 84 | success: true, | |
| 85 | updatedCode: newCode, | |
| 86 | reason: reason, | |
| 87 | }; | |
| 88 | }, | |
| 89 | }, | |
| 90 | }, | |
| 91 | }); | |
| 92 | ||
| 93 | export async function POST(request: NextRequest) { | |
| 94 | try { | |
| 95 | // Check authentication | |
| 96 | const supabase = await createClient(); | |
| 97 | const { data: { user }, error: authError } = await supabase.auth.getUser(); | |
| 98 | ||
| 99 | if (authError || !user) { | |
| 100 | return new Response( | |
| 101 | JSON.stringify({ error: 'Authentication required' }), | |
| 102 | { status: 401, headers: { 'Content-Type': 'application/json' } } | |
| 103 | ); | |
| 104 | } | |
| 105 | ||
| 106 | const { messages, currentCode, language } = await request.json(); | |
| 107 | ||
| 108 | if (!messages || !Array.isArray(messages)) { | |
| 109 | return new Response( | |
| 110 | JSON.stringify({ error: 'Messages array is required' }), | |
| 111 | { status: 400, headers: { 'Content-Type': 'application/json' } } | |
| 112 | ); | |
| 113 | } | |
| 114 | ||
| 115 | // Inject current code context into messages | |
| 116 | const messagesWithContext = [...messages]; | |
| 117 | ||
| 118 | if (currentCode && language) { | |
| 119 | // Add context as a user message with unique ID | |
| 120 | const contextMessage = { | |
| 121 | id: `context-${Date.now()}`, | |
| 122 | role: 'user' as const, | |
| 123 | parts: [ | |
| 124 | { | |
| 125 | type: 'text' as const, | |
| 126 | text: `[Current code in editor - ${language}]\n\`\`\`${language}\n${currentCode}\n\`\`\`\n\nWhen you suggest code changes, always provide the complete updated function in a code block.`, | |
| 127 | }, | |
| 128 | ], | |
| 129 | }; | |
| 130 | ||
| 131 | // Insert context before the last user message | |
| 132 | if (messagesWithContext.length > 0) { | |
| 133 | messagesWithContext.splice(messagesWithContext.length - 1, 0, contextMessage); | |
| 134 | } else { | |
| 135 | messagesWithContext.push(contextMessage); | |
| 136 | } | |
| 137 | } | |
| 138 | ||
| 139 | // Create streaming response with the agent | |
| 140 | return createAgentUIStreamResponse({ | |
| 141 | agent: codeAssistantAgent, | |
| 142 | messages: messagesWithContext, | |
| 143 | }); | |
| 144 | } catch (error) { | |
| 145 | console.error('Agent error:', error); | |
| 146 | return new Response( | |
| 147 | JSON.stringify({ | |
| 148 | error: 'Failed to process request', | |
| 149 | details: error instanceof Error ? error.message : 'Unknown error' | |
| 150 | }), | |
| 151 | { status: 500, headers: { 'Content-Type': 'application/json' } } | |
| 152 | ); | |
| 153 | } | |
| 154 | } |
src/components/playground/FunctionCreator.tsx +231 −1
| @@ -4,8 +4,10 @@ import React, { useState } from 'react' | ||
| 4 | 4 | import dynamic from 'next/dynamic' |
| 5 | 5 | import { Card } from "@/components/ui/card" |
| 6 | 6 | import { Button } from "@/components/ui/button" |
| 7 | import { PlayIcon, ClockIcon, CodeIcon, SettingsIcon, XIcon } from 'lucide-react' | |
| 7 | import { PlayIcon, ClockIcon, CodeIcon, SettingsIcon, XIcon, BotIcon, SendIcon } from 'lucide-react' | |
| 8 | 8 | import { toast } from '@/hooks/use-toast' |
| 9 | import { useChat } from '@ai-sdk/react' | |
| 10 | import { DefaultChatTransport } from 'ai' | |
| 9 | 11 | |
| 10 | 12 | const DynamicCodeEditor = dynamic( |
| 11 | 13 | () => import('@uiw/react-textarea-code-editor').then((mod) => mod.default), |
| @@ -82,6 +84,28 @@ export const FunctionCreator: React.FC = () => { | ||
| 82 | 84 | const [showScheduler, setShowScheduler] = useState(false); |
| 83 | 85 | const [showEnvVars, setShowEnvVars] = useState(false); |
| 84 | 86 | const [envVars, setEnvVars] = useState<EnvVar[]>([{ key: '', value: '' }]); |
| 87 | const [showAgent, setShowAgent] = useState(false); | |
| 88 | const [agentInput, setAgentInput] = useState(''); | |
| 89 | ||
| 90 | // AI Agent chat hook | |
| 91 | const { messages, sendMessage, status } = useChat({ | |
| 92 | transport: new DefaultChatTransport({ | |
| 93 | api: '/api/agent/code-assistant', | |
| 94 | body: () => ({ | |
| 95 | currentCode: code, | |
| 96 | language: language, | |
| 97 | }), | |
| 98 | }), | |
| 99 | onError: (error) => { | |
| 100 | toast({ | |
| 101 | title: "Agent Error", | |
| 102 | description: error.message || "Failed to communicate with the AI agent", | |
| 103 | variant: "destructive", | |
| 104 | }); | |
| 105 | }, | |
| 106 | }); | |
| 107 | ||
| 108 | const isAgentLoading = status === 'submitted' || status === 'streaming'; | |
| 85 | 109 | |
| 86 | 110 | const handleLanguageChange = (newLanguage: Language) => { |
| 87 | 111 | setLanguage(newLanguage); |
| @@ -235,6 +259,15 @@ export const FunctionCreator: React.FC = () => { | ||
| 235 | 259 | <PlayIcon className="w-4 h-4 mr-2" /> |
| 236 | 260 | {isExecuting ? 'Running...' : 'Run'} |
| 237 | 261 | </Button> |
| 262 | <Button | |
| 263 | onClick={() => setShowAgent(!showAgent)} | |
| 264 | variant={showAgent ? 'default' : 'outline'} | |
| 265 | className="flex-1 sm:flex-initial transition-all duration-200" | |
| 266 | size="sm" | |
| 267 | > | |
| 268 | <BotIcon className="w-4 h-4 mr-2" /> | |
| 269 | AI Agent | |
| 270 | </Button> | |
| 238 | 271 | <Button |
| 239 | 272 | onClick={() => setShowEnvVars(!showEnvVars)} |
| 240 | 273 | variant="outline" |
| @@ -256,6 +289,203 @@ export const FunctionCreator: React.FC = () => { | ||
| 256 | 289 | </div> |
| 257 | 290 | </div> |
| 258 | 291 | |
| 292 | {/* AI Agent Chat Panel */} | |
| 293 | {showAgent && ( | |
| 294 | <Card className="bg-black/50 backdrop-blur-md border-purple-500 p-4 animate-in slide-in-from-top-2 duration-200"> | |
| 295 | <div className="space-y-3"> | |
| 296 | <div className="flex items-center justify-between"> | |
| 297 | <h3 className="text-lg font-semibold text-white flex items-center gap-2"> | |
| 298 | <BotIcon className="w-5 h-5 text-purple-400" /> | |
| 299 | AI Code Assistant | |
| 300 | </h3> | |
| 301 | <Button | |
| 302 | onClick={() => setShowAgent(false)} | |
| 303 | variant="ghost" | |
| 304 | size="sm" | |
| 305 | className="h-8 w-8 p-0" | |
| 306 | > | |
| 307 | <XIcon className="w-4 h-4" /> | |
| 308 | </Button> | |
| 309 | </div> | |
| 310 | ||
| 311 | {/* Chat Messages */} | |
| 312 | <div className="bg-gray-900/50 rounded-lg p-3 max-h-80 overflow-y-auto space-y-3"> | |
| 313 | {messages.length === 0 ? ( | |
| 314 | <div className="text-center text-gray-400 py-8"> | |
| 315 | <BotIcon className="w-12 h-12 mx-auto mb-3 text-purple-400 opacity-50" /> | |
| 316 | <p className="text-sm">I'm your code mentor!</p> | |
| 317 | <p className="text-xs mt-2">Ask me to explain, improve, or write new functions.</p> | |
| 318 | </div> | |
| 319 | ) : ( | |
| 320 | messages.map((message) => { | |
| 321 | // Debug: Log entire message structure | |
| 322 | if (message.role === 'assistant') { | |
| 323 | console.log('=== ASSISTANT MESSAGE ==='); | |
| 324 | console.log('Message keys:', Object.keys(message)); | |
| 325 | console.log('Full message:', message); | |
| 326 | console.log('Parts:', message.parts); | |
| 327 | } | |
| 328 | ||
| 329 | const textContent = message.parts | |
| 330 | .filter((part: any) => part.type === 'text') | |
| 331 | .map((part: any) => part.text) | |
| 332 | .join(''); | |
| 333 | ||
| 334 | // Check for updateCode tool results in multiple possible locations | |
| 335 | let codeUpdate: any = null; | |
| 336 | ||
| 337 | // Check in parts array | |
| 338 | message.parts.forEach((part: any, idx: number) => { | |
| 339 | console.log(`Part ${idx}:`, part.type, part); | |
| 340 | ||
| 341 | if (part.type === 'tool-result') { | |
| 342 | console.log('Found tool-result part:', part); | |
| 343 | if (part.result?.updatedCode && part.result?.success) { | |
| 344 | codeUpdate = part; | |
| 345 | } | |
| 346 | } | |
| 347 | }); | |
| 348 | ||
| 349 | // Also check if there's a toolInvocations or toolResults property | |
| 350 | if ((message as any).toolInvocations) { | |
| 351 | console.log('Found toolInvocations:', (message as any).toolInvocations); | |
| 352 | } | |
| 353 | if ((message as any).toolResults) { | |
| 354 | console.log('Found toolResults:', (message as any).toolResults); | |
| 355 | } | |
| 356 | ||
| 357 | return ( | |
| 358 | <div key={message.id} className="space-y-2"> | |
| 359 | <div | |
| 360 | className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`} | |
| 361 | > | |
| 362 | <div | |
| 363 | className={`max-w-[80%] rounded-lg px-4 py-2 ${ | |
| 364 | message.role === 'user' | |
| 365 | ? 'bg-blue-600 text-white' | |
| 366 | : 'bg-gray-800 text-gray-100' | |
| 367 | }`} | |
| 368 | > | |
| 369 | <p className="text-sm whitespace-pre-wrap">{textContent}</p> | |
| 370 | </div> | |
| 371 | </div> | |
| 372 | ||
| 373 | {/* Show Apply button if agent updated code */} | |
| 374 | {codeUpdate && ( | |
| 375 | <div className="flex justify-start"> | |
| 376 | <div className="bg-green-900/30 border border-green-500/50 rounded-lg px-4 py-2 flex items-center gap-3"> | |
| 377 | <div className="flex-1"> | |
| 378 | <p className="text-xs text-green-400 font-medium">Code Updated</p> | |
| 379 | <p className="text-xs text-gray-300">{codeUpdate.result.reason}</p> | |
| 380 | </div> | |
| 381 | <Button | |
| 382 | size="sm" | |
| 383 | className="bg-green-600 hover:bg-green-700 text-white" | |
| 384 | onClick={() => { | |
| 385 | setCode(codeUpdate.result.updatedCode); | |
| 386 | toast({ | |
| 387 | title: "Code Applied", | |
| 388 | description: "The updated code has been applied to the editor", | |
| 389 | }); | |
| 390 | }} | |
| 391 | > | |
| 392 | Apply | |
| 393 | </Button> | |
| 394 | </div> | |
| 395 | </div> | |
| 396 | )} | |
| 397 | </div> | |
| 398 | ); | |
| 399 | }) | |
| 400 | )} | |
| 401 | {isAgentLoading && ( | |
| 402 | <div className="flex justify-start"> | |
| 403 | <div className="bg-gray-800 text-gray-100 rounded-lg px-4 py-2"> | |
| 404 | <div className="flex items-center gap-2"> | |
| 405 | <div className="w-2 h-2 bg-purple-400 rounded-full animate-pulse" /> | |
| 406 | <p className="text-sm">Thinking...</p> | |
| 407 | </div> | |
| 408 | </div> | |
| 409 | </div> | |
| 410 | )} | |
| 411 | </div> | |
| 412 | ||
| 413 | {/* Chat Input */} | |
| 414 | <form | |
| 415 | onSubmit={(e) => { | |
| 416 | e.preventDefault(); | |
| 417 | if (agentInput.trim() && !isAgentLoading) { | |
| 418 | sendMessage({ text: agentInput }); | |
| 419 | setAgentInput(''); | |
| 420 | } | |
| 421 | }} | |
| 422 | className="flex gap-2" | |
| 423 | > | |
| 424 | <input | |
| 425 | type="text" | |
| 426 | value={agentInput} | |
| 427 | onChange={(e) => setAgentInput(e.target.value)} | |
| 428 | placeholder={`Ask about ${language} code...`} | |
| 429 | className="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-purple-500" | |
| 430 | disabled={isAgentLoading} | |
| 431 | /> | |
| 432 | <Button | |
| 433 | type="submit" | |
| 434 | disabled={isAgentLoading || !agentInput.trim()} | |
| 435 | className="bg-purple-600 hover:bg-purple-700" | |
| 436 | size="sm" | |
| 437 | > | |
| 438 | <SendIcon className="w-4 h-4" /> | |
| 439 | </Button> | |
| 440 | </form> | |
| 441 | ||
| 442 | {/* Quick Actions */} | |
| 443 | <div className="flex flex-wrap gap-2"> | |
| 444 | <Button | |
| 445 | type="button" | |
| 446 | variant="outline" | |
| 447 | size="sm" | |
| 448 | className="text-xs" | |
| 449 | onClick={() => setAgentInput("Explain what this code does")} | |
| 450 | disabled={isAgentLoading} | |
| 451 | > | |
| 452 | Explain Code | |
| 453 | </Button> | |
| 454 | <Button | |
| 455 | type="button" | |
| 456 | variant="outline" | |
| 457 | size="sm" | |
| 458 | className="text-xs" | |
| 459 | onClick={() => setAgentInput("How can I improve this?")} | |
| 460 | disabled={isAgentLoading} | |
| 461 | > | |
| 462 | Improve | |
| 463 | </Button> | |
| 464 | <Button | |
| 465 | type="button" | |
| 466 | variant="outline" | |
| 467 | size="sm" | |
| 468 | className="text-xs" | |
| 469 | onClick={() => setAgentInput("Add error handling")} | |
| 470 | disabled={isAgentLoading} | |
| 471 | > | |
| 472 | Add Error Handling | |
| 473 | </Button> | |
| 474 | <Button | |
| 475 | type="button" | |
| 476 | variant="outline" | |
| 477 | size="sm" | |
| 478 | className="text-xs" | |
| 479 | onClick={() => setAgentInput("Make this more concise")} | |
| 480 | disabled={isAgentLoading} | |
| 481 | > | |
| 482 | Simplify | |
| 483 | </Button> | |
| 484 | </div> | |
| 485 | </div> | |
| 486 | </Card> | |
| 487 | )} | |
| 488 | ||
| 259 | 489 | {/* Environment Variables Panel */} |
| 260 | 490 | {showEnvVars && ( |
| 261 | 491 | <Card className="bg-black/50 backdrop-blur-md border-blue-500 p-4 animate-in slide-in-from-top-2 duration-200"> |