neuron-inspector 1.2.1 → 1.3.1
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 +128 -5
- package/dist/collections/executor.js +330 -93
- 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 +86 -0
- package/dist/collections/scenario-run.js +169 -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 +313 -89
- 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,105 @@
|
|
|
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, rerunScenario, RerunContexts } 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();
|
|
101
|
+
/** In-memory context of recent runs (by run_id), for neuron_api_rerun_step. */
|
|
102
|
+
const rerunContexts = new RerunContexts();
|
|
7
103
|
export function buildCollectionTools() {
|
|
8
104
|
return [
|
|
9
105
|
{
|
|
@@ -123,7 +219,8 @@ export function buildCollectionTools() {
|
|
|
123
219
|
},
|
|
124
220
|
extract: {
|
|
125
221
|
type: "object",
|
|
126
|
-
description: "Variables to extract from the response, e.g. { bot_id: 'body.id' }"
|
|
222
|
+
description: "Variables to extract from the response, e.g. { bot_id: 'body.id' }. " +
|
|
223
|
+
"Use { access_token: { path: 'body.access_token', mask: true } } to show the value as ••• in every result for the rest of the run.",
|
|
127
224
|
},
|
|
128
225
|
body_mode: {
|
|
129
226
|
type: "string",
|
|
@@ -263,6 +360,8 @@ export function buildCollectionTools() {
|
|
|
263
360
|
variables: { type: "object", description: "Environment variables (key-value pairs)" },
|
|
264
361
|
headers: { type: "object", description: "Environment-specific header overrides" },
|
|
265
362
|
timeout_ms: { type: "number", description: "Environment-specific timeout" },
|
|
363
|
+
inputs: INPUTS_SCHEMA,
|
|
364
|
+
timezone: { type: "string", description: "IANA zone for {{$today}} and formatted {{$now}}, e.g. 'Africa/Lagos' (default: defaults.timezone, else UTC)" },
|
|
266
365
|
},
|
|
267
366
|
required: ["collection", "name", "base_url"],
|
|
268
367
|
},
|
|
@@ -321,6 +420,7 @@ export function buildCollectionTools() {
|
|
|
321
420
|
{
|
|
322
421
|
name: "neuron_api_set_defaults",
|
|
323
422
|
description: "Set or update collection-level defaults. Merges with existing defaults. " +
|
|
423
|
+
"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
424
|
"Pass null for auth to clear it. Omit fields to leave them unchanged.",
|
|
325
425
|
inputSchema: {
|
|
326
426
|
type: "object",
|
|
@@ -328,6 +428,7 @@ export function buildCollectionTools() {
|
|
|
328
428
|
collection: { type: "string", description: "Collection id" },
|
|
329
429
|
headers: { type: "object", description: "Default headers (key-value pairs)" },
|
|
330
430
|
variables: { type: "object", description: "Default variables (key-value pairs)" },
|
|
431
|
+
timezone: { type: "string", description: "IANA zone for {{$today}} and formatted {{$now}}, e.g. 'Africa/Lagos' (default UTC). An environment's timezone wins." },
|
|
331
432
|
timeout_ms: { type: "number", description: "Default timeout (ms)" },
|
|
332
433
|
auth: {
|
|
333
434
|
type: "object",
|
|
@@ -444,34 +545,20 @@ export function buildCollectionTools() {
|
|
|
444
545
|
// ── Scenario management ───────────────────────────────────
|
|
445
546
|
{
|
|
446
547
|
name: "neuron_api_create_scenario",
|
|
447
|
-
description: "Create a new scenario in a collection. Steps reference endpoints as 'folder
|
|
548
|
+
description: "Create a new scenario in a collection. Steps reference endpoints as 'folder/<endpoint name>' or " +
|
|
549
|
+
"'folder/METHOD /path' (e.g. 'auth/POST /login'). A step can poll until assertions pass. " +
|
|
550
|
+
"Declare run-time values (client ids, secrets) in 'inputs' and pass them when you run.",
|
|
448
551
|
inputSchema: {
|
|
449
552
|
type: "object",
|
|
450
553
|
properties: {
|
|
451
554
|
collection: { type: "string", description: "Collection id" },
|
|
452
555
|
name: { type: "string", description: "Scenario name" },
|
|
453
556
|
description: { type: "string", description: "Scenario description" },
|
|
557
|
+
inputs: INPUTS_SCHEMA,
|
|
454
558
|
steps: {
|
|
455
559
|
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
|
-
},
|
|
560
|
+
description: "Ordered steps. Each step has a 'ref', optional overrides and an optional poll.",
|
|
561
|
+
items: STEP_SCHEMA,
|
|
475
562
|
},
|
|
476
563
|
},
|
|
477
564
|
required: ["collection", "name", "steps"],
|
|
@@ -479,24 +566,18 @@ export function buildCollectionTools() {
|
|
|
479
566
|
},
|
|
480
567
|
{
|
|
481
568
|
name: "neuron_api_update_scenario",
|
|
482
|
-
description: "Update a scenario's description or steps.",
|
|
569
|
+
description: "Update a scenario's description, inputs or steps.",
|
|
483
570
|
inputSchema: {
|
|
484
571
|
type: "object",
|
|
485
572
|
properties: {
|
|
486
573
|
collection: { type: "string", description: "Collection id" },
|
|
487
574
|
scenario: { type: "string", description: "Scenario name" },
|
|
488
575
|
description: { type: "string", description: "New description" },
|
|
576
|
+
inputs: INPUTS_SCHEMA,
|
|
489
577
|
steps: {
|
|
490
578
|
type: "array",
|
|
491
579
|
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
|
-
},
|
|
580
|
+
items: STEP_SCHEMA,
|
|
500
581
|
},
|
|
501
582
|
},
|
|
502
583
|
required: ["collection", "scenario"],
|
|
@@ -551,6 +632,7 @@ export function buildCollectionTools() {
|
|
|
551
632
|
follow_authorization: { type: "boolean", description: "Override: retain Authorization on cross-host redirects" },
|
|
552
633
|
remove_referer: { type: "boolean", description: "Override: remove Referer header on redirect" },
|
|
553
634
|
encode_url: { type: "boolean", description: "Override: encode URL automatically" },
|
|
635
|
+
inputs: RUN_INPUT_VALUES_SCHEMA,
|
|
554
636
|
},
|
|
555
637
|
required: ["collection"],
|
|
556
638
|
},
|
|
@@ -559,16 +641,63 @@ export function buildCollectionTools() {
|
|
|
559
641
|
name: "neuron_api_run_scenario",
|
|
560
642
|
description: "Run a named scenario: an ordered sequence of endpoint calls with variable chaining. " +
|
|
561
643
|
"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."
|
|
644
|
+
"Execution halts on the first step that fails (HTTP error, assertion failure, or a poll that never succeeds). " +
|
|
645
|
+
"Steps can include overrides for headers, body, tests, path, and variables, and a poll. " +
|
|
646
|
+
"Pass values for the scenario's/environment's declared inputs in 'inputs'; secret ones are masked as ••• in the result. " +
|
|
647
|
+
"The environment you pass is the one used, whatever the collection's active environment is.",
|
|
564
648
|
inputSchema: {
|
|
565
649
|
type: "object",
|
|
566
650
|
properties: {
|
|
567
651
|
collection: { type: "string", description: "Collection id" },
|
|
568
652
|
scenario: { type: "string", description: "Scenario name (key under 'scenarios' in the collection)" },
|
|
569
653
|
environment: { type: "string", description: "Environment name (defaults to active_environment)" },
|
|
654
|
+
inputs: RUN_INPUT_VALUES_SCHEMA,
|
|
655
|
+
run_id: { type: "string", description: "Optional id so the run can be cancelled with neuron_api_cancel_run" },
|
|
656
|
+
collection_data: COLLECTION_DATA_SCHEMA,
|
|
570
657
|
},
|
|
571
|
-
required: ["
|
|
658
|
+
required: ["scenario"],
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: "neuron_api_get_run_inputs",
|
|
663
|
+
description: "List the inputs a scenario run will ask for (environment inputs merged with the scenario's). " +
|
|
664
|
+
"Returns declarations only (name, label, secret, required), never values.",
|
|
665
|
+
inputSchema: {
|
|
666
|
+
type: "object",
|
|
667
|
+
properties: {
|
|
668
|
+
collection: { type: "string", description: "Collection id" },
|
|
669
|
+
scenario: { type: "string", description: "Scenario name (omit for environment inputs only)" },
|
|
670
|
+
environment: { type: "string", description: "Environment name (defaults to active_environment)" },
|
|
671
|
+
collection_data: COLLECTION_DATA_SCHEMA,
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: "neuron_api_rerun_step",
|
|
677
|
+
description: "Re-run one step of an earlier scenario run (mode 'step'), or that step and everything after it (mode 'from'), " +
|
|
678
|
+
"like re-running a job in GitHub Actions. It starts from the variables the original run had just before that step " +
|
|
679
|
+
"(tokens, ids, inputs), so earlier steps aren't called again. It uses the scenario as currently saved, so a fixed step can be re-run. " +
|
|
680
|
+
"Only runs started with a run_id in this bridge process within the last 30 minutes can be re-run; their context is kept in memory only.",
|
|
681
|
+
inputSchema: {
|
|
682
|
+
type: "object",
|
|
683
|
+
properties: {
|
|
684
|
+
run_id: { type: "string", description: "run_id of the original run_scenario call" },
|
|
685
|
+
step: { type: "number", description: "Step index as in the result's `step` field (0-based)" },
|
|
686
|
+
mode: { type: "string", enum: ["step", "from"], description: "'step' = only this step (default); 'from' = this step and all later ones" },
|
|
687
|
+
new_run_id: { type: "string", description: "Optional id for this re-run's live events / cancel" },
|
|
688
|
+
collection: { type: "string", description: "Collection id" },
|
|
689
|
+
collection_data: COLLECTION_DATA_SCHEMA,
|
|
690
|
+
},
|
|
691
|
+
required: ["run_id", "step"],
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
name: "neuron_api_cancel_run",
|
|
696
|
+
description: "Stop a scenario run started with a run_id. It halts before its next request or poll attempt.",
|
|
697
|
+
inputSchema: {
|
|
698
|
+
type: "object",
|
|
699
|
+
properties: { run_id: { type: "string" } },
|
|
700
|
+
required: ["run_id"],
|
|
572
701
|
},
|
|
573
702
|
},
|
|
574
703
|
// ── Query and generation ─────────────────────────────────
|
|
@@ -693,7 +822,24 @@ export function buildCollectionTools() {
|
|
|
693
822
|
}
|
|
694
823
|
// ── Handler ─────────────────────────────────────────────────────
|
|
695
824
|
export function buildCollectionHandler(store, httpExecutor, pipeline, assertionEvaluator, scriptRunner) {
|
|
696
|
-
|
|
825
|
+
/** Load the collection a run targets: inline data from the caller, else the store. */
|
|
826
|
+
async function loadForRun(args) {
|
|
827
|
+
if (args.collection_data !== undefined) {
|
|
828
|
+
const data = args.collection_data;
|
|
829
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
830
|
+
return { error: "collection_data must be a collection object" };
|
|
831
|
+
const c = data;
|
|
832
|
+
return { id: args.collection || slugify(c.name || "inline"), collection: c };
|
|
833
|
+
}
|
|
834
|
+
const id = args.collection;
|
|
835
|
+
if (!id)
|
|
836
|
+
return { error: "Provide 'collection' (id) or 'collection_data'" };
|
|
837
|
+
const result = await store.get(id);
|
|
838
|
+
if (!result)
|
|
839
|
+
return { error: `Collection '${id}' not found` };
|
|
840
|
+
return { id, collection: result.collection };
|
|
841
|
+
}
|
|
842
|
+
return async function handleCollectionTool(name, args, hooks) {
|
|
697
843
|
switch (name) {
|
|
698
844
|
case "neuron_api_list_collections": {
|
|
699
845
|
return store.list();
|
|
@@ -785,6 +931,13 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
785
931
|
env.headers = args.headers;
|
|
786
932
|
if (args.timeout_ms)
|
|
787
933
|
env.timeout_ms = args.timeout_ms;
|
|
934
|
+
if (typeof args.timezone === "string" && args.timezone) {
|
|
935
|
+
assertTimeZone(args.timezone);
|
|
936
|
+
env.timezone = args.timezone;
|
|
937
|
+
}
|
|
938
|
+
const envInputs = sanitizeInputDeclarations(args.inputs);
|
|
939
|
+
if (envInputs)
|
|
940
|
+
env.inputs = envInputs;
|
|
788
941
|
await store.setEnvironment(id, envName, env);
|
|
789
942
|
return { set: true, collection: id, environment: slugify(envName) };
|
|
790
943
|
}
|
|
@@ -837,6 +990,8 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
837
990
|
const { collection: _c, ...defaults } = args;
|
|
838
991
|
if (Object.keys(defaults).length === 0)
|
|
839
992
|
return { error: "No defaults provided" };
|
|
993
|
+
if (typeof defaults.timezone === "string" && defaults.timezone)
|
|
994
|
+
assertTimeZone(defaults.timezone);
|
|
840
995
|
await store.setDefaults(id, defaults);
|
|
841
996
|
return { set: true, collection: id, fields: Object.keys(defaults) };
|
|
842
997
|
}
|
|
@@ -901,12 +1056,22 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
901
1056
|
const id = args.collection;
|
|
902
1057
|
const scenarioName = args.name;
|
|
903
1058
|
const scenario = {
|
|
904
|
-
steps: args.steps,
|
|
1059
|
+
steps: validateSteps(args.steps),
|
|
905
1060
|
};
|
|
906
1061
|
if (args.description)
|
|
907
1062
|
scenario.description = args.description;
|
|
908
|
-
|
|
909
|
-
|
|
1063
|
+
const scenarioInputs = sanitizeInputDeclarations(args.inputs);
|
|
1064
|
+
if (scenarioInputs)
|
|
1065
|
+
scenario.inputs = scenarioInputs;
|
|
1066
|
+
const updated = await store.createScenario(id, scenarioName, scenario);
|
|
1067
|
+
const warnings = checkStepRefs(updated, scenario.steps);
|
|
1068
|
+
return {
|
|
1069
|
+
created: true,
|
|
1070
|
+
collection: id,
|
|
1071
|
+
scenario: scenarioName,
|
|
1072
|
+
step_count: scenario.steps.length,
|
|
1073
|
+
...(warnings.length ? { warnings } : {}),
|
|
1074
|
+
};
|
|
910
1075
|
}
|
|
911
1076
|
case "neuron_api_update_scenario": {
|
|
912
1077
|
const id = args.collection;
|
|
@@ -915,11 +1080,14 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
915
1080
|
if (args.description !== undefined)
|
|
916
1081
|
updates.description = args.description;
|
|
917
1082
|
if (args.steps !== undefined)
|
|
918
|
-
updates.steps = args.steps;
|
|
1083
|
+
updates.steps = validateSteps(args.steps);
|
|
1084
|
+
if (args.inputs !== undefined)
|
|
1085
|
+
updates.inputs = sanitizeInputDeclarations(args.inputs) ?? [];
|
|
919
1086
|
if (Object.keys(updates).length === 0)
|
|
920
1087
|
return { error: "No updates provided" };
|
|
921
|
-
await store.updateScenario(id, scenarioName, updates);
|
|
922
|
-
|
|
1088
|
+
const updated = await store.updateScenario(id, scenarioName, updates);
|
|
1089
|
+
const warnings = checkStepRefs(updated, updated.scenarios?.[scenarioName]?.steps ?? []);
|
|
1090
|
+
return { updated: true, collection: id, scenario: scenarioName, ...(warnings.length ? { warnings } : {}) };
|
|
923
1091
|
}
|
|
924
1092
|
case "neuron_api_delete_scenario": {
|
|
925
1093
|
const id = args.collection;
|
|
@@ -938,6 +1106,7 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
938
1106
|
name: key,
|
|
939
1107
|
description: s.description ?? null,
|
|
940
1108
|
step_count: s.steps?.length ?? 0,
|
|
1109
|
+
inputs: describeInputs(s.inputs ?? []),
|
|
941
1110
|
})),
|
|
942
1111
|
};
|
|
943
1112
|
}
|
|
@@ -946,25 +1115,26 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
946
1115
|
if (!httpExecutor || !pipeline || !assertionEvaluator) {
|
|
947
1116
|
return { error: "HTTP executor not configured" };
|
|
948
1117
|
}
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
return { error: `Environment '${envName}' not found in collection '${id}'` };
|
|
1118
|
+
const loaded = await loadForRun(args);
|
|
1119
|
+
if ("error" in loaded)
|
|
1120
|
+
return loaded;
|
|
1121
|
+
const { id, collection } = loaded;
|
|
1122
|
+
const picked = pickEnvironment(collection, id, args.environment);
|
|
1123
|
+
if ("error" in picked)
|
|
1124
|
+
return picked;
|
|
1125
|
+
const { key: envKey, environment } = picked;
|
|
1126
|
+
const declared = mergeInputDeclarations(environment.inputs);
|
|
1127
|
+
const prepared = prepareInputs(declared, args.inputs);
|
|
1128
|
+
if (prepared.missing.length) {
|
|
1129
|
+
return { error: `Missing required input(s): ${prepared.missing.join(", ")}`, inputs: describeInputs(declared) };
|
|
962
1130
|
}
|
|
1131
|
+
const masker = new SecretMasker(prepared.secrets);
|
|
1132
|
+
const runCtx = { environmentName: envKey, inputs: prepared.values, onSecret: (v) => masker.add(v) };
|
|
963
1133
|
const hasFolder = typeof args.folder === "string";
|
|
964
1134
|
const hasEndpoint = typeof args.method === "string" && typeof args.path === "string";
|
|
965
1135
|
// Mode 3: Collection-wide run (no folder specified)
|
|
966
1136
|
if (!hasFolder) {
|
|
967
|
-
return executeCollection({
|
|
1137
|
+
return masker.mask(await executeCollection({
|
|
968
1138
|
collectionId: id,
|
|
969
1139
|
collection,
|
|
970
1140
|
environment,
|
|
@@ -972,16 +1142,17 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
972
1142
|
pipeline,
|
|
973
1143
|
assertionEvaluator,
|
|
974
1144
|
scriptRunner,
|
|
975
|
-
|
|
1145
|
+
...runCtx,
|
|
1146
|
+
}));
|
|
976
1147
|
}
|
|
977
1148
|
const folderKey = slugify(args.folder);
|
|
978
|
-
const folder = collection.folders?.[folderKey];
|
|
1149
|
+
const folder = collection.folders?.[folderKey] ?? collection.folders?.[args.folder];
|
|
979
1150
|
if (!folder) {
|
|
980
1151
|
return { error: `Folder '${args.folder}' not found in collection '${id}'` };
|
|
981
1152
|
}
|
|
982
1153
|
// Mode 2: Folder run (folder but no method/path)
|
|
983
1154
|
if (!hasEndpoint) {
|
|
984
|
-
return executeFolder({
|
|
1155
|
+
return masker.mask(await executeFolder({
|
|
985
1156
|
folderKey,
|
|
986
1157
|
collection,
|
|
987
1158
|
environment,
|
|
@@ -990,7 +1161,8 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
990
1161
|
pipeline,
|
|
991
1162
|
assertionEvaluator,
|
|
992
1163
|
scriptRunner,
|
|
993
|
-
|
|
1164
|
+
...runCtx,
|
|
1165
|
+
}));
|
|
994
1166
|
}
|
|
995
1167
|
// Mode 1: Single endpoint
|
|
996
1168
|
const method = args.method.toUpperCase();
|
|
@@ -1023,7 +1195,7 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
1023
1195
|
runSettings.encode_url = args.encode_url;
|
|
1024
1196
|
endpoint.settings = runSettings;
|
|
1025
1197
|
}
|
|
1026
|
-
return executeEndpoint({
|
|
1198
|
+
return masker.mask(await executeEndpoint({
|
|
1027
1199
|
collection,
|
|
1028
1200
|
environment,
|
|
1029
1201
|
folder,
|
|
@@ -1032,43 +1204,95 @@ export function buildCollectionHandler(store, httpExecutor, pipeline, assertionE
|
|
|
1032
1204
|
pipeline,
|
|
1033
1205
|
assertionEvaluator,
|
|
1034
1206
|
scriptRunner,
|
|
1035
|
-
|
|
1207
|
+
...runCtx,
|
|
1208
|
+
}));
|
|
1036
1209
|
}
|
|
1037
1210
|
case "neuron_api_run_scenario": {
|
|
1038
1211
|
if (!httpExecutor || !pipeline || !assertionEvaluator) {
|
|
1039
1212
|
return { error: "HTTP executor not configured" };
|
|
1040
1213
|
}
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
const {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1214
|
+
const loaded = await loadForRun(args);
|
|
1215
|
+
if ("error" in loaded)
|
|
1216
|
+
return loaded;
|
|
1217
|
+
const runId = typeof args.run_id === "string" && args.run_id ? args.run_id : undefined;
|
|
1218
|
+
const flag = { cancelled: false };
|
|
1219
|
+
if (runId)
|
|
1220
|
+
activeRuns.set(runId, flag);
|
|
1221
|
+
try {
|
|
1222
|
+
const rec = runId
|
|
1223
|
+
? rerunContexts.recorder(runId, { scenario: args.scenario, environment: args.environment })
|
|
1224
|
+
: undefined;
|
|
1225
|
+
return await runScenario({
|
|
1226
|
+
...(rec ?? {}),
|
|
1227
|
+
collection: loaded.collection,
|
|
1228
|
+
collectionId: loaded.id,
|
|
1229
|
+
scenario: args.scenario,
|
|
1230
|
+
environment: args.environment,
|
|
1231
|
+
inputs: args.inputs,
|
|
1232
|
+
httpExecutor,
|
|
1233
|
+
pipeline,
|
|
1234
|
+
assertionEvaluator,
|
|
1235
|
+
scriptRunner,
|
|
1236
|
+
isCancelled: () => flag.cancelled,
|
|
1237
|
+
onEvent: hooks?.onEvent && runId ? (event) => hooks.onEvent({ ...event, run_id: runId }) : undefined,
|
|
1238
|
+
});
|
|
1050
1239
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1240
|
+
finally {
|
|
1241
|
+
if (runId)
|
|
1242
|
+
activeRuns.delete(runId);
|
|
1054
1243
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
const
|
|
1058
|
-
if (
|
|
1059
|
-
return
|
|
1244
|
+
}
|
|
1245
|
+
case "neuron_api_get_run_inputs": {
|
|
1246
|
+
const loaded = await loadForRun(args);
|
|
1247
|
+
if ("error" in loaded)
|
|
1248
|
+
return loaded;
|
|
1249
|
+
const res = runInputsFor(loaded.collection, loaded.id, args.scenario, args.environment);
|
|
1250
|
+
if ("error" in res)
|
|
1251
|
+
return res;
|
|
1252
|
+
return { collection: loaded.id, environment: res.environment, scenario: args.scenario ?? null, inputs: res.inputs };
|
|
1253
|
+
}
|
|
1254
|
+
case "neuron_api_rerun_step": {
|
|
1255
|
+
if (!httpExecutor || !pipeline || !assertionEvaluator) {
|
|
1256
|
+
return { error: "HTTP executor not configured" };
|
|
1060
1257
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1258
|
+
const loaded = await loadForRun(args);
|
|
1259
|
+
if ("error" in loaded)
|
|
1260
|
+
return loaded;
|
|
1261
|
+
const step = Number(args.step);
|
|
1262
|
+
if (!Number.isInteger(step) || step < 0)
|
|
1263
|
+
return { error: "step must be a 0-based step index" };
|
|
1264
|
+
const mode = args.mode === "from" ? "from" : "step";
|
|
1265
|
+
const newRunId = typeof args.new_run_id === "string" && args.new_run_id ? args.new_run_id : undefined;
|
|
1266
|
+
const flag = { cancelled: false };
|
|
1267
|
+
if (newRunId)
|
|
1268
|
+
activeRuns.set(newRunId, flag);
|
|
1269
|
+
try {
|
|
1270
|
+
return await rerunScenario({
|
|
1271
|
+
contexts: rerunContexts,
|
|
1272
|
+
runId: args.run_id,
|
|
1273
|
+
step,
|
|
1274
|
+
mode,
|
|
1275
|
+
collection: loaded.collection,
|
|
1276
|
+
collectionId: loaded.id,
|
|
1277
|
+
httpExecutor,
|
|
1278
|
+
pipeline,
|
|
1279
|
+
assertionEvaluator,
|
|
1280
|
+
scriptRunner,
|
|
1281
|
+
isCancelled: () => flag.cancelled,
|
|
1282
|
+
onEvent: hooks?.onEvent && newRunId ? (event) => hooks.onEvent({ ...event, run_id: newRunId }) : undefined,
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
finally {
|
|
1286
|
+
if (newRunId)
|
|
1287
|
+
activeRuns.delete(newRunId);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
case "neuron_api_cancel_run": {
|
|
1291
|
+
const flag = activeRuns.get(args.run_id);
|
|
1292
|
+
if (!flag)
|
|
1293
|
+
return { cancelled: false, error: `No active run '${args.run_id}'` };
|
|
1294
|
+
flag.cancelled = true;
|
|
1295
|
+
return { cancelled: true, run_id: args.run_id };
|
|
1072
1296
|
}
|
|
1073
1297
|
// ── Query and generation ─────────────────────────────
|
|
1074
1298
|
case "neuron_api_query": {
|