@juspay/neurolink 8.32.0 → 8.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +284 -75
- package/dist/action/actionExecutor.d.ts +29 -0
- package/dist/action/actionExecutor.js +290 -0
- package/dist/action/actionInputs.d.ts +25 -0
- package/dist/action/actionInputs.js +293 -0
- package/dist/action/githubIntegration.d.ts +21 -0
- package/dist/action/githubIntegration.js +187 -0
- package/dist/action/index.d.ts +8 -0
- package/dist/action/index.js +11 -0
- package/dist/index.d.ts +145 -13
- package/dist/index.js +145 -13
- package/dist/lib/action/actionExecutor.d.ts +29 -0
- package/dist/lib/action/actionExecutor.js +291 -0
- package/dist/lib/action/actionInputs.d.ts +25 -0
- package/dist/lib/action/actionInputs.js +294 -0
- package/dist/lib/action/githubIntegration.d.ts +21 -0
- package/dist/lib/action/githubIntegration.js +188 -0
- package/dist/lib/action/index.d.ts +8 -0
- package/dist/lib/action/index.js +12 -0
- package/dist/lib/index.d.ts +145 -13
- package/dist/lib/index.js +145 -13
- package/dist/lib/mcp/externalServerManager.js +41 -7
- package/dist/lib/neurolink.d.ts +172 -0
- package/dist/lib/neurolink.js +172 -0
- package/dist/lib/types/actionTypes.d.ts +205 -0
- package/dist/lib/types/actionTypes.js +7 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/utils/errorHandling.d.ts +8 -0
- package/dist/lib/utils/errorHandling.js +29 -0
- package/dist/mcp/externalServerManager.js +41 -7
- package/dist/neurolink.d.ts +172 -0
- package/dist/neurolink.js +172 -0
- package/dist/types/actionTypes.d.ts +205 -0
- package/dist/types/actionTypes.js +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/errorHandling.d.ts +8 -0
- package/dist/utils/errorHandling.js +29 -0
- package/package.json +11 -3
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// src/lib/action/actionExecutor.ts
|
|
2
|
+
/**
|
|
3
|
+
* CLI execution for GitHub Action
|
|
4
|
+
* @module action/actionExecutor
|
|
5
|
+
*/
|
|
6
|
+
import * as exec from "@actions/exec";
|
|
7
|
+
import * as core from "@actions/core";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import { buildEnvironmentVariables } from "./actionInputs.js";
|
|
11
|
+
import { withTimeout } from "../utils/errorHandling.js";
|
|
12
|
+
/** Default timeout for CLI execution (5 minutes) */
|
|
13
|
+
const DEFAULT_EXECUTION_TIMEOUT_MS = 300000;
|
|
14
|
+
/**
|
|
15
|
+
* Transform CLI token usage format to action format
|
|
16
|
+
*/
|
|
17
|
+
function transformTokenUsage(cliUsage) {
|
|
18
|
+
if (!cliUsage) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
promptTokens: cliUsage.input,
|
|
23
|
+
completionTokens: cliUsage.output,
|
|
24
|
+
totalTokens: cliUsage.total,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Transform CLI evaluation format to action format (scale 1-10 to 0-100)
|
|
29
|
+
*/
|
|
30
|
+
function transformEvaluation(cliEval) {
|
|
31
|
+
if (!cliEval) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
overallScore: cliEval.overall * 10, // Scale to 0-100
|
|
36
|
+
relevance: cliEval.relevance,
|
|
37
|
+
accuracy: cliEval.accuracy,
|
|
38
|
+
completeness: cliEval.completeness,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Transform CLI response to action result format
|
|
43
|
+
*/
|
|
44
|
+
export function transformCliResponse(cliResponse) {
|
|
45
|
+
return {
|
|
46
|
+
response: cliResponse.content,
|
|
47
|
+
responseJson: cliResponse,
|
|
48
|
+
// Use top-level provider/model if available, otherwise fallback to analytics
|
|
49
|
+
provider: cliResponse.provider || cliResponse.analytics?.provider,
|
|
50
|
+
model: cliResponse.model || cliResponse.analytics?.model,
|
|
51
|
+
usage: transformTokenUsage(cliResponse.usage),
|
|
52
|
+
cost: cliResponse.analytics?.cost,
|
|
53
|
+
executionTime: cliResponse.responseTime,
|
|
54
|
+
evaluation: transformEvaluation(cliResponse.evaluation),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build CLI arguments from action inputs (using verified camelCase flags)
|
|
59
|
+
*/
|
|
60
|
+
export function buildCliArgs(inputs) {
|
|
61
|
+
const args = [inputs.command, inputs.prompt];
|
|
62
|
+
// Provider & Model
|
|
63
|
+
if (inputs.provider && inputs.provider !== "auto") {
|
|
64
|
+
args.push("--provider", inputs.provider);
|
|
65
|
+
}
|
|
66
|
+
if (inputs.model) {
|
|
67
|
+
args.push("--model", inputs.model);
|
|
68
|
+
}
|
|
69
|
+
// Generation parameters (camelCase flags!)
|
|
70
|
+
if (inputs.temperature !== undefined) {
|
|
71
|
+
args.push("--temperature", inputs.temperature.toString());
|
|
72
|
+
}
|
|
73
|
+
if (inputs.maxTokens !== undefined) {
|
|
74
|
+
args.push("--maxTokens", inputs.maxTokens.toString()); // NOT --max-tokens
|
|
75
|
+
}
|
|
76
|
+
if (inputs.systemPrompt) {
|
|
77
|
+
args.push("--system", inputs.systemPrompt);
|
|
78
|
+
}
|
|
79
|
+
// Output format (always JSON for parsing)
|
|
80
|
+
args.push("--format", "json");
|
|
81
|
+
args.push("--quiet");
|
|
82
|
+
args.push("--noColor");
|
|
83
|
+
// Multimodal inputs
|
|
84
|
+
const { multimodal } = inputs;
|
|
85
|
+
if (multimodal.imagePaths) {
|
|
86
|
+
for (const img of multimodal.imagePaths) {
|
|
87
|
+
args.push("--image", img);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (multimodal.pdfPaths) {
|
|
91
|
+
for (const pdf of multimodal.pdfPaths) {
|
|
92
|
+
args.push("--pdf", pdf);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (multimodal.csvPaths) {
|
|
96
|
+
for (const csv of multimodal.csvPaths) {
|
|
97
|
+
args.push("--csv", csv);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (multimodal.videoPaths) {
|
|
101
|
+
for (const video of multimodal.videoPaths) {
|
|
102
|
+
args.push("--video", video);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Extended thinking (camelCase flags!)
|
|
106
|
+
if (inputs.thinking.enabled) {
|
|
107
|
+
args.push("--thinking");
|
|
108
|
+
args.push("--thinkingLevel", inputs.thinking.level); // NOT --thinking-level
|
|
109
|
+
args.push("--thinkingBudget", inputs.thinking.budget.toString()); // NOT --thinking-budget
|
|
110
|
+
}
|
|
111
|
+
// Features (camelCase flags!)
|
|
112
|
+
if (inputs.enableAnalytics) {
|
|
113
|
+
args.push("--enableAnalytics"); // NOT --enable-analytics
|
|
114
|
+
}
|
|
115
|
+
if (inputs.enableEvaluation) {
|
|
116
|
+
args.push("--enableEvaluation"); // NOT --enable-evaluation
|
|
117
|
+
}
|
|
118
|
+
// Timeout
|
|
119
|
+
if (inputs.timeout) {
|
|
120
|
+
args.push("--timeout", inputs.timeout.toString());
|
|
121
|
+
}
|
|
122
|
+
// Output file
|
|
123
|
+
if (inputs.outputFile) {
|
|
124
|
+
args.push("--output", inputs.outputFile);
|
|
125
|
+
}
|
|
126
|
+
// Debug
|
|
127
|
+
if (inputs.debug) {
|
|
128
|
+
args.push("--debug");
|
|
129
|
+
}
|
|
130
|
+
return args;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Install NeuroLink CLI
|
|
134
|
+
*/
|
|
135
|
+
export async function installNeurolink(version) {
|
|
136
|
+
core.info(`Installing @juspay/neurolink@${version}...`);
|
|
137
|
+
const installArgs = version === "latest"
|
|
138
|
+
? ["install", "-g", "@juspay/neurolink"]
|
|
139
|
+
: ["install", "-g", `@juspay/neurolink@${version}`];
|
|
140
|
+
await exec.exec("npm", installArgs, {
|
|
141
|
+
silent: !core.isDebug(),
|
|
142
|
+
});
|
|
143
|
+
core.info("NeuroLink CLI installed successfully");
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Execute NeuroLink CLI command
|
|
147
|
+
* @param args - CLI arguments
|
|
148
|
+
* @param env - Environment variables
|
|
149
|
+
* @param workingDirectory - Working directory for execution
|
|
150
|
+
* @param timeout - Timeout in milliseconds (defaults to 5 minutes)
|
|
151
|
+
*/
|
|
152
|
+
export async function executeNeurolink(args, env, workingDirectory, timeout = DEFAULT_EXECUTION_TIMEOUT_MS) {
|
|
153
|
+
const startTime = Date.now();
|
|
154
|
+
try {
|
|
155
|
+
// Filter out undefined values from process.env
|
|
156
|
+
const processEnv = {};
|
|
157
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
158
|
+
if (value !== undefined) {
|
|
159
|
+
processEnv[key] = value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const execPromise = exec.getExecOutput("neurolink", args, {
|
|
163
|
+
cwd: workingDirectory,
|
|
164
|
+
env: { ...processEnv, ...env },
|
|
165
|
+
silent: false,
|
|
166
|
+
ignoreReturnCode: true,
|
|
167
|
+
});
|
|
168
|
+
// Wrap execution with timeout protection
|
|
169
|
+
const { exitCode, stdout, stderr } = await withTimeout(execPromise, timeout, new Error(`CLI execution timed out after ${timeout}ms. Consider increasing the timeout parameter.`));
|
|
170
|
+
const executionTime = Date.now() - startTime;
|
|
171
|
+
if (exitCode !== 0) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
response: "",
|
|
175
|
+
error: stderr || `CLI exited with code ${exitCode}`,
|
|
176
|
+
executionTime,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// Parse JSON output
|
|
180
|
+
try {
|
|
181
|
+
// The CLI output may have log lines before the JSON
|
|
182
|
+
// The CLI JSON response always has "content" as the first key
|
|
183
|
+
// Find the line that contains '{"content"' or '{ "content"' to locate JSON start
|
|
184
|
+
const lines = stdout.split("\n");
|
|
185
|
+
let jsonStartIndex = -1;
|
|
186
|
+
// Find the line that starts the JSON response object
|
|
187
|
+
// Look for pattern: line is "{" and next non-empty line has "content"
|
|
188
|
+
for (let i = 0; i < lines.length; i++) {
|
|
189
|
+
const trimmedLine = lines[i].trim();
|
|
190
|
+
if (trimmedLine === "{") {
|
|
191
|
+
// Check if next non-empty line contains "content"
|
|
192
|
+
for (let j = i + 1; j < lines.length && j < i + 3; j++) {
|
|
193
|
+
const nextLine = lines[j].trim();
|
|
194
|
+
if (nextLine.includes('"content"')) {
|
|
195
|
+
jsonStartIndex = i;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (jsonStartIndex !== -1) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
core.debug(`JSON start line index: ${jsonStartIndex}`);
|
|
205
|
+
if (jsonStartIndex === -1) {
|
|
206
|
+
core.debug("No JSON found, returning raw stdout");
|
|
207
|
+
return {
|
|
208
|
+
success: true,
|
|
209
|
+
response: stdout.trim(),
|
|
210
|
+
executionTime,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// Extract JSON from that line onwards
|
|
214
|
+
const jsonStr = lines.slice(jsonStartIndex).join("\n").trim();
|
|
215
|
+
core.debug(`JSON string starts with: ${jsonStr.substring(0, 50)}`);
|
|
216
|
+
const cliResponse = JSON.parse(jsonStr);
|
|
217
|
+
core.debug(`Parsed CLI response keys: ${Object.keys(cliResponse).join(", ")}`);
|
|
218
|
+
core.debug(`cliResponse.content: ${cliResponse.content?.substring(0, 50)}`);
|
|
219
|
+
core.debug(`cliResponse.provider: ${cliResponse.provider}`);
|
|
220
|
+
const transformed = transformCliResponse(cliResponse);
|
|
221
|
+
core.debug(`Transformed response: ${transformed.response?.substring(0, 50)}`);
|
|
222
|
+
core.debug(`Transformed provider: ${transformed.provider}`);
|
|
223
|
+
return {
|
|
224
|
+
success: true,
|
|
225
|
+
...transformed,
|
|
226
|
+
executionTime: transformed.executionTime || executionTime,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch (parseError) {
|
|
230
|
+
// If not JSON, return raw output
|
|
231
|
+
core.debug(`JSON parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
response: stdout.trim(),
|
|
235
|
+
executionTime,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
response: "",
|
|
243
|
+
error: error instanceof Error ? error.message : String(error),
|
|
244
|
+
executionTime: Date.now() - startTime,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Run complete NeuroLink action
|
|
250
|
+
*/
|
|
251
|
+
export async function runNeurolink(inputs) {
|
|
252
|
+
let credPath;
|
|
253
|
+
try {
|
|
254
|
+
// Install CLI
|
|
255
|
+
await installNeurolink(inputs.neurolinkVersion);
|
|
256
|
+
// Build environment
|
|
257
|
+
const env = buildEnvironmentVariables(inputs);
|
|
258
|
+
// Handle Google Cloud credentials (base64 decode)
|
|
259
|
+
if (inputs.googleCloudConfig.googleApplicationCredentials) {
|
|
260
|
+
const decoded = Buffer.from(inputs.googleCloudConfig.googleApplicationCredentials, "base64").toString("utf-8");
|
|
261
|
+
credPath = path.join(process.env.RUNNER_TEMP || "/tmp", `vertex-credentials-${process.pid}-${Date.now()}.json`);
|
|
262
|
+
fs.writeFileSync(credPath, decoded, { mode: 0o600 });
|
|
263
|
+
env.GOOGLE_APPLICATION_CREDENTIALS = credPath;
|
|
264
|
+
}
|
|
265
|
+
// Build CLI arguments
|
|
266
|
+
const args = buildCliArgs(inputs);
|
|
267
|
+
core.info(`Executing: neurolink ${args.join(" ")}`);
|
|
268
|
+
if (inputs.debug) {
|
|
269
|
+
core.debug(`Working directory: ${inputs.workingDirectory}`);
|
|
270
|
+
core.debug(`Environment keys: ${Object.keys(env)
|
|
271
|
+
.filter((k) => k.includes("API") || k.includes("KEY"))
|
|
272
|
+
.join(", ")}`);
|
|
273
|
+
}
|
|
274
|
+
// Execute with timeout (use input timeout or default)
|
|
275
|
+
const executionTimeout = inputs.timeout || DEFAULT_EXECUTION_TIMEOUT_MS;
|
|
276
|
+
return await executeNeurolink(args, env, inputs.workingDirectory, executionTimeout);
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
// Clean up credential file
|
|
280
|
+
if (credPath && fs.existsSync(credPath)) {
|
|
281
|
+
try {
|
|
282
|
+
fs.unlinkSync(credPath);
|
|
283
|
+
core.debug("Cleaned up temporary credentials file");
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// Ignore cleanup errors
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Action input parsing and validation
|
|
3
|
+
* @module action/actionInputs
|
|
4
|
+
*/
|
|
5
|
+
import type { ActionInputs, ActionProviderKeys, ActionInputValidation, ActionAWSConfig, ActionGoogleCloudConfig } from "../types/actionTypes.js";
|
|
6
|
+
/**
|
|
7
|
+
* Mask all secrets in logs
|
|
8
|
+
*/
|
|
9
|
+
export declare function maskSecrets(inputs: ActionInputs): void;
|
|
10
|
+
/**
|
|
11
|
+
* Validate that provider has required API key
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateProviderKey(provider: string, keys: Partial<ActionProviderKeys>, awsConfig?: Partial<ActionAWSConfig>, googleConfig?: Partial<ActionGoogleCloudConfig>): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Parse all action inputs from GitHub Action context
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseActionInputs(): ActionInputs;
|
|
18
|
+
/**
|
|
19
|
+
* Build environment variables from action inputs
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildEnvironmentVariables(inputs: ActionInputs): Record<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Validate all action inputs
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateActionInputs(inputs: ActionInputs): ActionInputValidation;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// src/lib/action/actionInputs.ts
|
|
2
|
+
/**
|
|
3
|
+
* GitHub Action input parsing and validation
|
|
4
|
+
* @module action/actionInputs
|
|
5
|
+
*/
|
|
6
|
+
import * as core from "@actions/core";
|
|
7
|
+
import { AIProviderName } from "../constants/enums.js";
|
|
8
|
+
import { ErrorFactory } from "../utils/errorHandling.js";
|
|
9
|
+
const PROVIDER_KEY_MAP = {
|
|
10
|
+
[AIProviderName.OPENAI]: ["openaiApiKey"],
|
|
11
|
+
[AIProviderName.ANTHROPIC]: ["anthropicApiKey"],
|
|
12
|
+
[AIProviderName.GOOGLE_AI]: ["googleAiApiKey"],
|
|
13
|
+
[AIProviderName.VERTEX]: ["googleVertexProject"],
|
|
14
|
+
[AIProviderName.BEDROCK]: ["awsAccessKeyId", "awsSecretAccessKey"],
|
|
15
|
+
[AIProviderName.AZURE]: ["azureOpenaiApiKey", "azureOpenaiEndpoint"],
|
|
16
|
+
[AIProviderName.MISTRAL]: ["mistralApiKey"],
|
|
17
|
+
[AIProviderName.HUGGINGFACE]: ["huggingfaceApiKey"],
|
|
18
|
+
[AIProviderName.OPENROUTER]: ["openrouterApiKey"],
|
|
19
|
+
[AIProviderName.LITELLM]: ["litellmApiKey", "litellmBaseUrl"],
|
|
20
|
+
[AIProviderName.SAGEMAKER]: ["awsAccessKeyId", "awsSecretAccessKey"],
|
|
21
|
+
[AIProviderName.OPENAI_COMPATIBLE]: [
|
|
22
|
+
"openaiCompatibleApiKey",
|
|
23
|
+
"openaiCompatibleBaseUrl",
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Parse comma-separated paths into array
|
|
28
|
+
*/
|
|
29
|
+
function parsePathList(input) {
|
|
30
|
+
if (!input) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return input
|
|
34
|
+
.split(",")
|
|
35
|
+
.map((p) => p.trim())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse and validate a numeric input, throwing if NaN
|
|
40
|
+
*/
|
|
41
|
+
function parseNumericInput(inputName, rawValue, defaultValue, parseFunction, radix) {
|
|
42
|
+
if (!rawValue) {
|
|
43
|
+
return defaultValue;
|
|
44
|
+
}
|
|
45
|
+
const parsed = radix !== undefined
|
|
46
|
+
? parseFunction(rawValue, radix)
|
|
47
|
+
: parseFunction(rawValue);
|
|
48
|
+
if (isNaN(parsed)) {
|
|
49
|
+
throw ErrorFactory.invalidConfiguration(inputName, `expected a valid number but received "${rawValue}"`);
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Mask all secrets in logs
|
|
55
|
+
*/
|
|
56
|
+
export function maskSecrets(inputs) {
|
|
57
|
+
const secrets = [
|
|
58
|
+
inputs.providerKeys.openaiApiKey,
|
|
59
|
+
inputs.providerKeys.anthropicApiKey,
|
|
60
|
+
inputs.providerKeys.googleAiApiKey,
|
|
61
|
+
inputs.providerKeys.azureOpenaiApiKey,
|
|
62
|
+
inputs.providerKeys.mistralApiKey,
|
|
63
|
+
inputs.providerKeys.huggingfaceApiKey,
|
|
64
|
+
inputs.providerKeys.openrouterApiKey,
|
|
65
|
+
inputs.providerKeys.litellmApiKey,
|
|
66
|
+
inputs.providerKeys.openaiCompatibleApiKey,
|
|
67
|
+
inputs.awsConfig.awsAccessKeyId,
|
|
68
|
+
inputs.awsConfig.awsSecretAccessKey,
|
|
69
|
+
inputs.awsConfig.awsSessionToken,
|
|
70
|
+
inputs.googleCloudConfig.googleApplicationCredentials,
|
|
71
|
+
inputs.githubToken,
|
|
72
|
+
];
|
|
73
|
+
secrets
|
|
74
|
+
.filter((s) => Boolean(s))
|
|
75
|
+
.forEach((secret) => core.setSecret(secret));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Validate that provider has required API key
|
|
79
|
+
*/
|
|
80
|
+
export function validateProviderKey(provider, keys, awsConfig, googleConfig) {
|
|
81
|
+
// These providers don't require API keys
|
|
82
|
+
if (provider === "auto" || provider === "ollama") {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
const requiredKeys = PROVIDER_KEY_MAP[provider];
|
|
86
|
+
if (!requiredKeys) {
|
|
87
|
+
// Log warning for unknown provider, but don't fail (allows new providers)
|
|
88
|
+
core.warning(`Unknown provider "${provider}" - skipping key validation`);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
// Check keys across all config objects
|
|
92
|
+
return requiredKeys.every((key) => {
|
|
93
|
+
const keyValue = keys[key] ||
|
|
94
|
+
awsConfig?.[key] ||
|
|
95
|
+
googleConfig?.[key];
|
|
96
|
+
return !!keyValue;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse all action inputs from GitHub Action context
|
|
101
|
+
*/
|
|
102
|
+
export function parseActionInputs() {
|
|
103
|
+
const prompt = core.getInput("prompt", { required: true });
|
|
104
|
+
const provider = core.getInput("provider") || "auto";
|
|
105
|
+
// Parse provider keys (verified providers only)
|
|
106
|
+
const providerKeys = {
|
|
107
|
+
openaiApiKey: core.getInput("openai_api_key") || undefined,
|
|
108
|
+
anthropicApiKey: core.getInput("anthropic_api_key") || undefined,
|
|
109
|
+
googleAiApiKey: core.getInput("google_ai_api_key") || undefined,
|
|
110
|
+
azureOpenaiApiKey: core.getInput("azure_openai_api_key") || undefined,
|
|
111
|
+
azureOpenaiEndpoint: core.getInput("azure_openai_endpoint") || undefined,
|
|
112
|
+
azureOpenaiDeployment: core.getInput("azure_openai_deployment") || undefined,
|
|
113
|
+
mistralApiKey: core.getInput("mistral_api_key") || undefined,
|
|
114
|
+
huggingfaceApiKey: core.getInput("huggingface_api_key") || undefined,
|
|
115
|
+
openrouterApiKey: core.getInput("openrouter_api_key") || undefined,
|
|
116
|
+
litellmApiKey: core.getInput("litellm_api_key") || undefined,
|
|
117
|
+
litellmBaseUrl: core.getInput("litellm_base_url") || undefined,
|
|
118
|
+
openaiCompatibleApiKey: core.getInput("openai_compatible_api_key") || undefined,
|
|
119
|
+
openaiCompatibleBaseUrl: core.getInput("openai_compatible_base_url") || undefined,
|
|
120
|
+
};
|
|
121
|
+
// AWS config
|
|
122
|
+
const awsConfig = {
|
|
123
|
+
awsAccessKeyId: core.getInput("aws_access_key_id") || undefined,
|
|
124
|
+
awsSecretAccessKey: core.getInput("aws_secret_access_key") || undefined,
|
|
125
|
+
awsRegion: core.getInput("aws_region") || "us-east-1",
|
|
126
|
+
awsSessionToken: core.getInput("aws_session_token") || undefined,
|
|
127
|
+
bedrockModelId: core.getInput("bedrock_model_id") || undefined,
|
|
128
|
+
sagemakerEndpoint: core.getInput("sagemaker_endpoint") || undefined,
|
|
129
|
+
};
|
|
130
|
+
// Google Cloud config
|
|
131
|
+
const googleCloudConfig = {
|
|
132
|
+
googleVertexProject: core.getInput("google_vertex_project") || undefined,
|
|
133
|
+
googleVertexLocation: core.getInput("google_vertex_location") || "us-central1",
|
|
134
|
+
googleApplicationCredentials: core.getInput("google_application_credentials") || undefined,
|
|
135
|
+
};
|
|
136
|
+
// Validate provider has key
|
|
137
|
+
if (!validateProviderKey(provider, providerKeys, awsConfig, googleCloudConfig)) {
|
|
138
|
+
throw ErrorFactory.missingConfiguration(`API key for provider: ${provider}`);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
prompt,
|
|
142
|
+
provider: provider,
|
|
143
|
+
model: core.getInput("model") || undefined,
|
|
144
|
+
// Generation parameters
|
|
145
|
+
temperature: parseNumericInput("temperature", core.getInput("temperature"), 0.7, parseFloat),
|
|
146
|
+
maxTokens: parseNumericInput("max_tokens", core.getInput("max_tokens"), 4096, parseInt, 10),
|
|
147
|
+
systemPrompt: core.getInput("system_prompt") || undefined,
|
|
148
|
+
// Command
|
|
149
|
+
command: (core.getInput("command") || "generate"),
|
|
150
|
+
// Provider keys
|
|
151
|
+
providerKeys,
|
|
152
|
+
// AWS config
|
|
153
|
+
awsConfig,
|
|
154
|
+
// Google Cloud config
|
|
155
|
+
googleCloudConfig,
|
|
156
|
+
// Multimodal inputs
|
|
157
|
+
multimodal: {
|
|
158
|
+
imagePaths: parsePathList(core.getInput("image_paths")),
|
|
159
|
+
pdfPaths: parsePathList(core.getInput("pdf_paths")),
|
|
160
|
+
csvPaths: parsePathList(core.getInput("csv_paths")),
|
|
161
|
+
videoPaths: parsePathList(core.getInput("video_paths")),
|
|
162
|
+
},
|
|
163
|
+
// Extended thinking
|
|
164
|
+
thinking: {
|
|
165
|
+
enabled: core.getBooleanInput("thinking_enabled"),
|
|
166
|
+
level: (core.getInput("thinking_level") || "medium"),
|
|
167
|
+
budget: parseNumericInput("thinking_budget", core.getInput("thinking_budget"), 10000, parseInt, 10),
|
|
168
|
+
},
|
|
169
|
+
// Features (verified to exist in CLI)
|
|
170
|
+
enableAnalytics: core.getBooleanInput("enable_analytics"),
|
|
171
|
+
enableEvaluation: core.getBooleanInput("enable_evaluation"),
|
|
172
|
+
// Output
|
|
173
|
+
outputFormat: (core.getInput("output_format") || "text"),
|
|
174
|
+
outputFile: core.getInput("output_file") || undefined,
|
|
175
|
+
// MCP Tools
|
|
176
|
+
enableTools: core.getBooleanInput("enable_tools"),
|
|
177
|
+
mcpConfigPath: core.getInput("mcp_config_path") || undefined,
|
|
178
|
+
// GitHub Integration
|
|
179
|
+
postComment: core.getBooleanInput("post_comment"),
|
|
180
|
+
updateExistingComment: core.getBooleanInput("update_existing_comment"),
|
|
181
|
+
commentTag: core.getInput("comment_tag") || "neurolink-action",
|
|
182
|
+
githubToken: core.getInput("github_token") || process.env.GITHUB_TOKEN,
|
|
183
|
+
// Advanced
|
|
184
|
+
timeout: parseNumericInput("timeout", core.getInput("timeout"), 300, parseInt, 10),
|
|
185
|
+
debug: core.getBooleanInput("debug"),
|
|
186
|
+
neurolinkVersion: core.getInput("neurolink_version") || "latest",
|
|
187
|
+
workingDirectory: core.getInput("working_directory") || ".",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Build environment variables from action inputs
|
|
192
|
+
*/
|
|
193
|
+
export function buildEnvironmentVariables(inputs) {
|
|
194
|
+
const env = {
|
|
195
|
+
CI: "true",
|
|
196
|
+
NEUROLINK_NON_INTERACTIVE: "true",
|
|
197
|
+
};
|
|
198
|
+
// Provider keys
|
|
199
|
+
const { providerKeys } = inputs;
|
|
200
|
+
if (providerKeys.openaiApiKey) {
|
|
201
|
+
env.OPENAI_API_KEY = providerKeys.openaiApiKey;
|
|
202
|
+
}
|
|
203
|
+
if (providerKeys.anthropicApiKey) {
|
|
204
|
+
env.ANTHROPIC_API_KEY = providerKeys.anthropicApiKey;
|
|
205
|
+
}
|
|
206
|
+
if (providerKeys.googleAiApiKey) {
|
|
207
|
+
env.GOOGLE_AI_API_KEY = providerKeys.googleAiApiKey;
|
|
208
|
+
}
|
|
209
|
+
if (providerKeys.azureOpenaiApiKey) {
|
|
210
|
+
env.AZURE_OPENAI_API_KEY = providerKeys.azureOpenaiApiKey;
|
|
211
|
+
}
|
|
212
|
+
if (providerKeys.azureOpenaiEndpoint) {
|
|
213
|
+
env.AZURE_OPENAI_ENDPOINT = providerKeys.azureOpenaiEndpoint;
|
|
214
|
+
}
|
|
215
|
+
if (providerKeys.azureOpenaiDeployment) {
|
|
216
|
+
env.AZURE_OPENAI_DEPLOYMENT = providerKeys.azureOpenaiDeployment;
|
|
217
|
+
}
|
|
218
|
+
if (providerKeys.mistralApiKey) {
|
|
219
|
+
env.MISTRAL_API_KEY = providerKeys.mistralApiKey;
|
|
220
|
+
}
|
|
221
|
+
if (providerKeys.huggingfaceApiKey) {
|
|
222
|
+
env.HUGGINGFACE_API_KEY = providerKeys.huggingfaceApiKey;
|
|
223
|
+
}
|
|
224
|
+
if (providerKeys.openrouterApiKey) {
|
|
225
|
+
env.OPENROUTER_API_KEY = providerKeys.openrouterApiKey;
|
|
226
|
+
}
|
|
227
|
+
if (providerKeys.litellmApiKey) {
|
|
228
|
+
env.LITELLM_API_KEY = providerKeys.litellmApiKey;
|
|
229
|
+
}
|
|
230
|
+
if (providerKeys.litellmBaseUrl) {
|
|
231
|
+
env.LITELLM_BASE_URL = providerKeys.litellmBaseUrl;
|
|
232
|
+
}
|
|
233
|
+
if (providerKeys.openaiCompatibleApiKey) {
|
|
234
|
+
env.OPENAI_COMPATIBLE_API_KEY = providerKeys.openaiCompatibleApiKey;
|
|
235
|
+
}
|
|
236
|
+
if (providerKeys.openaiCompatibleBaseUrl) {
|
|
237
|
+
env.OPENAI_COMPATIBLE_BASE_URL = providerKeys.openaiCompatibleBaseUrl;
|
|
238
|
+
}
|
|
239
|
+
// AWS
|
|
240
|
+
const { awsConfig } = inputs;
|
|
241
|
+
if (awsConfig.awsAccessKeyId) {
|
|
242
|
+
env.AWS_ACCESS_KEY_ID = awsConfig.awsAccessKeyId;
|
|
243
|
+
}
|
|
244
|
+
if (awsConfig.awsSecretAccessKey) {
|
|
245
|
+
env.AWS_SECRET_ACCESS_KEY = awsConfig.awsSecretAccessKey;
|
|
246
|
+
}
|
|
247
|
+
env.AWS_REGION = awsConfig.awsRegion;
|
|
248
|
+
if (awsConfig.awsSessionToken) {
|
|
249
|
+
env.AWS_SESSION_TOKEN = awsConfig.awsSessionToken;
|
|
250
|
+
}
|
|
251
|
+
if (awsConfig.bedrockModelId) {
|
|
252
|
+
env.BEDROCK_MODEL_ID = awsConfig.bedrockModelId;
|
|
253
|
+
}
|
|
254
|
+
if (awsConfig.sagemakerEndpoint) {
|
|
255
|
+
env.SAGEMAKER_DEFAULT_ENDPOINT = awsConfig.sagemakerEndpoint;
|
|
256
|
+
}
|
|
257
|
+
// Google Cloud
|
|
258
|
+
const { googleCloudConfig } = inputs;
|
|
259
|
+
if (googleCloudConfig.googleVertexProject) {
|
|
260
|
+
env.GOOGLE_VERTEX_PROJECT = googleCloudConfig.googleVertexProject;
|
|
261
|
+
}
|
|
262
|
+
env.GOOGLE_VERTEX_LOCATION = googleCloudConfig.googleVertexLocation;
|
|
263
|
+
// GitHub
|
|
264
|
+
if (inputs.githubToken) {
|
|
265
|
+
env.GITHUB_TOKEN = inputs.githubToken;
|
|
266
|
+
}
|
|
267
|
+
return env;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Validate all action inputs
|
|
271
|
+
*/
|
|
272
|
+
export function validateActionInputs(inputs) {
|
|
273
|
+
const errors = [];
|
|
274
|
+
const warnings = [];
|
|
275
|
+
// Required fields
|
|
276
|
+
if (!inputs.prompt) {
|
|
277
|
+
errors.push("prompt is required");
|
|
278
|
+
}
|
|
279
|
+
// Temperature range
|
|
280
|
+
if (inputs.temperature < 0 || inputs.temperature > 2) {
|
|
281
|
+
warnings.push("temperature should be between 0 and 2");
|
|
282
|
+
}
|
|
283
|
+
// Thinking budget range
|
|
284
|
+
if (inputs.thinking.enabled &&
|
|
285
|
+
(inputs.thinking.budget < 5000 || inputs.thinking.budget > 100000)) {
|
|
286
|
+
warnings.push("thinking_budget should be between 5000 and 100000");
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
valid: errors.length === 0,
|
|
290
|
+
errors,
|
|
291
|
+
warnings,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub API integration for comments, outputs, and job summary
|
|
3
|
+
* @module action/githubIntegration
|
|
4
|
+
*/
|
|
5
|
+
import type { ActionInputs, ActionExecutionResult, ActionCommentResult, ActionOutput } from "../types/actionTypes.js";
|
|
6
|
+
/**
|
|
7
|
+
* Post result as comment on PR or issue
|
|
8
|
+
*/
|
|
9
|
+
export declare function postResultComment(inputs: ActionInputs, result: ActionExecutionResult): Promise<ActionCommentResult>;
|
|
10
|
+
/**
|
|
11
|
+
* Write job summary
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeJobSummary(inputs: ActionInputs, result: ActionExecutionResult): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Set all action outputs
|
|
16
|
+
*/
|
|
17
|
+
export declare function setActionOutputs(result: ActionExecutionResult, commentResult?: ActionCommentResult): void;
|
|
18
|
+
/**
|
|
19
|
+
* Get all outputs as typed object (snake_case to match action.yml outputs)
|
|
20
|
+
*/
|
|
21
|
+
export declare function getActionOutputs(result: ActionExecutionResult, commentResult?: ActionCommentResult): ActionOutput;
|