Public: anyone can read this; the crew can change it.

Claude/add agent function 011 cu ysw u rf laqy4u srp vyn p #22

opened this view on origin

Check · Review · Land · Live — Its check is red. Next: builder, its check is red.

Ready to land

claude/add-agent-function-011CUYswURfLAQY4uSrpVynP → main 11 commits

  • Ready to merge — no conflicts with main. 11 commits will land on main.

    Check faileddeploy dd7db1c

    You need write access to merge.

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).

Review

Sign in to review this change.
4 files changed +3994 −2315
package-lock.json +3600 −2314

Diff not shown — 5,914 changed lines (limit 5,000). +3600 −2314

View file
package.json +9
@@ -8,7 +8,13 @@
88 "start": "next start",
99 "lint": "next lint"
1010 },
11 "overrides": {
12 "zod": "^4.1.12"
13 },
1114 "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",
1218 "@calcom/embed-react": "^1.5.0",
1319 "@radix-ui/react-avatar": "^1.1.0",
1420 "@radix-ui/react-dialog": "^1.1.1",
@@ -22,10 +28,13 @@
2228 "@react-three/fiber": "^8.17.7",
2329 "@supabase/ssr": "^0.5.1",
2430 "@uiw/react-textarea-code-editor": "^3.0.2",
31 "@valibot/to-json-schema": "^1.3.0",
2532 "@vercel/analytics": "^1.5.0",
2633 "@vercel/sandbox": "^1.0.2",
34 "ai": "^6.0.0-beta.81",
2735 "class-variance-authority": "^0.7.0",
2836 "clsx": "^2.1.1",
37 "effect": "^3.18.4",
2938 "framer-motion": "^11.5.4",
3039 "lucide-react": "^0.439.0",
3140 "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'
44 import dynamic from 'next/dynamic'
55 import { Card } from "@/components/ui/card"
66 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'
88 import { toast } from '@/hooks/use-toast'
9 import { useChat } from '@ai-sdk/react'
10 import { DefaultChatTransport } from 'ai'
911
1012 const DynamicCodeEditor = dynamic(
1113 () => import('@uiw/react-textarea-code-editor').then((mod) => mod.default),
@@ -82,6 +84,28 @@ export const FunctionCreator: React.FC = () => {
8284 const [showScheduler, setShowScheduler] = useState(false);
8385 const [showEnvVars, setShowEnvVars] = useState(false);
8486 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';
85109
86110 const handleLanguageChange = (newLanguage: Language) => {
87111 setLanguage(newLanguage);
@@ -235,6 +259,15 @@ export const FunctionCreator: React.FC = () => {
235259 <PlayIcon className="w-4 h-4 mr-2" />
236260 {isExecuting ? 'Running...' : 'Run'}
237261 </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>
238271 <Button
239272 onClick={() => setShowEnvVars(!showEnvVars)}
240273 variant="outline"
@@ -256,6 +289,203 @@ export const FunctionCreator: React.FC = () => {
256289 </div>
257290 </div>
258291
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&apos;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
259489 {/* Environment Variables Panel */}
260490 {showEnvVars && (
261491 <Card className="bg-black/50 backdrop-blur-md border-blue-500 p-4 animate-in slide-in-from-top-2 duration-200">

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs