Skip to content

Commit

Permalink
feat: enhance the copilot response based on the requirements
Browse files Browse the repository at this point in the history
  • Loading branch information
aboudzein committed Nov 21, 2024
1 parent ca8668a commit d7f771b
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 235 deletions.
225 changes: 123 additions & 102 deletions packages/server/api/src/app/copilot/tools/code-agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createOpenAI } from '@ai-sdk/openai'
import { DeepPartial, generateObject } from 'ai'
import { z } from 'zod'
import { system , AppSystemProp } from '@activepieces/server-shared'
import { system, AppSystemProp } from '@activepieces/server-shared'

export function getModel() {
try {
Expand All @@ -28,7 +28,7 @@ const codeGenerationSchema = z.object({
name: z.string(),
type: z.string(),
description: z.string().optional(),
suggestedValue: z.string()
suggestedValue: z.string().optional(),
})).default([]),
})

Expand All @@ -42,100 +42,130 @@ export async function generateCode(
return {}
}

const result = await generateObject({
model,
system: `
You are a TypeScript code generation expert for Node.js backend development.
Your task is to:
1. Generate TypeScript code based on the requirement
2. List any required npm packages (only if sandbox mode is enabled)
3. Define necessary input parameters
CRITICAL REQUIREMENT:
- You MUST ALWAYS name the exported function variable 'code'
- The code MUST ALWAYS start with 'export const code ='
- This naming is required for the system to work properly
Environment Context:
- Node.js backend only (no frontend frameworks)
- Running in a sandbox/serverless environment
- ${sandboxMode ? 'External packages are allowed' : 'Only Node.js native features are allowed'}
Always follow these rules:
- ALWAYS use 'export const code = ' as the function declaration
- Code should be in TypeScript
- Use async/await for asynchronous operations
- Include type definitions for inputs
- Make code reusable and modular
- Follow best practices and clean code principles
- Include proper error handling and timeouts
- Consider sandbox environment limitations
${sandboxMode ? '' : '- Use only Node.js native modules (no external packages)'}
Simple Example:
{
"code": "export const code = async (inputs: { data: string }) => { return inputs.data.toUpperCase(); }",
"packages": [],
"inputs": [
{
"name": "data",
"type": "string",
"description": "Input string to process",
"suggestedValue": "suggest a value to put here for the user, if the type of the value is anything other than string put it inside {{}} like {{5}} or {{[1,2,3,4]}} or {{ ['jon','doe'] }}"
}
]
}
const systemPrompt = `You are a TypeScript code generation expert for Node.js backend development.
You are generating code for a custom automation flow block.
HTTP Example:
{
"code": "export const code = async (inputs: { url: string }) => { const response = await fetch(inputs.url); return response.json(); }",
"packages": ["node-fetch"],
"inputs": [
{
"name": "url",
"type": "string",
"description": "URL to fetch",
"suggestedValue": "suggest a value to put here for the user"
}
]
}
CRITICAL REQUIREMENTS:
1. Function Requirements:
- MUST start with 'export const code ='
- MUST be an async function
- MUST have proper input parameters
- MUST return a value (never void)
- Return value will be passed to the next step in the flow
Email Example:
{
"code": "export const code = async (inputs: { to: string, subject: string, body: string }) => { const nodemailer = require('nodemailer'); /* rest of implementation */ }",
"packages": ["nodemailer"],
"inputs": [
{
"name": "to",
"type": "string",
"description": "Recipient email",
"suggestedValue": "[email protected]"
},
{
"name": "subject",
"type": "string",
"description": "Email subject",
"suggestedValue": "Introduction Email"
},
{
"name": "body",
"type": "string",
"description": "Email content"
}
]
}
`,
2. Security and Best Practices:
- NEVER generate OAuth token management code
- Use ONLY ES Module imports (import ... from ...)
- ALL configuration through input parameters
- NO environment variables or file system access
- ALL imports must be static (top of file)
3. Input Parameters:
- ALL configuration must be passed via inputs
- Each input must have a clear description
- Each input must have a suggested value
- Input types must be explicit
Environment Context:
- This is a custom block in an automation flow
- Code runs in a Node.js ${sandboxMode ? 'sandbox with external packages' : 'environment with only native features'}
- Each execution must return a value for the next step
GOOD EXAMPLES:
1. API Request Example:
{
"code": "import { fetch } from '@activepieces/piece-http';\\n\\nexport const code = async (inputs: { apiKey: string, endpoint: string }) => {\\n try {\\n const response = await fetch(inputs.endpoint, {\\n headers: { Authorization: \`Bearer \${inputs.apiKey}\` }\\n });\\n return { data: await response.json() };\\n } catch (error) {\\n throw new Error(\`API request failed: \${error.message}\`);\\n }\\n}",
"packages": ["@activepieces/piece-http"],
"inputs": [
{
"name": "apiKey",
"type": "string",
"description": "API key for authentication",
"suggestedValue": "your-api-key-here"
},
{
"name": "endpoint",
"type": "string",
"description": "API endpoint URL",
"suggestedValue": "https://api.example.com/data"
}
]
}
2. Data Processing Example:
{
"code": "export const code = async (inputs: { items: string[] }) => {\\n const processed = inputs.items.map(item => item.toUpperCase());\\n return {\\n processedItems: processed,\\n count: processed.length,\\n timestamp: new Date().toISOString()\\n };\\n}",
"packages": [],
"inputs": [
{
"name": "items",
"type": "array",
"description": "Array of strings to process",
"suggestedValue": "{{ ['item1', 'item2', 'item3'] }}"
}
]
}
BAD EXAMPLES (NEVER DO THESE):
1. ❌ Using require() (Wrong):
const axios = require('axios');
✅ Correct:
import { fetch } from '@activepieces/piece-http';
2. ❌ Environment variables (Wrong):
const apiKey = process.env.API_KEY;
✅ Correct:
const apiKey = inputs.apiKey;
3. ❌ No return value (Wrong):
export const code = async (inputs) => { console.log(inputs); }
✅ Correct:
export const code = async (inputs) => { return { result: processedData }; }
4. ❌ Missing input types (Wrong):
export const code = async (inputs) => { ... }
✅ Correct:
export const code = async (inputs: { key: string }) => { ... }
5. ❌ File system operations (Wrong):
import fs from 'fs';
const config = fs.readFileSync('config.json');
✅ Correct:
const config = inputs.configuration;
6. ❌ OAuth token management (Wrong):
const token = await generateOAuthToken(credentials);
✅ Correct:
const token = inputs.accessToken;
7. ❌ Dynamic imports (Wrong):
const module = await import('some-package');
✅ Correct:
import { something } from 'some-package';
IMPORTANT REMINDERS:
- Always include error handling
- Always return meaningful data
- Use proper TypeScript types
- Include input validation
- Provide clear error messages
- Make return values useful for the next step in the flow
- Include type definitions for complex inputs
- Use suggested values that demonstrate the expected format`

const result = await generateObject({
model,
system: systemPrompt,
schema: codeGenerationSchema,
prompt: `
Generate TypeScript code for this requirement:
${requirement}
IMPORTANT:
- Use 'export const code = ' as the function declaration
- Keep the code simple and avoid complex string literals in the response JSON
- Ensure proper JSON escaping for any special characters
Remember: ${sandboxMode ? 'External packages are allowed' : 'Only Node.js native features are allowed'}
`,
prompt: `Generate TypeScript code for this automation flow requirement: ${requirement}
Remember:
- ${sandboxMode ? 'External packages are allowed' : 'Use only Node.js native features'}
- Must return useful data for the next step
- Include proper error handling
- All inputs must have suggested values
- Use proper TypeScript types`,
temperature: 0,
})

Expand All @@ -147,15 +177,6 @@ export async function generateCode(
}
}

// Verify and fix the code format if needed
if (!result.object.code.trim().startsWith('export const code =')) {
const fixedCode = `export const code = ${result.object.code.trim()}`
return {
...result.object,
code: fixedCode,
}
}

return result.object
}
catch (error) {
Expand Down
84 changes: 52 additions & 32 deletions packages/server/api/src/app/copilot/tools/code-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,32 @@ interface SearchQuery {
reason: string;
}

function createDefaultResponse(error?: string): AskCopilotCodeResponse {
return {
code: `export const code = async (inputs: Record<string, never>) => {
throw new Error('${error || 'Failed to generate code. Please try again with a different prompt.'}');
return { error: true };
}`,
packageJson: {
dependencies: {},
},
inputs: {},
icon: 'AlertTriangle',
title: 'Error: Code Generation Failed',
}
}

export const codeGeneratorTool = {
async generateCode(
request: AskCopilotRequest,
): Promise<AskCopilotCodeResponse | null> {
): Promise<AskCopilotCodeResponse> {
try {
const sandboxMode = system.getOrThrow(SharedSystemProp.EXECUTION_MODE) !== ExecutionMode.UNSANDBOXED;
const sandboxMode = system.getOrThrow(SharedSystemProp.EXECUTION_MODE) !== ExecutionMode.UNSANDBOXED

let plan = await generatePlan(request.prompt, sandboxMode)
if (!plan) {
return createDefaultResponse('Failed to generate plan')
}

if (plan.needsResearch && Array.isArray(plan.searchQueries) && plan.searchQueries.length > 0) {
const searchResults = await Promise.all(
Expand All @@ -82,45 +100,47 @@ export const codeGeneratorTool = {
.join('\n\n')

plan = await generatePlan(request.prompt, sandboxMode, validResults)
if (!plan) {
return createDefaultResponse('Failed to generate plan after research')
}
}

if (plan.readyForCode && plan.context) {
const result = await generateCode(plan.context, sandboxMode)

if (!result?.code) {
return null
}
if (!plan.readyForCode || !plan.context) {
return createDefaultResponse('Plan is not ready for code generation')
}

const dependencies: Record<string, string> = {}
result.packages?.forEach((pkg) => {
if (typeof pkg === 'string' && pkg.length > 0) {
dependencies[pkg] = '*'
}
})
const result = await generateCode(plan.context, sandboxMode)
if (!result?.code) {
return createDefaultResponse('Code generation failed')
}

const inputs: Record<string, string> = {}
result.inputs?.forEach((input) => {
if (input?.name && input?.suggestedValue) {
inputs[input.name] = input.suggestedValue
}
})
const dependencies: Record<string, string> = {}
result.packages?.forEach((pkg) => {
if (typeof pkg === 'string' && pkg.length > 0) {
dependencies[pkg] = '*'
}
})

return {
code: result.code,
packageJson: {
dependencies,
},
inputs,
icon: plan.suggestedIcon || 'Code2',
title: plan.suggestedTitle || 'Code Implementation',
const inputs: Record<string, string> = {}
result.inputs?.forEach((input) => {
if (input?.name && input?.suggestedValue) {
inputs[input.name] = input.suggestedValue
}
}
})

return null
return {
code: result.code,
packageJson: {
dependencies,
},
inputs,
icon: plan.suggestedIcon || 'Code2',
title: plan.suggestedTitle || 'Code Implementation',
}
}
catch (error) {
console.error(error)
return null
console.error('Code generation failed:', error)
return createDefaultResponse(error instanceof Error ? error.message : 'Unknown error occurred')
}
},
}
Loading

0 comments on commit d7f771b

Please sign in to comment.