@argosvix/mcp-server 0.14.1-alpha.1 → 0.21.0-alpha.2
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/tools.d.ts +2 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +1329 -74
- package/dist/tools.js.map +1 -1
- package/dist/tools.test.js +497 -1
- package/dist/tools.test.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/tools.test.js
CHANGED
|
@@ -19,29 +19,57 @@ describe("MCP version source of truth", () => {
|
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
describe("MCP tools metadata", () => {
|
|
22
|
-
it("exposes
|
|
22
|
+
it("exposes 49 tools (47 prior + v1.6 axis 1: rename_project + delete_project)", () => {
|
|
23
23
|
expect(tools.map((t) => t.name).sort()).toEqual([
|
|
24
24
|
"acknowledge_alert",
|
|
25
|
+
"aggregate_calls",
|
|
26
|
+
"bulk_delete_calls",
|
|
27
|
+
"compare_eval_runs",
|
|
25
28
|
"create_alert",
|
|
29
|
+
"create_annotation",
|
|
30
|
+
"create_eval_criterion",
|
|
31
|
+
"create_project",
|
|
32
|
+
"create_prompt",
|
|
33
|
+
"create_saved_view",
|
|
34
|
+
"delete_alert",
|
|
35
|
+
"delete_annotation",
|
|
36
|
+
"delete_eval_criterion",
|
|
37
|
+
"delete_project",
|
|
38
|
+
"delete_prompt",
|
|
39
|
+
"delete_saved_view",
|
|
40
|
+
"export_calls",
|
|
26
41
|
"get_alert",
|
|
27
42
|
"get_annotation",
|
|
28
43
|
"get_cost_summary",
|
|
29
44
|
"get_eval_criterion",
|
|
30
45
|
"get_eval_run",
|
|
46
|
+
"get_llm_budget",
|
|
47
|
+
"get_percentiles",
|
|
31
48
|
"get_prompt",
|
|
32
49
|
"get_safety_assessment",
|
|
33
50
|
"list_alert_events",
|
|
34
51
|
"list_alerts",
|
|
35
52
|
"list_annotations_by_label",
|
|
36
53
|
"list_annotations_for_call",
|
|
54
|
+
"list_audit_log",
|
|
37
55
|
"list_eval_criteria",
|
|
38
56
|
"list_eval_runs",
|
|
57
|
+
"list_projects",
|
|
39
58
|
"list_prompts",
|
|
40
59
|
"list_safety_assessments",
|
|
60
|
+
"list_saved_views",
|
|
41
61
|
"query_calls",
|
|
62
|
+
"raise_llm_budget",
|
|
63
|
+
"rename_project",
|
|
64
|
+
"rename_prompt",
|
|
42
65
|
"run_eval",
|
|
43
66
|
"silence_alert",
|
|
67
|
+
"test_webhook",
|
|
44
68
|
"unsilence_alert",
|
|
69
|
+
"update_alert",
|
|
70
|
+
"update_annotation",
|
|
71
|
+
"update_eval_criterion",
|
|
72
|
+
"update_prompt",
|
|
45
73
|
]);
|
|
46
74
|
});
|
|
47
75
|
it("each tool has description + inputSchema", () => {
|
|
@@ -136,6 +164,28 @@ describe("dispatchTool", () => {
|
|
|
136
164
|
const parsed = JSON.parse(res.content[0]?.text ?? "{}");
|
|
137
165
|
expect(parsed.groups[0].provider).toBe("openai");
|
|
138
166
|
});
|
|
167
|
+
it("export_calls uses POST /v1/query/export (regression for path/method drift)", async () => {
|
|
168
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ records: [] }), {
|
|
169
|
+
status: 200,
|
|
170
|
+
headers: { "content-type": "application/json" },
|
|
171
|
+
})));
|
|
172
|
+
const res = await dispatchTool({
|
|
173
|
+
name: "export_calls",
|
|
174
|
+
args: { provider: "openai", limit: 1000 },
|
|
175
|
+
apiKey: "argosvix_live_test",
|
|
176
|
+
apiBase: "https://ingest.example.com",
|
|
177
|
+
});
|
|
178
|
+
expect(res.isError).toBeUndefined();
|
|
179
|
+
const fetchMock = global.fetch;
|
|
180
|
+
const fetchedUrl = String(fetchMock.mock.calls[0]?.[0]);
|
|
181
|
+
expect(fetchedUrl).toContain("/v1/query/export");
|
|
182
|
+
expect(fetchedUrl).not.toContain("/v1/export?");
|
|
183
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
184
|
+
expect(init.method).toBe("POST");
|
|
185
|
+
const body = JSON.parse(init.body);
|
|
186
|
+
expect(body.provider).toBe("openai");
|
|
187
|
+
expect(body.limit).toBe(1000);
|
|
188
|
+
});
|
|
139
189
|
it("list_alerts uses /v1/alerts endpoint", async () => {
|
|
140
190
|
const res = await dispatchTool({
|
|
141
191
|
name: "list_alerts",
|
|
@@ -353,6 +403,452 @@ describe("dispatchTool", () => {
|
|
|
353
403
|
expect(init.method).toBe("DELETE");
|
|
354
404
|
expect(init.body).toBeUndefined();
|
|
355
405
|
});
|
|
406
|
+
it("update_alert sends PATCH to /v1/alerts/:id with allowlisted body (= v1.6 #13-4)", async () => {
|
|
407
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: "alt-abc123", name: "renamed" }), {
|
|
408
|
+
status: 200,
|
|
409
|
+
headers: { "content-type": "application/json" },
|
|
410
|
+
})));
|
|
411
|
+
const res = await dispatchTool({
|
|
412
|
+
name: "update_alert",
|
|
413
|
+
args: {
|
|
414
|
+
alertId: "alt-abc123",
|
|
415
|
+
name: "renamed",
|
|
416
|
+
thresholdValue: 50,
|
|
417
|
+
enabled: false,
|
|
418
|
+
// alertType は schema にないため呼び出し側で混入させない
|
|
419
|
+
},
|
|
420
|
+
apiKey: "argosvix_live_test",
|
|
421
|
+
apiBase: "https://ingest.example.com",
|
|
422
|
+
});
|
|
423
|
+
expect(res.isError).toBeUndefined();
|
|
424
|
+
const fetchMock = global.fetch;
|
|
425
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
426
|
+
expect(url).toContain("/v1/alerts/alt-abc123");
|
|
427
|
+
expect(url).not.toContain("/silence");
|
|
428
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
429
|
+
expect(init.method).toBe("PATCH");
|
|
430
|
+
const body = JSON.parse(init.body);
|
|
431
|
+
expect(body).toEqual({ name: "renamed", thresholdValue: 50, enabled: false });
|
|
432
|
+
// alertId は path に carry、 body には混入しない
|
|
433
|
+
expect(body.alertId).toBeUndefined();
|
|
434
|
+
});
|
|
435
|
+
it("update_alert rejects invalid alertId (= path injection 防御)", async () => {
|
|
436
|
+
const res = await dispatchTool({
|
|
437
|
+
name: "update_alert",
|
|
438
|
+
args: { alertId: "../../admin/internal", name: "x" },
|
|
439
|
+
apiKey: "argosvix_live_test",
|
|
440
|
+
apiBase: "https://ingest.example.com",
|
|
441
|
+
});
|
|
442
|
+
expect(res.isError).toBe(true);
|
|
443
|
+
expect(res.content[0]?.text).toContain("alertId required");
|
|
444
|
+
});
|
|
445
|
+
it("delete_alert sends DELETE to /v1/alerts/:id without body (= v1.6 #13-4)", async () => {
|
|
446
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(null, { status: 204 })));
|
|
447
|
+
const res = await dispatchTool({
|
|
448
|
+
name: "delete_alert",
|
|
449
|
+
args: { alertId: "alt-doomed42" },
|
|
450
|
+
apiKey: "argosvix_live_test",
|
|
451
|
+
apiBase: "https://ingest.example.com",
|
|
452
|
+
});
|
|
453
|
+
expect(res.isError).toBeUndefined();
|
|
454
|
+
const parsed = JSON.parse(res.content[0]?.text ?? "{}");
|
|
455
|
+
expect(parsed.ok).toBe(true);
|
|
456
|
+
expect(parsed.status).toBe(204);
|
|
457
|
+
const fetchMock = global.fetch;
|
|
458
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
459
|
+
expect(url).toContain("/v1/alerts/alt-doomed42");
|
|
460
|
+
expect(url).not.toContain("/silence");
|
|
461
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
462
|
+
expect(init.method).toBe("DELETE");
|
|
463
|
+
expect(init.body).toBeUndefined();
|
|
464
|
+
});
|
|
465
|
+
it("delete_alert rejects invalid alertId (= path injection 防御)", async () => {
|
|
466
|
+
const res = await dispatchTool({
|
|
467
|
+
name: "delete_alert",
|
|
468
|
+
args: { alertId: "/etc/passwd" },
|
|
469
|
+
apiKey: "argosvix_live_test",
|
|
470
|
+
apiBase: "https://ingest.example.com",
|
|
471
|
+
});
|
|
472
|
+
expect(res.isError).toBe(true);
|
|
473
|
+
expect(res.content[0]?.text).toContain("alertId required");
|
|
474
|
+
});
|
|
475
|
+
it("create_annotation POSTs allowlisted body to /v1/annotations (= v1.6 #13-3)", async () => {
|
|
476
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 42, callId: "call-xyz" }), {
|
|
477
|
+
status: 201,
|
|
478
|
+
headers: { "content-type": "application/json" },
|
|
479
|
+
})));
|
|
480
|
+
const res = await dispatchTool({
|
|
481
|
+
name: "create_annotation",
|
|
482
|
+
args: {
|
|
483
|
+
callId: "call-xyz",
|
|
484
|
+
annotationText: "looks good",
|
|
485
|
+
label: "approved",
|
|
486
|
+
qualityScore: 5,
|
|
487
|
+
},
|
|
488
|
+
apiKey: "argosvix_live_test",
|
|
489
|
+
apiBase: "https://ingest.example.com",
|
|
490
|
+
});
|
|
491
|
+
expect(res.isError).toBeUndefined();
|
|
492
|
+
const fetchMock = global.fetch;
|
|
493
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
494
|
+
expect(url).toContain("/v1/annotations");
|
|
495
|
+
expect(url).not.toContain("/v1/annotations/");
|
|
496
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
497
|
+
expect(init.method).toBe("POST");
|
|
498
|
+
const body = JSON.parse(init.body);
|
|
499
|
+
expect(body).toEqual({
|
|
500
|
+
callId: "call-xyz",
|
|
501
|
+
annotationText: "looks good",
|
|
502
|
+
label: "approved",
|
|
503
|
+
qualityScore: 5,
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
it("create_annotation rejects invalid callId (= path injection 防御)", async () => {
|
|
507
|
+
const res = await dispatchTool({
|
|
508
|
+
name: "create_annotation",
|
|
509
|
+
args: { callId: "../../admin", annotationText: "x" },
|
|
510
|
+
apiKey: "argosvix_live_test",
|
|
511
|
+
apiBase: "https://ingest.example.com",
|
|
512
|
+
});
|
|
513
|
+
expect(res.isError).toBe(true);
|
|
514
|
+
expect(res.content[0]?.text).toContain("callId required");
|
|
515
|
+
});
|
|
516
|
+
it("update_annotation sends PATCH with allowlisted body (= v1.6 #13-3)", async () => {
|
|
517
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 7, label: "renamed" }), {
|
|
518
|
+
status: 200,
|
|
519
|
+
headers: { "content-type": "application/json" },
|
|
520
|
+
})));
|
|
521
|
+
const res = await dispatchTool({
|
|
522
|
+
name: "update_annotation",
|
|
523
|
+
args: {
|
|
524
|
+
annotationId: 7,
|
|
525
|
+
label: "renamed",
|
|
526
|
+
qualityScore: 4,
|
|
527
|
+
},
|
|
528
|
+
apiKey: "argosvix_live_test",
|
|
529
|
+
apiBase: "https://ingest.example.com",
|
|
530
|
+
});
|
|
531
|
+
expect(res.isError).toBeUndefined();
|
|
532
|
+
const fetchMock = global.fetch;
|
|
533
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
534
|
+
expect(url).toContain("/v1/annotations/7");
|
|
535
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
536
|
+
expect(init.method).toBe("PATCH");
|
|
537
|
+
const body = JSON.parse(init.body);
|
|
538
|
+
expect(body).toEqual({ label: "renamed", qualityScore: 4 });
|
|
539
|
+
// annotationId は path に carry、 body には混入しない
|
|
540
|
+
expect(body.annotationId).toBeUndefined();
|
|
541
|
+
});
|
|
542
|
+
it("delete_annotation sends DELETE to /v1/annotations/:id without body (= v1.6 #13-3)", async () => {
|
|
543
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(null, { status: 204 })));
|
|
544
|
+
const res = await dispatchTool({
|
|
545
|
+
name: "delete_annotation",
|
|
546
|
+
args: { annotationId: 99 },
|
|
547
|
+
apiKey: "argosvix_live_test",
|
|
548
|
+
apiBase: "https://ingest.example.com",
|
|
549
|
+
});
|
|
550
|
+
expect(res.isError).toBeUndefined();
|
|
551
|
+
const parsed = JSON.parse(res.content[0]?.text ?? "{}");
|
|
552
|
+
expect(parsed.ok).toBe(true);
|
|
553
|
+
expect(parsed.status).toBe(204);
|
|
554
|
+
const fetchMock = global.fetch;
|
|
555
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
556
|
+
expect(url).toContain("/v1/annotations/99");
|
|
557
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
558
|
+
expect(init.method).toBe("DELETE");
|
|
559
|
+
expect(init.body).toBeUndefined();
|
|
560
|
+
});
|
|
561
|
+
it("create_prompt POSTs allowlisted body to /v1/prompts (= v1.6 #13-1)", async () => {
|
|
562
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 42, name: "customer_support" }), {
|
|
563
|
+
status: 201,
|
|
564
|
+
headers: { "content-type": "application/json" },
|
|
565
|
+
})));
|
|
566
|
+
const res = await dispatchTool({
|
|
567
|
+
name: "create_prompt",
|
|
568
|
+
args: {
|
|
569
|
+
name: "customer_support",
|
|
570
|
+
version: "v1",
|
|
571
|
+
template: "Hello {{user}}",
|
|
572
|
+
variables: { user: "world" },
|
|
573
|
+
labels: ["production"],
|
|
574
|
+
description: "primary cs prompt",
|
|
575
|
+
},
|
|
576
|
+
apiKey: "argosvix_live_test",
|
|
577
|
+
apiBase: "https://ingest.example.com",
|
|
578
|
+
});
|
|
579
|
+
expect(res.isError).toBeUndefined();
|
|
580
|
+
const fetchMock = global.fetch;
|
|
581
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
582
|
+
expect(url).toContain("/v1/prompts");
|
|
583
|
+
expect(url).not.toContain("/v1/prompts/");
|
|
584
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
585
|
+
expect(init.method).toBe("POST");
|
|
586
|
+
const body = JSON.parse(init.body);
|
|
587
|
+
expect(body).toEqual({
|
|
588
|
+
name: "customer_support",
|
|
589
|
+
version: "v1",
|
|
590
|
+
template: "Hello {{user}}",
|
|
591
|
+
variables: { user: "world" },
|
|
592
|
+
labels: ["production"],
|
|
593
|
+
description: "primary cs prompt",
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
it("create_prompt rejects missing name/version/template (= v1.6 #13-1)", async () => {
|
|
597
|
+
const res = await dispatchTool({
|
|
598
|
+
name: "create_prompt",
|
|
599
|
+
args: { name: "x" },
|
|
600
|
+
apiKey: "argosvix_live_test",
|
|
601
|
+
apiBase: "https://ingest.example.com",
|
|
602
|
+
});
|
|
603
|
+
expect(res.isError).toBe(true);
|
|
604
|
+
expect(res.content[0]?.text).toContain("name + version + template required");
|
|
605
|
+
});
|
|
606
|
+
it("update_prompt sends PATCH to /v1/prompts/:id with allowlisted body (= v1.6 #13-1)", async () => {
|
|
607
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 7, labels: ["production"] }), {
|
|
608
|
+
status: 200,
|
|
609
|
+
headers: { "content-type": "application/json" },
|
|
610
|
+
})));
|
|
611
|
+
const res = await dispatchTool({
|
|
612
|
+
name: "update_prompt",
|
|
613
|
+
args: {
|
|
614
|
+
promptId: 7,
|
|
615
|
+
labels: ["production"],
|
|
616
|
+
description: "renamed",
|
|
617
|
+
},
|
|
618
|
+
apiKey: "argosvix_live_test",
|
|
619
|
+
apiBase: "https://ingest.example.com",
|
|
620
|
+
});
|
|
621
|
+
expect(res.isError).toBeUndefined();
|
|
622
|
+
const fetchMock = global.fetch;
|
|
623
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
624
|
+
expect(url).toContain("/v1/prompts/7");
|
|
625
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
626
|
+
expect(init.method).toBe("PATCH");
|
|
627
|
+
const body = JSON.parse(init.body);
|
|
628
|
+
expect(body).toEqual({ labels: ["production"], description: "renamed" });
|
|
629
|
+
expect(body.promptId).toBeUndefined();
|
|
630
|
+
});
|
|
631
|
+
it("rename_prompt POSTs to /v1/prompts/:id/rename with name+version body (= v1.6 #13-1)", async () => {
|
|
632
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 7, name: "customer_support" }), {
|
|
633
|
+
status: 200,
|
|
634
|
+
headers: { "content-type": "application/json" },
|
|
635
|
+
})));
|
|
636
|
+
const res = await dispatchTool({
|
|
637
|
+
name: "rename_prompt",
|
|
638
|
+
args: { promptId: 7, name: "customer_support", version: "v2" },
|
|
639
|
+
apiKey: "argosvix_live_test",
|
|
640
|
+
apiBase: "https://ingest.example.com",
|
|
641
|
+
});
|
|
642
|
+
expect(res.isError).toBeUndefined();
|
|
643
|
+
const fetchMock = global.fetch;
|
|
644
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
645
|
+
expect(url).toContain("/v1/prompts/7/rename");
|
|
646
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
647
|
+
expect(init.method).toBe("POST");
|
|
648
|
+
const body = JSON.parse(init.body);
|
|
649
|
+
expect(body).toEqual({ name: "customer_support", version: "v2" });
|
|
650
|
+
});
|
|
651
|
+
it("rename_prompt rejects missing name/version (= v1.6 #13-1)", async () => {
|
|
652
|
+
const res = await dispatchTool({
|
|
653
|
+
name: "rename_prompt",
|
|
654
|
+
args: { promptId: 7, name: "x" },
|
|
655
|
+
apiKey: "argosvix_live_test",
|
|
656
|
+
apiBase: "https://ingest.example.com",
|
|
657
|
+
});
|
|
658
|
+
expect(res.isError).toBe(true);
|
|
659
|
+
expect(res.content[0]?.text).toContain("name + version required");
|
|
660
|
+
});
|
|
661
|
+
it("delete_prompt sends DELETE to /v1/prompts/:id without body (= v1.6 #13-1)", async () => {
|
|
662
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(null, { status: 204 })));
|
|
663
|
+
const res = await dispatchTool({
|
|
664
|
+
name: "delete_prompt",
|
|
665
|
+
args: { promptId: 99 },
|
|
666
|
+
apiKey: "argosvix_live_test",
|
|
667
|
+
apiBase: "https://ingest.example.com",
|
|
668
|
+
});
|
|
669
|
+
expect(res.isError).toBeUndefined();
|
|
670
|
+
const parsed = JSON.parse(res.content[0]?.text ?? "{}");
|
|
671
|
+
expect(parsed.ok).toBe(true);
|
|
672
|
+
expect(parsed.status).toBe(204);
|
|
673
|
+
const fetchMock = global.fetch;
|
|
674
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
675
|
+
expect(url).toContain("/v1/prompts/99");
|
|
676
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
677
|
+
expect(init.method).toBe("DELETE");
|
|
678
|
+
expect(init.body).toBeUndefined();
|
|
679
|
+
});
|
|
680
|
+
it("create_eval_criterion POSTs allowlisted body to /v1/eval-criteria (= v1.6 #13-2)", async () => {
|
|
681
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 11, name: "helpfulness" }), {
|
|
682
|
+
status: 201,
|
|
683
|
+
headers: { "content-type": "application/json" },
|
|
684
|
+
})));
|
|
685
|
+
const res = await dispatchTool({
|
|
686
|
+
name: "create_eval_criterion",
|
|
687
|
+
args: {
|
|
688
|
+
name: "helpfulness",
|
|
689
|
+
rubric: "Score how helpful the answer is to the user.",
|
|
690
|
+
scaleMin: 1,
|
|
691
|
+
scaleMax: 5,
|
|
692
|
+
},
|
|
693
|
+
apiKey: "argosvix_live_test",
|
|
694
|
+
apiBase: "https://ingest.example.com",
|
|
695
|
+
});
|
|
696
|
+
expect(res.isError).toBeUndefined();
|
|
697
|
+
const fetchMock = global.fetch;
|
|
698
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
699
|
+
expect(url).toContain("/v1/eval-criteria");
|
|
700
|
+
expect(url).not.toContain("/v1/eval-criteria/");
|
|
701
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
702
|
+
expect(init.method).toBe("POST");
|
|
703
|
+
const body = JSON.parse(init.body);
|
|
704
|
+
expect(body).toEqual({
|
|
705
|
+
name: "helpfulness",
|
|
706
|
+
rubric: "Score how helpful the answer is to the user.",
|
|
707
|
+
scaleMin: 1,
|
|
708
|
+
scaleMax: 5,
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
it("create_eval_criterion rejects missing required fields (= v1.6 #13-2)", async () => {
|
|
712
|
+
const res = await dispatchTool({
|
|
713
|
+
name: "create_eval_criterion",
|
|
714
|
+
args: { name: "x" },
|
|
715
|
+
apiKey: "argosvix_live_test",
|
|
716
|
+
apiBase: "https://ingest.example.com",
|
|
717
|
+
});
|
|
718
|
+
expect(res.isError).toBe(true);
|
|
719
|
+
expect(res.content[0]?.text).toContain("name + rubric + scaleMin + scaleMax required");
|
|
720
|
+
});
|
|
721
|
+
it("update_eval_criterion sends PATCH with full body to /v1/eval-criteria/:id (= v1.6 #13-2)", async () => {
|
|
722
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 11, name: "renamed" }), {
|
|
723
|
+
status: 200,
|
|
724
|
+
headers: { "content-type": "application/json" },
|
|
725
|
+
})));
|
|
726
|
+
const res = await dispatchTool({
|
|
727
|
+
name: "update_eval_criterion",
|
|
728
|
+
args: {
|
|
729
|
+
criterionId: 11,
|
|
730
|
+
name: "renamed",
|
|
731
|
+
rubric: "Updated rubric narrative here.",
|
|
732
|
+
scaleMin: 1,
|
|
733
|
+
scaleMax: 10,
|
|
734
|
+
},
|
|
735
|
+
apiKey: "argosvix_live_test",
|
|
736
|
+
apiBase: "https://ingest.example.com",
|
|
737
|
+
});
|
|
738
|
+
expect(res.isError).toBeUndefined();
|
|
739
|
+
const fetchMock = global.fetch;
|
|
740
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
741
|
+
expect(url).toContain("/v1/eval-criteria/11");
|
|
742
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
743
|
+
expect(init.method).toBe("PATCH");
|
|
744
|
+
const body = JSON.parse(init.body);
|
|
745
|
+
expect(body).toEqual({
|
|
746
|
+
name: "renamed",
|
|
747
|
+
rubric: "Updated rubric narrative here.",
|
|
748
|
+
scaleMin: 1,
|
|
749
|
+
scaleMax: 10,
|
|
750
|
+
});
|
|
751
|
+
expect(body.criterionId).toBeUndefined();
|
|
752
|
+
});
|
|
753
|
+
it("get_llm_budget hits /v1/account/llm-feature-budget GET (= v1.6 #13-7)", async () => {
|
|
754
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ budgetUsd: 5, spentUsd: 1.2, remainingUsd: 3.8 }), { status: 200, headers: { "content-type": "application/json" } })));
|
|
755
|
+
const res = await dispatchTool({
|
|
756
|
+
name: "get_llm_budget",
|
|
757
|
+
args: {},
|
|
758
|
+
apiKey: "argosvix_live_test",
|
|
759
|
+
apiBase: "https://ingest.example.com",
|
|
760
|
+
});
|
|
761
|
+
expect(res.isError).toBeUndefined();
|
|
762
|
+
const fetchMock = global.fetch;
|
|
763
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
764
|
+
expect(url).toContain("/v1/account/llm-feature-budget");
|
|
765
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
766
|
+
expect(init.method ?? "GET").toBe("GET");
|
|
767
|
+
});
|
|
768
|
+
it("raise_llm_budget PATCHes /v1/account/llm-feature-budget with budgetUsd (= v1.6 #13-7)", async () => {
|
|
769
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ ok: true, budgetUsd: 30, spentUsd: 0, remainingUsd: 30 }), { status: 200, headers: { "content-type": "application/json" } })));
|
|
770
|
+
const res = await dispatchTool({
|
|
771
|
+
name: "raise_llm_budget",
|
|
772
|
+
args: { budgetUsd: 30 },
|
|
773
|
+
apiKey: "argosvix_live_test",
|
|
774
|
+
apiBase: "https://ingest.example.com",
|
|
775
|
+
});
|
|
776
|
+
expect(res.isError).toBeUndefined();
|
|
777
|
+
const fetchMock = global.fetch;
|
|
778
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
779
|
+
expect(url).toContain("/v1/account/llm-feature-budget");
|
|
780
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
781
|
+
expect(init.method).toBe("PATCH");
|
|
782
|
+
const body = JSON.parse(init.body);
|
|
783
|
+
expect(body).toEqual({ budgetUsd: 30 });
|
|
784
|
+
});
|
|
785
|
+
it("raise_llm_budget rejects missing budgetUsd (= v1.6 #13-7)", async () => {
|
|
786
|
+
const res = await dispatchTool({
|
|
787
|
+
name: "raise_llm_budget",
|
|
788
|
+
args: {},
|
|
789
|
+
apiKey: "argosvix_live_test",
|
|
790
|
+
apiBase: "https://ingest.example.com",
|
|
791
|
+
});
|
|
792
|
+
expect(res.isError).toBe(true);
|
|
793
|
+
expect(res.content[0]?.text).toContain("budgetUsd required");
|
|
794
|
+
});
|
|
795
|
+
it("test_webhook POSTs allowlisted body to /v1/alerts/test-webhook (= v1.6 #13-5)", async () => {
|
|
796
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ ok: true, delivered: true, message: "test webhook delivered" }), {
|
|
797
|
+
status: 200,
|
|
798
|
+
headers: { "content-type": "application/json" },
|
|
799
|
+
})));
|
|
800
|
+
const res = await dispatchTool({
|
|
801
|
+
name: "test_webhook",
|
|
802
|
+
args: {
|
|
803
|
+
url: "https://example.com/hook",
|
|
804
|
+
secret: "shhh",
|
|
805
|
+
alertName: "test alert",
|
|
806
|
+
},
|
|
807
|
+
apiKey: "argosvix_live_test",
|
|
808
|
+
apiBase: "https://ingest.example.com",
|
|
809
|
+
});
|
|
810
|
+
expect(res.isError).toBeUndefined();
|
|
811
|
+
const fetchMock = global.fetch;
|
|
812
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
813
|
+
expect(url).toContain("/v1/alerts/test-webhook");
|
|
814
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
815
|
+
expect(init.method).toBe("POST");
|
|
816
|
+
const body = JSON.parse(init.body);
|
|
817
|
+
expect(body).toEqual({
|
|
818
|
+
url: "https://example.com/hook",
|
|
819
|
+
secret: "shhh",
|
|
820
|
+
alertName: "test alert",
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
it("test_webhook rejects missing url (= v1.6 #13-5)", async () => {
|
|
824
|
+
const res = await dispatchTool({
|
|
825
|
+
name: "test_webhook",
|
|
826
|
+
args: { secret: "shhh" },
|
|
827
|
+
apiKey: "argosvix_live_test",
|
|
828
|
+
apiBase: "https://ingest.example.com",
|
|
829
|
+
});
|
|
830
|
+
expect(res.isError).toBe(true);
|
|
831
|
+
expect(res.content[0]?.text).toContain("url required");
|
|
832
|
+
});
|
|
833
|
+
it("delete_eval_criterion sends DELETE to /v1/eval-criteria/:id without body (= v1.6 #13-2)", async () => {
|
|
834
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(null, { status: 204 })));
|
|
835
|
+
const res = await dispatchTool({
|
|
836
|
+
name: "delete_eval_criterion",
|
|
837
|
+
args: { criterionId: 11 },
|
|
838
|
+
apiKey: "argosvix_live_test",
|
|
839
|
+
apiBase: "https://ingest.example.com",
|
|
840
|
+
});
|
|
841
|
+
expect(res.isError).toBeUndefined();
|
|
842
|
+
const parsed = JSON.parse(res.content[0]?.text ?? "{}");
|
|
843
|
+
expect(parsed.ok).toBe(true);
|
|
844
|
+
expect(parsed.status).toBe(204);
|
|
845
|
+
const fetchMock = global.fetch;
|
|
846
|
+
const url = String(fetchMock.mock.calls[0]?.[0]);
|
|
847
|
+
expect(url).toContain("/v1/eval-criteria/11");
|
|
848
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
849
|
+
expect(init.method).toBe("DELETE");
|
|
850
|
+
expect(init.body).toBeUndefined();
|
|
851
|
+
});
|
|
356
852
|
it("silence_alert rejects invalid alertId (= path injection 防御)", async () => {
|
|
357
853
|
const res = await dispatchTool({
|
|
358
854
|
name: "silence_alert",
|