neuron-inspector 1.2.1 → 1.3.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/dist/collections/assertions.js +11 -0
- package/dist/collections/assertions.js.map +1 -1
- package/dist/collections/dynamic-vars-core.d.ts +79 -0
- package/dist/collections/dynamic-vars-core.js +214 -0
- package/dist/collections/dynamic-vars-core.js.map +1 -0
- package/dist/collections/dynamic-vars.d.ts +2 -61
- package/dist/collections/dynamic-vars.js +6 -146
- package/dist/collections/dynamic-vars.js.map +1 -1
- package/dist/collections/encoding.d.ts +3 -0
- package/dist/collections/encoding.js +20 -0
- package/dist/collections/encoding.js.map +1 -0
- package/dist/collections/executor.d.ts +114 -5
- package/dist/collections/executor.js +315 -91
- package/dist/collections/executor.js.map +1 -1
- package/dist/collections/index.d.ts +11 -3
- package/dist/collections/index.js +7 -1
- package/dist/collections/index.js.map +1 -1
- package/dist/collections/masking.d.ts +42 -0
- package/dist/collections/masking.js +188 -0
- package/dist/collections/masking.js.map +1 -0
- package/dist/collections/resolver-core.d.ts +74 -0
- package/dist/collections/resolver-core.js +309 -0
- package/dist/collections/resolver-core.js.map +1 -0
- package/dist/collections/resolver.d.ts +9 -62
- package/dist/collections/resolver.js +21 -281
- package/dist/collections/resolver.js.map +1 -1
- package/dist/collections/scenario-run.d.ts +33 -0
- package/dist/collections/scenario-run.js +68 -0
- package/dist/collections/scenario-run.js.map +1 -0
- package/dist/collections/script-api.d.ts +18 -0
- package/dist/collections/script-api.js +208 -0
- package/dist/collections/script-api.js.map +1 -0
- package/dist/collections/scripts.d.ts +1 -8
- package/dist/collections/scripts.js +6 -196
- package/dist/collections/scripts.js.map +1 -1
- package/dist/collections/slug.d.ts +2 -0
- package/dist/collections/slug.js +8 -0
- package/dist/collections/slug.js.map +1 -0
- package/dist/collections/store.d.ts +5 -2
- package/dist/collections/store.js +6 -6
- package/dist/collections/store.js.map +1 -1
- package/dist/collections/tools.d.ts +8 -1
- package/dist/collections/tools.js +253 -90
- package/dist/collections/tools.js.map +1 -1
- package/dist/collections/types.d.ts +42 -3
- package/dist/correlation.js +10 -0
- package/dist/correlation.js.map +1 -1
- package/dist/extension-slot.d.ts +45 -0
- package/dist/extension-slot.js +81 -0
- package/dist/extension-slot.js.map +1 -0
- package/dist/monitor.d.ts +5 -0
- package/dist/monitor.js +7 -2
- package/dist/monitor.js.map +1 -1
- package/dist/server.js +113 -25
- package/dist/server.js.map +1 -1
- package/dist/tools.js +1 -1
- package/dist/tools.js.map +1 -1
- package/package.json +3 -2
- package/dist/collections/__tests__/assertions.test.d.ts +0 -1
- package/dist/collections/__tests__/assertions.test.js +0 -203
- package/dist/collections/__tests__/assertions.test.js.map +0 -1
- package/dist/collections/__tests__/dynamic-vars.test.d.ts +0 -1
- package/dist/collections/__tests__/dynamic-vars.test.js +0 -169
- package/dist/collections/__tests__/dynamic-vars.test.js.map +0 -1
- package/dist/collections/__tests__/execution.test.d.ts +0 -1
- package/dist/collections/__tests__/execution.test.js +0 -271
- package/dist/collections/__tests__/execution.test.js.map +0 -1
- package/dist/collections/__tests__/integration.test.d.ts +0 -1
- package/dist/collections/__tests__/integration.test.js +0 -478
- package/dist/collections/__tests__/integration.test.js.map +0 -1
- package/dist/collections/__tests__/merge.test.d.ts +0 -1
- package/dist/collections/__tests__/merge.test.js +0 -411
- package/dist/collections/__tests__/merge.test.js.map +0 -1
- package/dist/collections/__tests__/resolver.test.d.ts +0 -1
- package/dist/collections/__tests__/resolver.test.js +0 -282
- package/dist/collections/__tests__/resolver.test.js.map +0 -1
- package/dist/collections/__tests__/scenario.test.d.ts +0 -1
- package/dist/collections/__tests__/scenario.test.js +0 -367
- package/dist/collections/__tests__/scenario.test.js.map +0 -1
- package/dist/collections/__tests__/scripts.test.d.ts +0 -1
- package/dist/collections/__tests__/scripts.test.js +0 -245
- package/dist/collections/__tests__/scripts.test.js.map +0 -1
- package/dist/collections/__tests__/store.test.d.ts +0 -1
- package/dist/collections/__tests__/store.test.js +0 -313
- package/dist/collections/__tests__/store.test.js.map +0 -1
|
@@ -1,9 +1,103 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import * as YAML from "yaml";
|
|
3
3
|
import { slugify } from "./store.js";
|
|
4
|
-
import { executeEndpoint, executeFolder, executeCollection,
|
|
4
|
+
import { executeEndpoint, executeFolder, executeCollection, resolveStepRef, normalizePoll } from "./executor.js";
|
|
5
|
+
import { assertTimeZone } from "./dynamic-vars.js";
|
|
6
|
+
import { pickEnvironment, runInputsFor, runScenario } from "./scenario-run.js";
|
|
7
|
+
import { sanitizeInputDeclarations, mergeInputDeclarations, prepareInputs, describeInputs, SecretMasker } from "./masking.js";
|
|
5
8
|
import { fromPostmanV21, toPostmanV21 } from "./postman.js";
|
|
6
9
|
import { mergeCollections } from "./merge.js";
|
|
10
|
+
const INPUTS_SCHEMA = {
|
|
11
|
+
type: "array",
|
|
12
|
+
description: "Values supplied at run time, used as {{name}}. Declarations only: never put a value here. " +
|
|
13
|
+
"Secret inputs are kept in memory for one run and shown as ••• in every result.",
|
|
14
|
+
items: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
name: { type: "string" },
|
|
18
|
+
label: { type: "string" },
|
|
19
|
+
description: { type: "string" },
|
|
20
|
+
secret: { type: "boolean", description: "Mask the value everywhere and never store it" },
|
|
21
|
+
required: { type: "boolean", description: "Default true" },
|
|
22
|
+
default: { type: "string", description: "Fallback for non-secret inputs only" },
|
|
23
|
+
},
|
|
24
|
+
required: ["name"],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const RUN_INPUT_VALUES_SCHEMA = {
|
|
28
|
+
type: "object",
|
|
29
|
+
description: "Values for the declared inputs, e.g. { client_id: '…', client_secret: '…' }. " +
|
|
30
|
+
"Held in memory for this run only; values of secret inputs are masked as ••• in the result.",
|
|
31
|
+
};
|
|
32
|
+
const STEP_SCHEMA = {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
ref: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Endpoint reference: 'folder/<endpoint name>' or 'folder/METHOD /path' (method case-insensitive)",
|
|
38
|
+
},
|
|
39
|
+
overrides: {
|
|
40
|
+
type: "object",
|
|
41
|
+
description: "Per-step overrides for headers, body, tests, path, variables",
|
|
42
|
+
properties: {
|
|
43
|
+
headers: { type: "object" },
|
|
44
|
+
body: {},
|
|
45
|
+
tests: { type: "array" },
|
|
46
|
+
path: { type: "string" },
|
|
47
|
+
variables: { type: "object" },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
poll: {
|
|
51
|
+
type: "object",
|
|
52
|
+
description: "Repeat this request until every 'until' assertion passes. Each attempt is recorded. " +
|
|
53
|
+
"Fails the step (and halts the scenario) if max_attempts runs out.",
|
|
54
|
+
properties: {
|
|
55
|
+
until: { type: "array", description: "Assertions: [{ check, op, expect }]. op 'in' takes an array expect." },
|
|
56
|
+
interval_ms: { type: "number", description: "Wait between attempts (default 2000, 100–60000)" },
|
|
57
|
+
max_attempts: { type: "number", description: "Default 10, max 120" },
|
|
58
|
+
},
|
|
59
|
+
required: ["until"],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ["ref"],
|
|
63
|
+
};
|
|
64
|
+
const COLLECTION_DATA_SCHEMA = {
|
|
65
|
+
type: "object",
|
|
66
|
+
description: "Run this collection object instead of loading one from disk (used by the extension for its local collections)",
|
|
67
|
+
};
|
|
68
|
+
/** Step refs that won't resolve, so a scenario can be fixed before it runs. */
|
|
69
|
+
function checkStepRefs(collection, steps) {
|
|
70
|
+
const problems = [];
|
|
71
|
+
steps.forEach((step, i) => {
|
|
72
|
+
const r = resolveStepRef(collection, step.ref);
|
|
73
|
+
if ("error" in r)
|
|
74
|
+
problems.push(`step ${i + 1}: ${r.error}`);
|
|
75
|
+
if (step.poll) {
|
|
76
|
+
try {
|
|
77
|
+
normalizePoll(step.poll);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
problems.push(`step ${i + 1}: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return problems;
|
|
85
|
+
}
|
|
86
|
+
function validateSteps(raw) {
|
|
87
|
+
if (!Array.isArray(raw))
|
|
88
|
+
throw new Error("steps must be an array");
|
|
89
|
+
return raw.map((step, i) => {
|
|
90
|
+
if (!step || typeof step !== "object" || typeof step.ref !== "string") {
|
|
91
|
+
throw new Error(`step ${i + 1} needs a 'ref'`);
|
|
92
|
+
}
|
|
93
|
+
const s = step;
|
|
94
|
+
if (s.poll !== undefined)
|
|
95
|
+
normalizePoll(s.poll);
|
|
96
|
+
return s;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** Cancellation flags for runs started with a run_id. */
|
|
100
|
+
const activeRuns = new Map();
|
|
7
101
|
export function buildCollectionTools() {
|
|
8
102
|
return [
|
|
9
103
|
{
|
|
@@ -123,7 +217,8 @@ export function buildCollectionTools() {
|
|
|
123
217
|
},
|
|
124
218
|
extract: {
|
|
125
219
|
type: "object",
|
|
126
|
-
description: "Variables to extract from the response, e.g. { bot_id: 'body.id' }"
|
|
220
|
+
description: "Variables to extract from the response, e.g. { bot_id: 'body.id' }. " +
|
|
221
|
+
"Use { access_token: { path: 'body.access_token', mask: true } } to show the value as ••• in every result for the rest of the run.",
|
|
127
222
|
},
|
|
128
223
|
body_mode: {
|
|
129
224
|
type: "string",
|
|
@@ -263,6 +358,8 @@ export function buildCollectionTools() {
|
|
|
263
358
|
variables: { type: "object", description: "Environment variables (key-value pairs)" },
|
|
264
359
|
headers: { type: "object", description: "Environment-specific header overrides" },
|
|
265
360
|
timeout_ms: { type: "number", description: "Environment-specific timeout" },
|
|
361
|
+
inputs: INPUTS_SCHEMA,
|
|
362
|
+
timezone: { type: "string", description: "IANA zone for {{$today}} and formatted {{$now}}, e.g. 'Africa/Lagos' (default: defaults.timezone, else UTC)" },
|
|
266
363
|
},
|
|
267
364
|
required: ["collection", "name", "base_url"],
|
|
268
365
|
},
|
|
@@ -321,6 +418,7 @@ export function buildCollectionTools() {
|
|
|
321
418
|
{
|
|
322
419
|
name: "neuron_api_set_defaults",
|
|
323
420
|
description: "Set or update collection-level defaults. Merges with existing defaults. " +
|
|
421
|
+
"pre_request / post_response scripts here run before each endpoint's own script (same sandbox and nr API), so endpoints can override what they set. " +
|
|
324
422
|
"Pass null for auth to clear it. Omit fields to leave them unchanged.",
|
|
325
423
|
inputSchema: {
|
|
326
424
|
type: "object",
|
|
@@ -328,6 +426,7 @@ export function buildCollectionTools() {
|
|
|
328
426
|
collection: { type: "string", description: "Collection id" },
|
|
329
427
|
headers: { type: "object", description: "Default headers (key-value pairs)" },
|
|
330
428
|
variables: { type: "object", description: "Default variables (key-value pairs)" },
|
|
429
|
+
timezone: { type: "string", description: "IANA zone for {{$today}} and formatted {{$now}}, e.g. 'Africa/Lagos' (default UTC). An environment's timezone wins." },
|
|
331
430
|
timeout_ms: { type: "number", description: "Default timeout (ms)" },
|
|
332
431
|
auth: {
|
|
333
432
|
type: "object",
|
|
@@ -444,34 +543,20 @@ export function buildCollectionTools() {
|
|
|
444
543
|
// ── Scenario management ───────────────────────────────────
|
|
445
544
|
{
|
|
446
545
|
name: "neuron_api_create_scenario",
|
|
447
|
-
description: "Create a new scenario in a collection. Steps reference endpoints as 'folder
|
|
546
|
+
description: "Create a new scenario in a collection. Steps reference endpoints as 'folder/<endpoint name>' or " +
|
|
547
|
+
"'folder/METHOD /path' (e.g. 'auth/POST /login'). A step can poll until assertions pass. " +
|
|
548
|
+
"Declare run-time values (client ids, secrets) in 'inputs' and pass them when you run.",
|
|
448
549
|
inputSchema: {
|
|
449
550
|
type: "object",
|
|
450
551
|
properties: {
|
|
451
552
|
collection: { type: "string", description: "Collection id" },
|
|
452
553
|
name: { type: "string", description: "Scenario name" },
|
|
453
554
|
description: { type: "string", description: "Scenario description" },
|
|
555
|
+
inputs: INPUTS_SCHEMA,
|
|
454
556
|
steps: {
|
|
455
557
|
type: "array",
|
|
456
|
-
description: "Ordered steps. Each step has a 'ref'
|
|
457
|
-
items:
|
|
458
|
-
type: "object",
|
|
459
|
-
properties: {
|
|
460
|
-
ref: { type: "string", description: "Endpoint reference: 'folder/METHOD path'" },
|
|
461
|
-
overrides: {
|
|
462
|
-
type: "object",
|
|
463
|
-
description: "Per-step overrides for headers, body, tests, path, variables",
|
|
464
|
-
properties: {
|
|
465
|
-
headers: { type: "object" },
|
|
466
|
-
body: {},
|
|
467
|
-
tests: { type: "array" },
|
|
468
|
-
path: { type: "string" },
|
|
469
|
-
variables: { type: "object" },
|
|
470
|
-
},
|
|
471
|
-
},
|
|
472
|
-
},
|
|
473
|
-
required: ["ref"],
|
|
474
|
-
},
|
|
558
|
+
description: "Ordered steps. Each step has a 'ref', optional overrides and an optional poll.",
|
|
559
|
+
items: STEP_SCHEMA,
|
|
475
560
|
},
|
|
476
561
|
},
|
|
477
562
|
required: ["collection", "name", "steps"],
|
|
@@ -479,24 +564,18 @@ export function buildCollectionTools() {
|
|
|
479
564
|
},
|
|
480
565
|
{
|
|
481
566
|
name: "neuron_api_update_scenario",
|
|
482
|
-
description: "Update a scenario's description or steps.",
|
|
567
|
+
description: "Update a scenario's description, inputs or steps.",
|
|
483
568
|
inputSchema: {
|
|
484
569
|
type: "object",
|
|
485
570
|
properties: {
|
|
486
571
|
collection: { type: "string", description: "Collection id" },
|
|
487
572
|
scenario: { type: "string", description: "Scenario name" },
|
|
488
573
|
description: { type: "string", description: "New description" },
|
|
574
|
+
inputs: INPUTS_SCHEMA,
|
|
489
575
|
steps: {
|
|
490
576
|
type: "array",
|
|
491
577
|
description: "New ordered steps (replaces existing)",
|
|
492
|
-
items:
|
|
493
|
-
type: "object",
|
|
494
|
-
properties: {
|
|
495
|
-
ref: { type: "string" },
|
|
496
|
-
overrides: { type: "object" },
|
|
497
|
-
},
|
|
498
|
-
required: ["ref"],
|
|
499
|
-
},
|
|
578
|
+
items: STEP_SCHEMA,
|
|
500
579
|
},
|
|
501
580
|
},
|
|
502
581
|
required: ["collection", "scenario"],
|
|
@@ -551,6 +630,7 @@ export function buildCollectionTools() {
|
|
|
551
630
|
follow_authorization: { type: "boolean", description: "Override: retain Authorization on cross-host redirects" },
|
|
552
631
|
remove_referer: { type: "boolean", description: "Override: remove Referer header on redirect" },
|
|
553
632
|
encode_url: { type: "boolean", description: "Override: encode URL automatically" },
|
|
633
|
+
inputs: RUN_INPUT_VALUES_SCHEMA,
|
|
554
634
|
},
|
|
555
635
|
required: ["collection"],
|
|
556
636
|
},
|
|
@@ -559,16 +639,44 @@ export function buildCollectionTools() {
|
|
|
559
639
|
name: "neuron_api_run_scenario",
|
|
560
640
|
description: "Run a named scenario: an ordered sequence of endpoint calls with variable chaining. " +
|
|
561
641
|
"Extracted variables from each step are available to subsequent steps via {{variable}} references. " +
|
|
562
|
-
"Execution halts on the first step that fails (HTTP error or
|
|
563
|
-
"Steps can include overrides for headers, body, tests, path, and variables."
|
|
642
|
+
"Execution halts on the first step that fails (HTTP error, assertion failure, or a poll that never succeeds). " +
|
|
643
|
+
"Steps can include overrides for headers, body, tests, path, and variables, and a poll. " +
|
|
644
|
+
"Pass values for the scenario's/environment's declared inputs in 'inputs'; secret ones are masked as ••• in the result. " +
|
|
645
|
+
"The environment you pass is the one used, whatever the collection's active environment is.",
|
|
564
646
|
inputSchema: {
|
|
565
647
|
type: "object",
|
|
566
648
|
properties: {
|
|
567
649
|
collection: { type: "string", description: "Collection id" },
|
|
568
650
|
scenario: { type: "string", description: "Scenario name (key under 'scenarios' in the collection)" },
|
|
569
651
|
environment: { type: "string", description: "Environment name (defaults to active_environment)" },
|
|
652
|
+
inputs: RUN_INPUT_VALUES_SCHEMA,
|
|
653
|
+
run_id: { type: "string", description: "Optional id so the run can be cancelled with neuron_api_cancel_run" },
|
|
654
|
+
collection_data: COLLECTION_DATA_SCHEMA,
|
|
570
655
|
},
|
|
571
|
-
required: ["
|
|
656
|
+
required: ["scenario"],
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
name: "neuron_api_get_run_inputs",
|
|
661
|
+
description: "List the inputs a scenario run will ask for (environment inputs merged with the scenario's). " +
|
|
662
|
+
"Returns declarations only (name, label, secret, required), never values.",
|
|
663
|
+
inputSchema: {
|
|
664
|
+
type: "object",
|
|
665
|
+
properties: {
|
|
666
|
+
collection: { type: "string", description: "Collection id" },
|
|
667
|
+
scenario: { type: "string", description: "Scenario name (omit for environment inputs only)" },
|
|
668
|
+
environment: { type: "string", description: "Environment name (defaults to active_environment)" },
|
|
669
|
+
collection_data: COLLECTION_DATA_SCHEMA,
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
name: "neuron_api_cancel_run",
|
|
675
|
+
description: "Stop a scenario run started with a run_id. It halts before its next request or poll attempt.",
|
|
676
|
+
inputSchema: {
|
|
677
|
+
type: "object",
|
|
678
|
+
properties: { run_id: { type: "string" } },
|
|
679
|
+
required: ["run_id"],
|
|
572
680
|
},
|
|
573
681
|
},
|
|
574
682
|
// ── Query and generation ─────────────────────────────────
|
|
@@ -693,7 +801,24 @@ export function buildCollectionTools() {
|
|
|
693
801
|
}
|
|
694
802
|
// ── Handler ─────────────────────────────────────────────────────
|
|
695
803
|
export function buildCollectionHandler(store, httpExecutor, pipeline, assertionEvaluator, scriptRunner) {
|
|
696
|
-
|
|
804
|
+
/** Load the collection a run targets: inline data from the caller, else the store. */
|
|
805
|
+
async function loadForRun(args) {
|
|
806
|
+
if (args.collection_data !== undefined) {
|
|
807
|
+
const data = args.collection_data;
|
|
808
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
809
|
+
return { error: "collection_data must be a collection object" };
|
|
810
|
+
const c = data;
|
|
811
|
+
return { id: args.collection || slugify(c.name || "inline"), collection: c };
|
|
812
|
+
}
|
|
813
|
+
const id = args.collection;
|
|
814
|
+
if (!id)
|
|
815
|
+
return { error: "Provide 'collection' (id) or 'collection_data'" };
|
|
816
|
+
const result = await store.get(id);
|
|
817
|
+
if (!result)
|
|
818
|
+
return { error: `Collection '${id}' not found` };
|
|
819
|
+
return { id, collection: result.collection };
|
|
820
|
+
}
|
|
821
|
+
return async function handleCollectionTool(name, args, hooks) {
|
|
697
822
|
switch (name) {
|
|
698
823
|
case "neuron_api_list_collections": {
|
|
699
824
|
return store.list();
|
|
@@ -785,6 +910,13 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
785
910
|
env.headers = args.headers;
|
|
786
911
|
if (args.timeout_ms)
|
|
787
912
|
env.timeout_ms = args.timeout_ms;
|
|
913
|
+
if (typeof args.timezone === "string" && args.timezone) {
|
|
914
|
+
assertTimeZone(args.timezone);
|
|
915
|
+
env.timezone = args.timezone;
|
|
916
|
+
}
|
|
917
|
+
const envInputs = sanitizeInputDeclarations(args.inputs);
|
|
918
|
+
if (envInputs)
|
|
919
|
+
env.inputs = envInputs;
|
|
788
920
|
await store.setEnvironment(id, envName, env);
|
|
789
921
|
return { set: true, collection: id, environment: slugify(envName) };
|
|
790
922
|
}
|
|
@@ -837,6 +969,8 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
837
969
|
const { collection: _c, ...defaults } = args;
|
|
838
970
|
if (Object.keys(defaults).length === 0)
|
|
839
971
|
return { error: "No defaults provided" };
|
|
972
|
+
if (typeof defaults.timezone === "string" && defaults.timezone)
|
|
973
|
+
assertTimeZone(defaults.timezone);
|
|
840
974
|
await store.setDefaults(id, defaults);
|
|
841
975
|
return { set: true, collection: id, fields: Object.keys(defaults) };
|
|
842
976
|
}
|
|
@@ -901,12 +1035,22 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
901
1035
|
const id = args.collection;
|
|
902
1036
|
const scenarioName = args.name;
|
|
903
1037
|
const scenario = {
|
|
904
|
-
steps: args.steps,
|
|
1038
|
+
steps: validateSteps(args.steps),
|
|
905
1039
|
};
|
|
906
1040
|
if (args.description)
|
|
907
1041
|
scenario.description = args.description;
|
|
908
|
-
|
|
909
|
-
|
|
1042
|
+
const scenarioInputs = sanitizeInputDeclarations(args.inputs);
|
|
1043
|
+
if (scenarioInputs)
|
|
1044
|
+
scenario.inputs = scenarioInputs;
|
|
1045
|
+
const updated = await store.createScenario(id, scenarioName, scenario);
|
|
1046
|
+
const warnings = checkStepRefs(updated, scenario.steps);
|
|
1047
|
+
return {
|
|
1048
|
+
created: true,
|
|
1049
|
+
collection: id,
|
|
1050
|
+
scenario: scenarioName,
|
|
1051
|
+
step_count: scenario.steps.length,
|
|
1052
|
+
...(warnings.length ? { warnings } : {}),
|
|
1053
|
+
};
|
|
910
1054
|
}
|
|
911
1055
|
case "neuron_api_update_scenario": {
|
|
912
1056
|
const id = args.collection;
|
|
@@ -915,11 +1059,14 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
915
1059
|
if (args.description !== undefined)
|
|
916
1060
|
updates.description = args.description;
|
|
917
1061
|
if (args.steps !== undefined)
|
|
918
|
-
updates.steps = args.steps;
|
|
1062
|
+
updates.steps = validateSteps(args.steps);
|
|
1063
|
+
if (args.inputs !== undefined)
|
|
1064
|
+
updates.inputs = sanitizeInputDeclarations(args.inputs) ?? [];
|
|
919
1065
|
if (Object.keys(updates).length === 0)
|
|
920
1066
|
return { error: "No updates provided" };
|
|
921
|
-
await store.updateScenario(id, scenarioName, updates);
|
|
922
|
-
|
|
1067
|
+
const updated = await store.updateScenario(id, scenarioName, updates);
|
|
1068
|
+
const warnings = checkStepRefs(updated, updated.scenarios?.[scenarioName]?.steps ?? []);
|
|
1069
|
+
return { updated: true, collection: id, scenario: scenarioName, ...(warnings.length ? { warnings } : {}) };
|
|
923
1070
|
}
|
|
924
1071
|
case "neuron_api_delete_scenario": {
|
|
925
1072
|
const id = args.collection;
|
|
@@ -938,6 +1085,7 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
938
1085
|
name: key,
|
|
939
1086
|
description: s.description ?? null,
|
|
940
1087
|
step_count: s.steps?.length ?? 0,
|
|
1088
|
+
inputs: describeInputs(s.inputs ?? []),
|
|
941
1089
|
})),
|
|
942
1090
|
};
|
|
943
1091
|
}
|
|
@@ -946,25 +1094,26 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
946
1094
|
if (!httpExecutor || !pipeline || !assertionEvaluator) {
|
|
947
1095
|
return { error: "HTTP executor not configured" };
|
|
948
1096
|
}
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
return { error: `Environment '${envName}' not found in collection '${id}'` };
|
|
1097
|
+
const loaded = await loadForRun(args);
|
|
1098
|
+
if ("error" in loaded)
|
|
1099
|
+
return loaded;
|
|
1100
|
+
const { id, collection } = loaded;
|
|
1101
|
+
const picked = pickEnvironment(collection, id, args.environment);
|
|
1102
|
+
if ("error" in picked)
|
|
1103
|
+
return picked;
|
|
1104
|
+
const { key: envKey, environment } = picked;
|
|
1105
|
+
const declared = mergeInputDeclarations(environment.inputs);
|
|
1106
|
+
const prepared = prepareInputs(declared, args.inputs);
|
|
1107
|
+
if (prepared.missing.length) {
|
|
1108
|
+
return { error: `Missing required input(s): ${prepared.missing.join(", ")}`, inputs: describeInputs(declared) };
|
|
962
1109
|
}
|
|
1110
|
+
const masker = new SecretMasker(prepared.secrets);
|
|
1111
|
+
const runCtx = { environmentName: envKey, inputs: prepared.values, onSecret: (v) => masker.add(v) };
|
|
963
1112
|
const hasFolder = typeof args.folder === "string";
|
|
964
1113
|
const hasEndpoint = typeof args.method === "string" && typeof args.path === "string";
|
|
965
1114
|
// Mode 3: Collection-wide run (no folder specified)
|
|
966
1115
|
if (!hasFolder) {
|
|
967
|
-
return executeCollection({
|
|
1116
|
+
return masker.mask(await executeCollection({
|
|
968
1117
|
collectionId: id,
|
|
969
1118
|
collection,
|
|
970
1119
|
environment,
|
|
@@ -972,16 +1121,17 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
972
1121
|
pipeline,
|
|
973
1122
|
assertionEvaluator,
|
|
974
1123
|
scriptRunner,
|
|
975
|
-
|
|
1124
|
+
...runCtx,
|
|
1125
|
+
}));
|
|
976
1126
|
}
|
|
977
1127
|
const folderKey = slugify(args.folder);
|
|
978
|
-
const folder = collection.folders?.[folderKey];
|
|
1128
|
+
const folder = collection.folders?.[folderKey] ?? collection.folders?.[args.folder];
|
|
979
1129
|
if (!folder) {
|
|
980
1130
|
return { error: `Folder '${args.folder}' not found in collection '${id}'` };
|
|
981
1131
|
}
|
|
982
1132
|
// Mode 2: Folder run (folder but no method/path)
|
|
983
1133
|
if (!hasEndpoint) {
|
|
984
|
-
return executeFolder({
|
|
1134
|
+
return masker.mask(await executeFolder({
|
|
985
1135
|
folderKey,
|
|
986
1136
|
collection,
|
|
987
1137
|
environment,
|
|
@@ -990,7 +1140,8 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
990
1140
|
pipeline,
|
|
991
1141
|
assertionEvaluator,
|
|
992
1142
|
scriptRunner,
|
|
993
|
-
|
|
1143
|
+
...runCtx,
|
|
1144
|
+
}));
|
|
994
1145
|
}
|
|
995
1146
|
// Mode 1: Single endpoint
|
|
996
1147
|
const method = args.method.toUpperCase();
|
|
@@ -1023,7 +1174,7 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
1023
1174
|
runSettings.encode_url = args.encode_url;
|
|
1024
1175
|
endpoint.settings = runSettings;
|
|
1025
1176
|
}
|
|
1026
|
-
return executeEndpoint({
|
|
1177
|
+
return masker.mask(await executeEndpoint({
|
|
1027
1178
|
collection,
|
|
1028
1179
|
environment,
|
|
1029
1180
|
folder,
|
|
@@ -1032,43 +1183,55 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
1032
1183
|
pipeline,
|
|
1033
1184
|
assertionEvaluator,
|
|
1034
1185
|
scriptRunner,
|
|
1035
|
-
|
|
1186
|
+
...runCtx,
|
|
1187
|
+
}));
|
|
1036
1188
|
}
|
|
1037
1189
|
case "neuron_api_run_scenario": {
|
|
1038
1190
|
if (!httpExecutor || !pipeline || !assertionEvaluator) {
|
|
1039
1191
|
return { error: "HTTP executor not configured" };
|
|
1040
1192
|
}
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
const {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
return {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1193
|
+
const loaded = await loadForRun(args);
|
|
1194
|
+
if ("error" in loaded)
|
|
1195
|
+
return loaded;
|
|
1196
|
+
const runId = typeof args.run_id === "string" && args.run_id ? args.run_id : undefined;
|
|
1197
|
+
const flag = { cancelled: false };
|
|
1198
|
+
if (runId)
|
|
1199
|
+
activeRuns.set(runId, flag);
|
|
1200
|
+
try {
|
|
1201
|
+
return await runScenario({
|
|
1202
|
+
collection: loaded.collection,
|
|
1203
|
+
collectionId: loaded.id,
|
|
1204
|
+
scenario: args.scenario,
|
|
1205
|
+
environment: args.environment,
|
|
1206
|
+
inputs: args.inputs,
|
|
1207
|
+
httpExecutor,
|
|
1208
|
+
pipeline,
|
|
1209
|
+
assertionEvaluator,
|
|
1210
|
+
scriptRunner,
|
|
1211
|
+
isCancelled: () => flag.cancelled,
|
|
1212
|
+
onEvent: hooks?.onEvent && runId ? (event) => hooks.onEvent({ ...event, run_id: runId }) : undefined,
|
|
1213
|
+
});
|
|
1054
1214
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if (!scenario) {
|
|
1059
|
-
return { error: `Scenario '${scenarioKey}' not found in collection '${id}'` };
|
|
1215
|
+
finally {
|
|
1216
|
+
if (runId)
|
|
1217
|
+
activeRuns.delete(runId);
|
|
1060
1218
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1219
|
+
}
|
|
1220
|
+
case "neuron_api_get_run_inputs": {
|
|
1221
|
+
const loaded = await loadForRun(args);
|
|
1222
|
+
if ("error" in loaded)
|
|
1223
|
+
return loaded;
|
|
1224
|
+
const res = runInputsFor(loaded.collection, loaded.id, args.scenario, args.environment);
|
|
1225
|
+
if ("error" in res)
|
|
1226
|
+
return res;
|
|
1227
|
+
return { collection: loaded.id, environment: res.environment, scenario: args.scenario ?? null, inputs: res.inputs };
|
|
1228
|
+
}
|
|
1229
|
+
case "neuron_api_cancel_run": {
|
|
1230
|
+
const flag = activeRuns.get(args.run_id);
|
|
1231
|
+
if (!flag)
|
|
1232
|
+
return { cancelled: false, error: `No active run '${args.run_id}'` };
|
|
1233
|
+
flag.cancelled = true;
|
|
1234
|
+
return { cancelled: true, run_id: args.run_id };
|
|
1072
1235
|
}
|
|
1073
1236
|
// ── Query and generation ─────────────────────────────
|
|
1074
1237
|
case "neuron_api_query": {
|