@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
188 lines (187 loc) • 7.01 kB
JavaScript
// src/lib/action/githubIntegration.ts
/**
* GitHub API integration for comments, outputs, and job summary
* @module action/githubIntegration
*/
import * as core from "@actions/core";
import * as github from "@actions/github";
/**
* Build comment body for PR/issue
*/
function buildCommentBody(inputs, result) {
const commentTag = `<!-- ${inputs.commentTag} -->`;
const parts = [commentTag];
parts.push("## 🤖 NeuroLink AI Response\n");
parts.push(result.response);
parts.push("\n---");
// Build metadata line
const metaParts = [];
if (result.model) {
metaParts.push(`\`${result.model}\``);
}
if (result.provider) {
metaParts.push(`via ${result.provider}`);
}
if (result.cost) {
metaParts.push(`$${result.cost.toFixed(6)}`);
}
if (result.usage?.totalTokens) {
metaParts.push(`${result.usage.totalTokens} tokens`);
}
parts.push(`<sub>Generated by [NeuroLink](https://github.com/juspay/neurolink)${metaParts.length ? ` using ${metaParts.join(" | ")}` : ""}</sub>`);
return parts.join("\n");
}
/**
* Find existing comment by tag
*/
async function findExistingComment(octokit, owner, repo, issueNumber, commentTag) {
const { data: comments } = await octokit.rest.issues.listComments({
owner,
repo,
issue_number: issueNumber,
});
const existing = comments.find((c) => c.body?.includes(`<!-- ${commentTag} -->`));
return existing?.id;
}
/**
* Post result as comment on PR or issue
*/
export async function postResultComment(inputs, result) {
if (!inputs.postComment) {
return { success: true };
}
const token = inputs.githubToken;
if (!token) {
core.warning("post_comment enabled but no github_token provided");
return { success: false, error: "No GitHub token" };
}
const context = github.context;
const issueNumber = context.payload.pull_request?.number || context.payload.issue?.number;
if (!issueNumber) {
core.warning("post_comment enabled but not in PR or issue context");
return { success: false, error: "Not in PR or issue context" };
}
const octokit = github.getOctokit(token);
const body = buildCommentBody(inputs, result);
try {
// Check for existing comment if update mode is enabled
if (inputs.updateExistingComment) {
const existingId = await findExistingComment(octokit, context.repo.owner, context.repo.repo, issueNumber, inputs.commentTag);
if (existingId) {
const { data } = await octokit.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingId,
body,
});
core.info(`Updated existing comment #${existingId}`);
return { success: true, commentId: data.id, commentUrl: data.html_url };
}
}
// Create new comment
const { data } = await octokit.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body,
});
core.info(`Created comment #${data.id}`);
return { success: true, commentId: data.id, commentUrl: data.html_url };
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
core.warning(`Failed to post comment: ${message}`);
return { success: false, error: message };
}
}
/**
* Write job summary
*/
export async function writeJobSummary(inputs, result) {
const summary = core.summary
.addHeading("NeuroLink Execution Summary", 2)
.addTable([
[
{ data: "Metric", header: true },
{ data: "Value", header: true },
],
["Provider", result.provider || "auto"],
["Model", result.model || "auto"],
["Input Tokens", String(result.usage?.promptTokens || "N/A")],
["Output Tokens", String(result.usage?.completionTokens || "N/A")],
["Total Tokens", String(result.usage?.totalTokens || "N/A")],
["Cost", result.cost ? `$${result.cost.toFixed(6)}` : "N/A"],
[
"Execution Time",
result.executionTime ? `${result.executionTime}ms` : "N/A",
],
]);
if (result.evaluation) {
summary.addHeading("Quality Evaluation", 3);
summary.addRaw(`Score: ${result.evaluation.overallScore}/100`);
}
summary.addHeading("Response", 3);
const truncatedResponse = result.response.length > 5000
? result.response.substring(0, 5000) + "..."
: result.response;
summary.addRaw(truncatedResponse);
try {
await summary.write();
}
catch (error) {
// Job summary may not be available in all environments (e.g., local testing)
core.warning(`Unable to write job summary: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Set all action outputs
*/
export function setActionOutputs(result, commentResult) {
core.setOutput("response", result.response);
core.setOutput("response_json", JSON.stringify(result.responseJson || {}));
if (result.provider) {
core.setOutput("provider", result.provider);
}
if (result.model) {
core.setOutput("model", result.model);
}
if (result.usage?.totalTokens) {
core.setOutput("tokens_used", result.usage.totalTokens.toString());
}
if (result.usage?.promptTokens) {
core.setOutput("prompt_tokens", result.usage.promptTokens.toString());
}
if (result.usage?.completionTokens) {
core.setOutput("completion_tokens", result.usage.completionTokens.toString());
}
if (result.cost) {
core.setOutput("cost", result.cost.toString());
}
if (result.executionTime) {
core.setOutput("execution_time", result.executionTime.toString());
}
if (result.evaluation?.overallScore) {
core.setOutput("evaluation_score", result.evaluation.overallScore.toString());
}
if (commentResult?.commentId) {
core.setOutput("comment_id", commentResult.commentId.toString());
}
}
/**
* Get all outputs as typed object (snake_case to match action.yml outputs)
*/
export function getActionOutputs(result, commentResult) {
return {
response: result.response,
response_json: JSON.stringify(result.responseJson || {}),
provider: result.provider,
model: result.model,
tokens_used: result.usage?.totalTokens?.toString(),
prompt_tokens: result.usage?.promptTokens?.toString(),
completion_tokens: result.usage?.completionTokens?.toString(),
cost: result.cost?.toString(),
execution_time: result.executionTime?.toString(),
evaluation_score: result.evaluation?.overallScore?.toString(),
comment_id: commentResult?.commentId?.toString(),
};
}