- add sandbox type for aws agentcore
- TODO: get images from agentcore
This commit is contained in:
parent
099cf481b4
commit
3e233c889c
|
|
@ -163,3 +163,15 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
||||||
# REDIS_CA=
|
# REDIS_CA=
|
||||||
# REDIS_KEEP_ALIVE=
|
# REDIS_KEEP_ALIVE=
|
||||||
# ENABLE_BULLMQ_DASHBOARD=
|
# ENABLE_BULLMQ_DASHBOARD=
|
||||||
|
|
||||||
|
|
||||||
|
############################################################################################################
|
||||||
|
############################################## SECURITY ####################################################
|
||||||
|
############################################################################################################
|
||||||
|
|
||||||
|
# HTTP_DENY_LIST=
|
||||||
|
# SANDBOX_TYPE= #(aws | e2b)
|
||||||
|
# AWS_AGENTCORE_ACCESS_KEY_ID=
|
||||||
|
# AWS_AGENTCORE_SECRET_ACCESS_KEY=
|
||||||
|
# AWS_AGENTCORE_REGION=
|
||||||
|
# E2B_APIKEY=
|
||||||
|
|
@ -0,0 +1,363 @@
|
||||||
|
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
|
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
|
import { StructuredTool, ToolInputParsingException, ToolParams } from '@langchain/core/tools'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { CallbackManager, CallbackManagerForToolRun, Callbacks, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
|
||||||
|
import { RunnableConfig } from '@langchain/core/runnables'
|
||||||
|
import { ARTIFACTS_PREFIX } from '../../../src/agents'
|
||||||
|
import {
|
||||||
|
BedrockAgentCoreClient,
|
||||||
|
StopCodeInterpreterSessionCommand,
|
||||||
|
InvokeCodeInterpreterCommand,
|
||||||
|
InvokeCodeInterpreterCommandInput,
|
||||||
|
BedrockAgentCoreClientConfig
|
||||||
|
} from '@aws-sdk/client-bedrock-agentcore'
|
||||||
|
|
||||||
|
const DESC = `Evaluates python code in a sandbox environment. \
|
||||||
|
The environment is long running and exists across multiple executions. \
|
||||||
|
You must send the whole script every time and print your outputs. \
|
||||||
|
Script should be pure python code that can be evaluated. \
|
||||||
|
It should be in python format NOT markdown. \
|
||||||
|
The code should NOT be wrapped in backticks. \
|
||||||
|
All python packages including requests, matplotlib, scipy, numpy, pandas, \
|
||||||
|
etc are available. Create and display chart using "plt.show()".`
|
||||||
|
const NAME = 'code_interpreter'
|
||||||
|
|
||||||
|
class Code_InterpreterAWS_Tools implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
version: number
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
inputs: INodeParams[]
|
||||||
|
badge: string
|
||||||
|
credential: INodeParams
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'Code Interpreter by AWS AgentCore'
|
||||||
|
this.name = 'codeInterpreterAWS'
|
||||||
|
this.version = 1.0
|
||||||
|
this.type = 'CodeInterpreter'
|
||||||
|
this.icon = 'agentcore.png'
|
||||||
|
this.category = 'Tools'
|
||||||
|
this.description = 'Execute code in a sandbox environment by AWS AgentCore'
|
||||||
|
this.baseClasses = [this.type, 'Tool', ...getBaseClasses(AWSAgentCoreTool)]
|
||||||
|
this.credential = {
|
||||||
|
label: 'Connect Credential',
|
||||||
|
name: 'credential',
|
||||||
|
type: 'credential',
|
||||||
|
credentialNames: ['awsApi']
|
||||||
|
}
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'AWS Region',
|
||||||
|
name: 'region',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{ label: 'US East (N. Virginia) - us-east-1', name: 'us-east-1' },
|
||||||
|
{ label: 'US East (Ohio) - us-east-2', name: 'us-east-2' },
|
||||||
|
{ label: 'US West (N. California) - us-west-1', name: 'us-west-1' },
|
||||||
|
{ label: 'US West (Oregon) - us-west-2', name: 'us-west-2' },
|
||||||
|
{ label: 'Africa (Cape Town) - af-south-1', name: 'af-south-1' },
|
||||||
|
{ label: 'Asia Pacific (Hong Kong) - ap-east-1', name: 'ap-east-1' },
|
||||||
|
{ label: 'Asia Pacific (Mumbai) - ap-south-1', name: 'ap-south-1' },
|
||||||
|
{ label: 'Asia Pacific (Osaka) - ap-northeast-3', name: 'ap-northeast-3' },
|
||||||
|
{ label: 'Asia Pacific (Seoul) - ap-northeast-2', name: 'ap-northeast-2' },
|
||||||
|
{ label: 'Asia Pacific (Singapore) - ap-southeast-1', name: 'ap-southeast-1' },
|
||||||
|
{ label: 'Asia Pacific (Sydney) - ap-southeast-2', name: 'ap-southeast-2' },
|
||||||
|
{ label: 'Asia Pacific (Tokyo) - ap-northeast-1', name: 'ap-northeast-1' },
|
||||||
|
{ label: 'Canada (Central) - ca-central-1', name: 'ca-central-1' },
|
||||||
|
{ label: 'Europe (Frankfurt) - eu-central-1', name: 'eu-central-1' },
|
||||||
|
{ label: 'Europe (Ireland) - eu-west-1', name: 'eu-west-1' },
|
||||||
|
{ label: 'Europe (London) - eu-west-2', name: 'eu-west-2' },
|
||||||
|
{ label: 'Europe (Milan) - eu-south-1', name: 'eu-south-1' },
|
||||||
|
{ label: 'Europe (Paris) - eu-west-3', name: 'eu-west-3' },
|
||||||
|
{ label: 'Europe (Stockholm) - eu-north-1', name: 'eu-north-1' },
|
||||||
|
{ label: 'Middle East (Bahrain) - me-south-1', name: 'me-south-1' },
|
||||||
|
{ label: 'South America (São Paulo) - sa-east-1', name: 'sa-east-1' }
|
||||||
|
],
|
||||||
|
default: 'us-east-1',
|
||||||
|
description: 'AWS Region for AgentCore'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tool Name',
|
||||||
|
name: 'toolName',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Specify the name of the tool',
|
||||||
|
default: 'code_interpreter'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tool Description',
|
||||||
|
name: 'toolDesc',
|
||||||
|
type: 'string',
|
||||||
|
rows: 4,
|
||||||
|
description: 'Specify the description of the tool',
|
||||||
|
default: DESC
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||||
|
const toolDesc = nodeData.inputs?.toolDesc as string
|
||||||
|
const toolName = nodeData.inputs?.toolName as string
|
||||||
|
const region = nodeData.inputs?.region as string
|
||||||
|
|
||||||
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
|
const accessKeyId = getCredentialParam('awsKey', credentialData, nodeData)
|
||||||
|
const secretAccessKey = getCredentialParam('awsSecret', credentialData, nodeData)
|
||||||
|
const sessionToken = getCredentialParam('awsSession', credentialData, nodeData)
|
||||||
|
|
||||||
|
return await AWSAgentCoreTool.initialize({
|
||||||
|
description: toolDesc ?? DESC,
|
||||||
|
name: toolName ?? NAME,
|
||||||
|
accessKeyId: accessKeyId,
|
||||||
|
secretAccessKey: secretAccessKey,
|
||||||
|
sessionToken: sessionToken,
|
||||||
|
region: region,
|
||||||
|
schema: z.object({
|
||||||
|
input: z.string().describe('Python code to be executed in the sandbox environment')
|
||||||
|
}),
|
||||||
|
chatflowid: options.chatflowid,
|
||||||
|
orgId: options.orgId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSAgentCoreToolParams = ToolParams
|
||||||
|
type AWSAgentCoreToolInput = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
accessKeyId: string
|
||||||
|
secretAccessKey: string
|
||||||
|
sessionToken?: string
|
||||||
|
region: string
|
||||||
|
schema: any
|
||||||
|
chatflowid: string
|
||||||
|
orgId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AWSAgentCoreTool extends StructuredTool {
|
||||||
|
static lc_name() {
|
||||||
|
return 'AWSAgentCoreTool'
|
||||||
|
}
|
||||||
|
|
||||||
|
name = NAME
|
||||||
|
|
||||||
|
description = DESC
|
||||||
|
|
||||||
|
client: BedrockAgentCoreClient
|
||||||
|
|
||||||
|
accessKeyId: string
|
||||||
|
secretAccessKey: string
|
||||||
|
sessionToken?: string
|
||||||
|
region: string
|
||||||
|
|
||||||
|
schema
|
||||||
|
|
||||||
|
chatflowid: string
|
||||||
|
|
||||||
|
orgId: string
|
||||||
|
|
||||||
|
flowObj: ICommonObject
|
||||||
|
|
||||||
|
constructor(options: AWSAgentCoreToolParams & AWSAgentCoreToolInput) {
|
||||||
|
super(options)
|
||||||
|
this.description = options.description
|
||||||
|
this.name = options.name
|
||||||
|
this.accessKeyId = options.accessKeyId
|
||||||
|
this.secretAccessKey = options.secretAccessKey
|
||||||
|
this.sessionToken = options.sessionToken
|
||||||
|
this.region = options.region
|
||||||
|
this.schema = options.schema
|
||||||
|
this.chatflowid = options.chatflowid
|
||||||
|
this.orgId = options.orgId
|
||||||
|
|
||||||
|
// Initialize AWS AgentCore client
|
||||||
|
const awsAgentcoreConfig: BedrockAgentCoreClientConfig = {
|
||||||
|
region: this.region
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accessKeyId && this.secretAccessKey) {
|
||||||
|
awsAgentcoreConfig.credentials = {
|
||||||
|
accessKeyId: this.accessKeyId,
|
||||||
|
secretAccessKey: this.secretAccessKey,
|
||||||
|
...(this.sessionToken && { sessionToken: this.sessionToken })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client = new BedrockAgentCoreClient(awsAgentcoreConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async initialize(options: Partial<AWSAgentCoreToolParams> & AWSAgentCoreToolInput) {
|
||||||
|
return new this({
|
||||||
|
name: options.name,
|
||||||
|
description: options.description,
|
||||||
|
accessKeyId: options.accessKeyId,
|
||||||
|
secretAccessKey: options.secretAccessKey,
|
||||||
|
sessionToken: options.sessionToken,
|
||||||
|
region: options.region,
|
||||||
|
schema: options.schema,
|
||||||
|
chatflowid: options.chatflowid,
|
||||||
|
orgId: options.orgId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async call(
|
||||||
|
arg: z.infer<typeof this.schema>,
|
||||||
|
configArg?: RunnableConfig | Callbacks,
|
||||||
|
tags?: string[],
|
||||||
|
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
|
||||||
|
): Promise<string> {
|
||||||
|
const config = parseCallbackConfigArg(configArg)
|
||||||
|
if (config.runName === undefined) {
|
||||||
|
config.runName = this.name
|
||||||
|
}
|
||||||
|
let parsed
|
||||||
|
try {
|
||||||
|
parsed = await this.schema.parseAsync(arg)
|
||||||
|
} catch (e) {
|
||||||
|
throw new ToolInputParsingException(`Received tool input did not match expected schema`, JSON.stringify(arg))
|
||||||
|
}
|
||||||
|
const callbackManager_ = await CallbackManager.configure(
|
||||||
|
config.callbacks,
|
||||||
|
this.callbacks,
|
||||||
|
config.tags || tags,
|
||||||
|
this.tags,
|
||||||
|
config.metadata,
|
||||||
|
this.metadata,
|
||||||
|
{ verbose: this.verbose }
|
||||||
|
)
|
||||||
|
const runManager = await callbackManager_?.handleToolStart(
|
||||||
|
this.toJSON(),
|
||||||
|
typeof parsed === 'string' ? parsed : JSON.stringify(parsed),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
config.runName
|
||||||
|
)
|
||||||
|
let result
|
||||||
|
try {
|
||||||
|
result = await this._call(parsed, runManager, flowConfig)
|
||||||
|
} catch (e) {
|
||||||
|
await runManager?.handleToolError(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
if (result && typeof result !== 'string') {
|
||||||
|
result = JSON.stringify(result)
|
||||||
|
}
|
||||||
|
await runManager?.handleToolEnd(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
protected async _call(
|
||||||
|
arg: z.infer<typeof this.schema>,
|
||||||
|
_?: CallbackManagerForToolRun,
|
||||||
|
flowConfig?: { sessionId?: string; chatId?: string; input?: string }
|
||||||
|
): Promise<string> {
|
||||||
|
flowConfig = { ...this.flowObj, ...flowConfig }
|
||||||
|
try {
|
||||||
|
if ('input' in arg) {
|
||||||
|
const input: InvokeCodeInterpreterCommandInput = {
|
||||||
|
codeInterpreterIdentifier: 'aws.codeinterpreter.v1',
|
||||||
|
name: 'executeCode',
|
||||||
|
arguments: {
|
||||||
|
code: arg.input,
|
||||||
|
language: 'python'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = new InvokeCodeInterpreterCommand(input)
|
||||||
|
const execution = await this.client.send(command)
|
||||||
|
const sessionId = execution.sessionId
|
||||||
|
|
||||||
|
let output = ''
|
||||||
|
const artifacts: any[] = []
|
||||||
|
|
||||||
|
if (!execution.stream) {
|
||||||
|
if (sessionId) {
|
||||||
|
const stopSessionCommand = new StopCodeInterpreterSessionCommand({
|
||||||
|
codeInterpreterIdentifier: 'aws.codeinterpreter.v1',
|
||||||
|
sessionId
|
||||||
|
})
|
||||||
|
await this.client.send(stopSessionCommand)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const chunk of execution.stream) {
|
||||||
|
// Process each chunk from the stream
|
||||||
|
if (chunk.result) {
|
||||||
|
console.log('chunk.result =', chunk.result)
|
||||||
|
|
||||||
|
// Process content blocks
|
||||||
|
if (chunk.result.content) {
|
||||||
|
console.log('chunk.resultcontent =', chunk.result.content)
|
||||||
|
|
||||||
|
for (const contentBlock of chunk.result.content) {
|
||||||
|
if (contentBlock.type === 'text' && contentBlock.text) {
|
||||||
|
output += contentBlock.text
|
||||||
|
}
|
||||||
|
// TODO: Handle other content types that might contain images
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process structured content (stdout/stderr)
|
||||||
|
if (!output && chunk.result.structuredContent) {
|
||||||
|
if (chunk.result.structuredContent.stdout) {
|
||||||
|
output += chunk.result.structuredContent.stdout
|
||||||
|
}
|
||||||
|
if (chunk.result.structuredContent.stderr) {
|
||||||
|
throw new Error(`Code execution error: ${chunk.result.structuredContent.stderr}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const err =
|
||||||
|
chunk.accessDeniedException ||
|
||||||
|
chunk.internalServerException ||
|
||||||
|
chunk.throttlingException ||
|
||||||
|
chunk.validationException ||
|
||||||
|
chunk.conflictException ||
|
||||||
|
chunk.resourceNotFoundException ||
|
||||||
|
chunk.serviceQuotaExceededException
|
||||||
|
if (err) {
|
||||||
|
if (sessionId) {
|
||||||
|
const stopSessionCommand = new StopCodeInterpreterSessionCommand({
|
||||||
|
codeInterpreterIdentifier: 'aws.codeinterpreter.v1',
|
||||||
|
sessionId
|
||||||
|
})
|
||||||
|
await this.client.send(stopSessionCommand)
|
||||||
|
}
|
||||||
|
throw new Error(`${err.name}: ${err.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up session
|
||||||
|
if (sessionId) {
|
||||||
|
const stopSessionCommand = new StopCodeInterpreterSessionCommand({
|
||||||
|
codeInterpreterIdentifier: 'aws.codeinterpreter.v1',
|
||||||
|
sessionId
|
||||||
|
})
|
||||||
|
await this.client.send(stopSessionCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifacts.length > 0 ? output + ARTIFACTS_PREFIX + JSON.stringify(artifacts) : output
|
||||||
|
} else {
|
||||||
|
return 'No input provided'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return typeof e === 'string' ? e : JSON.stringify(e, null, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlowObject(flowObj: ICommonObject) {
|
||||||
|
this.flowObj = flowObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: Code_InterpreterAWS_Tools }
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
|
|
@ -23,6 +23,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/json-schema-ref-parser": "^11.7.0",
|
"@apidevtools/json-schema-ref-parser": "^11.7.0",
|
||||||
"@arizeai/openinference-instrumentation-langchain": "^2.0.0",
|
"@arizeai/openinference-instrumentation-langchain": "^2.0.0",
|
||||||
|
"@aws-sdk/client-bedrock-agentcore": "^3.883.0",
|
||||||
"@aws-sdk/client-bedrock-runtime": "3.422.0",
|
"@aws-sdk/client-bedrock-runtime": "3.422.0",
|
||||||
"@aws-sdk/client-dynamodb": "^3.360.0",
|
"@aws-sdk/client-dynamodb": "^3.360.0",
|
||||||
"@aws-sdk/client-kendra": "^3.750.0",
|
"@aws-sdk/client-kendra": "^3.750.0",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,13 @@ import { TextSplitter } from 'langchain/text_splitter'
|
||||||
import { DocumentLoader } from 'langchain/document_loaders/base'
|
import { DocumentLoader } from 'langchain/document_loaders/base'
|
||||||
import { NodeVM } from '@flowiseai/nodevm'
|
import { NodeVM } from '@flowiseai/nodevm'
|
||||||
import { Sandbox } from '@e2b/code-interpreter'
|
import { Sandbox } from '@e2b/code-interpreter'
|
||||||
|
import {
|
||||||
|
BedrockAgentCoreClient,
|
||||||
|
StopCodeInterpreterSessionCommand,
|
||||||
|
InvokeCodeInterpreterCommand,
|
||||||
|
InvokeCodeInterpreterCommandInput,
|
||||||
|
BedrockAgentCoreClientConfig
|
||||||
|
} from '@aws-sdk/client-bedrock-agentcore'
|
||||||
|
|
||||||
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
||||||
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
||||||
|
|
@ -1401,63 +1408,69 @@ export const executeJavaScriptCode = async (
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
const { timeout = 300000, useSandbox = true, streamOutput, libraries = [], nodeVMOptions = {} } = options
|
const { timeout = 300000, useSandbox = true, streamOutput, libraries = [], nodeVMOptions = {} } = options
|
||||||
const shouldUseSandbox = useSandbox && process.env.E2B_APIKEY
|
|
||||||
|
|
||||||
if (shouldUseSandbox) {
|
const sandboxType = process.env.SANDBOX_TYPE
|
||||||
try {
|
|
||||||
const variableDeclarations = []
|
|
||||||
|
|
||||||
if (sandbox['$vars']) {
|
if (useSandbox) {
|
||||||
variableDeclarations.push(`const $vars = ${JSON.stringify(sandbox['$vars'])};`)
|
const variableDeclarations = []
|
||||||
|
|
||||||
|
if (sandbox['$vars']) {
|
||||||
|
variableDeclarations.push(`const $vars = ${JSON.stringify(sandbox['$vars'])};`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sandbox['$flow']) {
|
||||||
|
variableDeclarations.push(`const $flow = ${JSON.stringify(sandbox['$flow'])};`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add other sandbox variables
|
||||||
|
for (const [key, value] of Object.entries(sandbox)) {
|
||||||
|
if (
|
||||||
|
key !== '$vars' &&
|
||||||
|
key !== '$flow' &&
|
||||||
|
key !== 'util' &&
|
||||||
|
key !== 'Symbol' &&
|
||||||
|
key !== 'child_process' &&
|
||||||
|
key !== 'fs' &&
|
||||||
|
key !== 'process'
|
||||||
|
) {
|
||||||
|
variableDeclarations.push(`const ${key} = ${JSON.stringify(value)};`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle import statements properly - they must be at the top
|
||||||
|
const lines = code.split('\n')
|
||||||
|
const importLines = []
|
||||||
|
const otherLines = []
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmedLine = line.trim()
|
||||||
|
|
||||||
|
// Skip node-fetch imports since Node.js has built-in fetch
|
||||||
|
if (trimmedLine.includes('node-fetch') || trimmedLine.includes("'fetch'") || trimmedLine.includes('"fetch"')) {
|
||||||
|
continue // Skip this line entirely
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sandbox['$flow']) {
|
// Check for existing ES6 imports and exports
|
||||||
variableDeclarations.push(`const $flow = ${JSON.stringify(sandbox['$flow'])};`)
|
if (trimmedLine.startsWith('import ') || trimmedLine.startsWith('export ')) {
|
||||||
|
importLines.push(line)
|
||||||
}
|
}
|
||||||
|
// Check for CommonJS require statements and convert them to ESM imports
|
||||||
// Add other sandbox variables
|
else if (/^(const|let|var)\s+.*=\s*require\s*\(/.test(trimmedLine)) {
|
||||||
for (const [key, value] of Object.entries(sandbox)) {
|
const convertedImport = convertRequireToImport(trimmedLine)
|
||||||
if (
|
if (convertedImport) {
|
||||||
key !== '$vars' &&
|
importLines.push(convertedImport)
|
||||||
key !== '$flow' &&
|
|
||||||
key !== 'util' &&
|
|
||||||
key !== 'Symbol' &&
|
|
||||||
key !== 'child_process' &&
|
|
||||||
key !== 'fs' &&
|
|
||||||
key !== 'process'
|
|
||||||
) {
|
|
||||||
variableDeclarations.push(`const ${key} = ${JSON.stringify(value)};`)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
otherLines.push(line)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle import statements properly - they must be at the top
|
// Separate imports from the rest of the code for proper ES6 module structure
|
||||||
const lines = code.split('\n')
|
const codeWithImports = [...importLines, `module.exports = async function() {`, ...variableDeclarations, ...otherLines, `}()`].join(
|
||||||
const importLines = []
|
'\n'
|
||||||
const otherLines = []
|
)
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
const trimmedLine = line.trim()
|
|
||||||
|
|
||||||
// Skip node-fetch imports since Node.js has built-in fetch
|
|
||||||
if (trimmedLine.includes('node-fetch') || trimmedLine.includes("'fetch'") || trimmedLine.includes('"fetch"')) {
|
|
||||||
continue // Skip this line entirely
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing ES6 imports and exports
|
|
||||||
if (trimmedLine.startsWith('import ') || trimmedLine.startsWith('export ')) {
|
|
||||||
importLines.push(line)
|
|
||||||
}
|
|
||||||
// Check for CommonJS require statements and convert them to ESM imports
|
|
||||||
else if (/^(const|let|var)\s+.*=\s*require\s*\(/.test(trimmedLine)) {
|
|
||||||
const convertedImport = convertRequireToImport(trimmedLine)
|
|
||||||
if (convertedImport) {
|
|
||||||
importLines.push(convertedImport)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
otherLines.push(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (sandboxType === 'e2b' && process.env.E2B_APIKEY) {
|
||||||
const sbx = await Sandbox.create({ apiKey: process.env.E2B_APIKEY, timeoutMs: timeout })
|
const sbx = await Sandbox.create({ apiKey: process.env.E2B_APIKEY, timeoutMs: timeout })
|
||||||
|
|
||||||
// Install libraries
|
// Install libraries
|
||||||
|
|
@ -1465,41 +1478,162 @@ export const executeJavaScriptCode = async (
|
||||||
await sbx.commands.run(`npm install ${library}`)
|
await sbx.commands.run(`npm install ${library}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separate imports from the rest of the code for proper ES6 module structure
|
try {
|
||||||
const codeWithImports = [
|
const execution = await sbx.runCode(codeWithImports, { language: 'js' })
|
||||||
|
|
||||||
|
let output = ''
|
||||||
|
|
||||||
|
if (execution.text) output = execution.text
|
||||||
|
if (!execution.text && execution.logs.stdout.length) output = execution.logs.stdout.join('\n')
|
||||||
|
|
||||||
|
if (execution.error) {
|
||||||
|
throw new Error(`${execution.error.name}: ${execution.error.value}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (execution.logs.stderr.length) {
|
||||||
|
throw new Error(execution.logs.stderr.join('\n'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream output if streaming function provided
|
||||||
|
if (streamOutput && output) {
|
||||||
|
streamOutput(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up sandbox
|
||||||
|
sbx.kill()
|
||||||
|
|
||||||
|
return output
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Sandbox Execution Error: ${e}`)
|
||||||
|
}
|
||||||
|
} else if (sandboxType === 'aws') {
|
||||||
|
const accessKeyId = process.env.AWS_AGENTCORE_ACCESS_KEY_ID
|
||||||
|
const secretAccessKey = process.env.AWS_AGENTCORE_SECRET_ACCESS_KEY
|
||||||
|
const region = process.env.AWS_AGENTCORE_REGION
|
||||||
|
|
||||||
|
if (!region || region.trim() === '') {
|
||||||
|
throw new Error('aws agentcore region is missing')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accessKeyId || accessKeyId.trim() === '' || !secretAccessKey || secretAccessKey.trim() === '') {
|
||||||
|
throw new Error('aws agentcore access key id or secret access key is missing')
|
||||||
|
}
|
||||||
|
|
||||||
|
const awsAgentcoreConfig: BedrockAgentCoreClientConfig = {
|
||||||
|
region: region
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessKeyId && accessKeyId.trim() !== '' && secretAccessKey && secretAccessKey.trim() !== '') {
|
||||||
|
awsAgentcoreConfig.credentials = {
|
||||||
|
accessKeyId: accessKeyId,
|
||||||
|
secretAccessKey: secretAccessKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new BedrockAgentCoreClient(awsAgentcoreConfig)
|
||||||
|
|
||||||
|
// For AWS AgentCore (Deno), wrap in async function and log the result
|
||||||
|
const awsCodeWithImports = [
|
||||||
...importLines,
|
...importLines,
|
||||||
`module.exports = async function() {`,
|
`(async () => {`,
|
||||||
...variableDeclarations,
|
...variableDeclarations,
|
||||||
...otherLines,
|
// Add console.log before return statements for AgentCore output
|
||||||
`}()`
|
...otherLines.map((line) => {
|
||||||
|
const trimmed = line.trim()
|
||||||
|
if (trimmed.startsWith('return ')) {
|
||||||
|
const returnValue = trimmed.substring(7) // Remove 'return '
|
||||||
|
const indent = line.match(/^(\s*)/)?.[1] || ''
|
||||||
|
return indent + `console.log(${returnValue})` + '\n' + line
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}),
|
||||||
|
`})()`
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const execution = await sbx.runCode(codeWithImports, { language: 'js' })
|
const input: InvokeCodeInterpreterCommandInput = {
|
||||||
|
codeInterpreterIdentifier: 'aws.codeinterpreter.v1',
|
||||||
let output = ''
|
name: 'executeCode',
|
||||||
|
arguments: {
|
||||||
if (execution.text) output = execution.text
|
code: awsCodeWithImports,
|
||||||
if (!execution.text && execution.logs.stdout.length) output = execution.logs.stdout.join('\n')
|
language: 'javascript',
|
||||||
|
clearContext: true
|
||||||
if (execution.error) {
|
}
|
||||||
throw new Error(`${execution.error.name}: ${execution.error.value}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (execution.logs.stderr.length) {
|
try {
|
||||||
throw new Error(execution.logs.stderr.join('\n'))
|
const command = new InvokeCodeInterpreterCommand(input)
|
||||||
|
const execution = await client.send(command)
|
||||||
|
const sessionId = execution.sessionId
|
||||||
|
const stopSessionCommand = new StopCodeInterpreterSessionCommand({
|
||||||
|
codeInterpreterIdentifier: 'aws.codeinterpreter.v1',
|
||||||
|
sessionId
|
||||||
|
})
|
||||||
|
|
||||||
|
let output = ''
|
||||||
|
|
||||||
|
if (!execution.stream) {
|
||||||
|
if (sessionId) {
|
||||||
|
await client.send(stopSessionCommand)
|
||||||
|
}
|
||||||
|
client.destroy()
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const chunk of execution.stream) {
|
||||||
|
// Process each chunk from the stream
|
||||||
|
if (chunk.result) {
|
||||||
|
// Process content blocks
|
||||||
|
if (chunk.result.content) {
|
||||||
|
for (const contentBlock of chunk.result.content) {
|
||||||
|
if (contentBlock.type === 'text' && contentBlock.text) {
|
||||||
|
output += contentBlock.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process structured content (stdout/stderr)
|
||||||
|
if (!output && chunk.result.structuredContent) {
|
||||||
|
if (chunk.result.structuredContent.stdout) {
|
||||||
|
output += chunk.result.structuredContent.stdout
|
||||||
|
}
|
||||||
|
if (chunk.result.structuredContent.stderr) {
|
||||||
|
throw new Error(`Code execution error: ${chunk.result.structuredContent.stderr}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const err =
|
||||||
|
chunk.accessDeniedException ||
|
||||||
|
chunk.internalServerException ||
|
||||||
|
chunk.throttlingException ||
|
||||||
|
chunk.validationException ||
|
||||||
|
chunk.conflictException ||
|
||||||
|
chunk.resourceNotFoundException ||
|
||||||
|
chunk.serviceQuotaExceededException
|
||||||
|
if (err) {
|
||||||
|
if (sessionId) {
|
||||||
|
await client.send(stopSessionCommand)
|
||||||
|
}
|
||||||
|
client.destroy()
|
||||||
|
throw new Error(`${err.name}: ${err.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream output if streaming function provided
|
||||||
|
if (streamOutput && output) {
|
||||||
|
streamOutput(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up sandbox
|
||||||
|
if (sessionId) {
|
||||||
|
await client.send(stopSessionCommand)
|
||||||
|
}
|
||||||
|
client.destroy()
|
||||||
|
|
||||||
|
return output
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Sandbox Execution Error: ${e}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream output if streaming function provided
|
|
||||||
if (streamOutput && output) {
|
|
||||||
streamOutput(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up sandbox
|
|
||||||
sbx.kill()
|
|
||||||
|
|
||||||
return output
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Sandbox Execution Error: ${e}`)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
|
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,11 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
|
|
||||||
# HTTP_DENY_LIST=
|
# HTTP_DENY_LIST=
|
||||||
|
# SANDBOX_TYPE= #(aws | e2b)
|
||||||
|
# AWS_AGENTCORE_ACCESS_KEY_ID=
|
||||||
|
# AWS_AGENTCORE_SECRET_ACCESS_KEY=
|
||||||
|
# AWS_AGENTCORE_REGION=
|
||||||
|
# E2B_APIKEY=
|
||||||
|
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
########################################### DOCUMENT LOADERS ###############################################
|
########################################### DOCUMENT LOADERS ###############################################
|
||||||
|
|
|
||||||
816
pnpm-lock.yaml
816
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue