@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
291 lines (290 loc) • 10.8 kB
JavaScript
// src/lib/action/actionExecutor.ts
/**
* CLI execution for GitHub Action
* @module action/actionExecutor
*/
import * as exec from "@actions/exec";
import * as core from "@actions/core";
import * as fs from "fs";
import * as path from "path";
import { buildEnvironmentVariables } from "./actionInputs.js";
import { withTimeout } from "../utils/errorHandling.js";
/** Default timeout for CLI execution (5 minutes) */
const DEFAULT_EXECUTION_TIMEOUT_MS = 300000;
/**
* Transform CLI token usage format to action format
*/
function transformTokenUsage(cliUsage) {
if (!cliUsage) {
return undefined;
}
return {
promptTokens: cliUsage.input,
completionTokens: cliUsage.output,
totalTokens: cliUsage.total,
};
}
/**
* Transform CLI evaluation format to action format (scale 1-10 to 0-100)
*/
function transformEvaluation(cliEval) {
if (!cliEval) {
return undefined;
}
return {
overallScore: cliEval.overall * 10, // Scale to 0-100
relevance: cliEval.relevance,
accuracy: cliEval.accuracy,
completeness: cliEval.completeness,
};
}
/**
* Transform CLI response to action result format
*/
export function transformCliResponse(cliResponse) {
return {
response: cliResponse.content,
responseJson: cliResponse,
// Use top-level provider/model if available, otherwise fallback to analytics
provider: cliResponse.provider || cliResponse.analytics?.provider,
model: cliResponse.model || cliResponse.analytics?.model,
usage: transformTokenUsage(cliResponse.usage),
cost: cliResponse.analytics?.cost,
executionTime: cliResponse.responseTime,
evaluation: transformEvaluation(cliResponse.evaluation),
};
}
/**
* Build CLI arguments from action inputs (using verified camelCase flags)
*/
export function buildCliArgs(inputs) {
const args = [inputs.command, inputs.prompt];
// Provider & Model
if (inputs.provider && inputs.provider !== "auto") {
args.push("--provider", inputs.provider);
}
if (inputs.model) {
args.push("--model", inputs.model);
}
// Generation parameters (camelCase flags!)
if (inputs.temperature !== undefined) {
args.push("--temperature", inputs.temperature.toString());
}
if (inputs.maxTokens !== undefined) {
args.push("--maxTokens", inputs.maxTokens.toString()); // NOT --max-tokens
}
if (inputs.systemPrompt) {
args.push("--system", inputs.systemPrompt);
}
// Output format (always JSON for parsing)
args.push("--format", "json");
args.push("--quiet");
args.push("--noColor");
// Multimodal inputs
const { multimodal } = inputs;
if (multimodal.imagePaths) {
for (const img of multimodal.imagePaths) {
args.push("--image", img);
}
}
if (multimodal.pdfPaths) {
for (const pdf of multimodal.pdfPaths) {
args.push("--pdf", pdf);
}
}
if (multimodal.csvPaths) {
for (const csv of multimodal.csvPaths) {
args.push("--csv", csv);
}
}
if (multimodal.videoPaths) {
for (const video of multimodal.videoPaths) {
args.push("--video", video);
}
}
// Extended thinking (camelCase flags!)
if (inputs.thinking.enabled) {
args.push("--thinking");
args.push("--thinkingLevel", inputs.thinking.level); // NOT --thinking-level
args.push("--thinkingBudget", inputs.thinking.budget.toString()); // NOT --thinking-budget
}
// Features (camelCase flags!)
if (inputs.enableAnalytics) {
args.push("--enableAnalytics"); // NOT --enable-analytics
}
if (inputs.enableEvaluation) {
args.push("--enableEvaluation"); // NOT --enable-evaluation
}
// Timeout
if (inputs.timeout) {
args.push("--timeout", inputs.timeout.toString());
}
// Output file
if (inputs.outputFile) {
args.push("--output", inputs.outputFile);
}
// Debug
if (inputs.debug) {
args.push("--debug");
}
return args;
}
/**
* Install NeuroLink CLI
*/
export async function installNeurolink(version) {
core.info(`Installing @juspay/neurolink@${version}...`);
const installArgs = version === "latest"
? ["install", "-g", "@juspay/neurolink"]
: ["install", "-g", `@juspay/neurolink@${version}`];
await exec.exec("npm", installArgs, {
silent: !core.isDebug(),
});
core.info("NeuroLink CLI installed successfully");
}
/**
* Execute NeuroLink CLI command
* @param args - CLI arguments
* @param env - Environment variables
* @param workingDirectory - Working directory for execution
* @param timeout - Timeout in milliseconds (defaults to 5 minutes)
*/
export async function executeNeurolink(args, env, workingDirectory, timeout = DEFAULT_EXECUTION_TIMEOUT_MS) {
const startTime = Date.now();
try {
// Filter out undefined values from process.env
const processEnv = {};
for (const [key, value] of Object.entries(process.env)) {
if (value !== undefined) {
processEnv[key] = value;
}
}
const execPromise = exec.getExecOutput("neurolink", args, {
cwd: workingDirectory,
env: { ...processEnv, ...env },
silent: false,
ignoreReturnCode: true,
});
// Wrap execution with timeout protection
const { exitCode, stdout, stderr } = await withTimeout(execPromise, timeout, new Error(`CLI execution timed out after ${timeout}ms. Consider increasing the timeout parameter.`));
const executionTime = Date.now() - startTime;
if (exitCode !== 0) {
return {
success: false,
response: "",
error: stderr || `CLI exited with code ${exitCode}`,
executionTime,
};
}
// Parse JSON output
try {
// The CLI output may have log lines before the JSON
// The CLI JSON response always has "content" as the first key
// Find the line that contains '{"content"' or '{ "content"' to locate JSON start
const lines = stdout.split("\n");
let jsonStartIndex = -1;
// Find the line that starts the JSON response object
// Look for pattern: line is "{" and next non-empty line has "content"
for (let i = 0; i < lines.length; i++) {
const trimmedLine = lines[i].trim();
if (trimmedLine === "{") {
// Check if next non-empty line contains "content"
for (let j = i + 1; j < lines.length && j < i + 3; j++) {
const nextLine = lines[j].trim();
if (nextLine.includes('"content"')) {
jsonStartIndex = i;
break;
}
}
if (jsonStartIndex !== -1) {
break;
}
}
}
core.debug(`JSON start line index: ${jsonStartIndex}`);
if (jsonStartIndex === -1) {
core.debug("No JSON found, returning raw stdout");
return {
success: true,
response: stdout.trim(),
executionTime,
};
}
// Extract JSON from that line onwards
const jsonStr = lines.slice(jsonStartIndex).join("\n").trim();
core.debug(`JSON string starts with: ${jsonStr.substring(0, 50)}`);
const cliResponse = JSON.parse(jsonStr);
core.debug(`Parsed CLI response keys: ${Object.keys(cliResponse).join(", ")}`);
core.debug(`cliResponse.content: ${cliResponse.content?.substring(0, 50)}`);
core.debug(`cliResponse.provider: ${cliResponse.provider}`);
const transformed = transformCliResponse(cliResponse);
core.debug(`Transformed response: ${transformed.response?.substring(0, 50)}`);
core.debug(`Transformed provider: ${transformed.provider}`);
return {
success: true,
...transformed,
executionTime: transformed.executionTime || executionTime,
};
}
catch (parseError) {
// If not JSON, return raw output
core.debug(`JSON parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
return {
success: true,
response: stdout.trim(),
executionTime,
};
}
}
catch (error) {
return {
success: false,
response: "",
error: error instanceof Error ? error.message : String(error),
executionTime: Date.now() - startTime,
};
}
}
/**
* Run complete NeuroLink action
*/
export async function runNeurolink(inputs) {
let credPath;
try {
// Install CLI
await installNeurolink(inputs.neurolinkVersion);
// Build environment
const env = buildEnvironmentVariables(inputs);
// Handle Google Cloud credentials (base64 decode)
if (inputs.googleCloudConfig.googleApplicationCredentials) {
const decoded = Buffer.from(inputs.googleCloudConfig.googleApplicationCredentials, "base64").toString("utf-8");
credPath = path.join(process.env.RUNNER_TEMP || "/tmp", `vertex-credentials-${process.pid}-${Date.now()}.json`);
fs.writeFileSync(credPath, decoded, { mode: 0o600 });
env.GOOGLE_APPLICATION_CREDENTIALS = credPath;
}
// Build CLI arguments
const args = buildCliArgs(inputs);
core.info(`Executing: neurolink ${args.join(" ")}`);
if (inputs.debug) {
core.debug(`Working directory: ${inputs.workingDirectory}`);
core.debug(`Environment keys: ${Object.keys(env)
.filter((k) => k.includes("API") || k.includes("KEY"))
.join(", ")}`);
}
// Execute with timeout (use input timeout or default)
const executionTimeout = inputs.timeout || DEFAULT_EXECUTION_TIMEOUT_MS;
return await executeNeurolink(args, env, inputs.workingDirectory, executionTimeout);
}
finally {
// Clean up credential file
if (credPath && fs.existsSync(credPath)) {
try {
fs.unlinkSync(credPath);
core.debug("Cleaned up temporary credentials file");
}
catch {
// Ignore cleanup errors
}
}
}
}