llm-mock-server 1.0.4 → 1.0.6

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.
Files changed (113) hide show
  1. package/.desloppify/query.json +1162 -62
  2. package/.desloppify/review_packet_blind.json +18 -18
  3. package/.desloppify/review_packets/holistic_packet_20260315_185401.json +1407 -0
  4. package/.desloppify/review_packets/holistic_packet_20260315_185613.json +1407 -0
  5. package/.desloppify/state-typescript.json +2530 -645
  6. package/.desloppify/state-typescript.json.bak +2494 -582
  7. package/.desloppify/subagents/runs/20260315_185401/logs/batch-1.log +384 -0
  8. package/.desloppify/subagents/runs/20260315_185401/logs/batch-10.log +484 -0
  9. package/.desloppify/subagents/runs/20260315_185401/logs/batch-2.log +408 -0
  10. package/.desloppify/subagents/runs/20260315_185401/logs/batch-3.log +416 -0
  11. package/.desloppify/subagents/runs/20260315_185401/logs/batch-4.log +360 -0
  12. package/.desloppify/subagents/runs/20260315_185401/logs/batch-5.log +360 -0
  13. package/.desloppify/subagents/runs/20260315_185401/logs/batch-6.log +364 -0
  14. package/.desloppify/subagents/runs/20260315_185401/logs/batch-7.log +428 -0
  15. package/.desloppify/subagents/runs/20260315_185401/logs/batch-8.log +388 -0
  16. package/.desloppify/subagents/runs/20260315_185401/logs/batch-9.log +500 -0
  17. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-1.md +83 -0
  18. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-10.md +108 -0
  19. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-2.md +89 -0
  20. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-3.md +91 -0
  21. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-4.md +77 -0
  22. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-5.md +77 -0
  23. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-6.md +78 -0
  24. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-7.md +94 -0
  25. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-8.md +84 -0
  26. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-9.md +112 -0
  27. package/.desloppify/subagents/runs/20260315_185401/results/batch-1.raw.txt +0 -0
  28. package/.desloppify/subagents/runs/20260315_185401/results/batch-10.raw.txt +0 -0
  29. package/.desloppify/subagents/runs/20260315_185401/results/batch-2.raw.txt +0 -0
  30. package/.desloppify/subagents/runs/20260315_185401/results/batch-3.raw.txt +0 -0
  31. package/.desloppify/subagents/runs/20260315_185401/results/batch-4.raw.txt +0 -0
  32. package/.desloppify/subagents/runs/20260315_185401/results/batch-5.raw.txt +0 -0
  33. package/.desloppify/subagents/runs/20260315_185401/results/batch-6.raw.txt +0 -0
  34. package/.desloppify/subagents/runs/20260315_185401/results/batch-7.raw.txt +0 -0
  35. package/.desloppify/subagents/runs/20260315_185401/results/batch-8.raw.txt +0 -0
  36. package/.desloppify/subagents/runs/20260315_185401/results/batch-9.raw.txt +0 -0
  37. package/.desloppify/subagents/runs/20260315_185401/run.log +36 -0
  38. package/.desloppify/subagents/runs/20260315_185401/run_summary.json +156 -0
  39. package/.desloppify/subagents/runs/20260315_185613/holistic_findings_merged.json +741 -0
  40. package/.desloppify/subagents/runs/20260315_185613/logs/batch-1.log +579 -0
  41. package/.desloppify/subagents/runs/20260315_185613/logs/batch-10.log +1537 -0
  42. package/.desloppify/subagents/runs/20260315_185613/logs/batch-2.log +829 -0
  43. package/.desloppify/subagents/runs/20260315_185613/logs/batch-3.log +927 -0
  44. package/.desloppify/subagents/runs/20260315_185613/logs/batch-4.log +429 -0
  45. package/.desloppify/subagents/runs/20260315_185613/logs/batch-5.log +276 -0
  46. package/.desloppify/subagents/runs/20260315_185613/logs/batch-6.log +450 -0
  47. package/.desloppify/subagents/runs/20260315_185613/logs/batch-7.log +730 -0
  48. package/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log +698 -0
  49. package/.desloppify/subagents/runs/20260315_185613/logs/batch-9.log +938 -0
  50. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-1.md +83 -0
  51. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-10.md +108 -0
  52. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-2.md +89 -0
  53. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-3.md +91 -0
  54. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-4.md +77 -0
  55. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-5.md +77 -0
  56. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-6.md +78 -0
  57. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-7.md +94 -0
  58. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-8.md +84 -0
  59. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-9.md +112 -0
  60. package/.desloppify/subagents/runs/20260315_185613/results/batch-1.raw.txt +78 -0
  61. package/.desloppify/subagents/runs/20260315_185613/results/batch-10.raw.txt +242 -0
  62. package/.desloppify/subagents/runs/20260315_185613/results/batch-2.raw.txt +102 -0
  63. package/.desloppify/subagents/runs/20260315_185613/results/batch-3.raw.txt +94 -0
  64. package/.desloppify/subagents/runs/20260315_185613/results/batch-4.raw.txt +86 -0
  65. package/.desloppify/subagents/runs/20260315_185613/results/batch-5.raw.txt +1 -0
  66. package/.desloppify/subagents/runs/20260315_185613/results/batch-6.raw.txt +87 -0
  67. package/.desloppify/subagents/runs/20260315_185613/results/batch-7.raw.txt +1 -0
  68. package/.desloppify/subagents/runs/20260315_185613/results/batch-8.raw.txt +107 -0
  69. package/.desloppify/subagents/runs/20260315_185613/results/batch-9.raw.txt +67 -0
  70. package/.desloppify/subagents/runs/20260315_185613/run.log +96 -0
  71. package/.desloppify/subagents/runs/20260315_185613/run_summary.json +156 -0
  72. package/.github/workflows/docs.yml +46 -0
  73. package/.github/workflows/test.yml +3 -0
  74. package/README.md +8 -4
  75. package/docs/ARCHITECTURE.md +11 -11
  76. package/package.json +18 -11
  77. package/scorecard.png +0 -0
  78. package/src/{cli.ts → cli/cli.ts} +6 -11
  79. package/src/{cli-validators.ts → cli/validators.ts} +10 -9
  80. package/src/formats/anthropic/index.ts +2 -2
  81. package/src/formats/anthropic/parse.ts +5 -2
  82. package/src/formats/anthropic/serialize.ts +3 -3
  83. package/src/formats/openai/{index.ts → chat-completions/index.ts} +3 -3
  84. package/src/formats/openai/{parse.ts → chat-completions/parse.ts} +5 -2
  85. package/src/formats/openai/{serialize.ts → chat-completions/serialize.ts} +3 -3
  86. package/src/formats/{responses → openai/responses}/index.ts +2 -2
  87. package/src/formats/{responses → openai/responses}/parse.ts +5 -2
  88. package/src/formats/{responses → openai/responses}/serialize.ts +3 -3
  89. package/src/formats/request-helpers.ts +6 -1
  90. package/src/formats/serialize-helpers.ts +9 -4
  91. package/src/formats/types.ts +2 -6
  92. package/src/history.ts +6 -2
  93. package/src/loader.ts +2 -1
  94. package/src/mock-server.ts +55 -106
  95. package/src/route-handler.ts +7 -11
  96. package/src/rule-builder.ts +73 -0
  97. package/src/rule-engine.ts +3 -10
  98. package/src/sse-writer.ts +1 -1
  99. package/src/types/reply.ts +51 -8
  100. package/src/types/request.ts +21 -6
  101. package/src/types/rule.ts +65 -7
  102. package/test/cli-validators.test.ts +13 -5
  103. package/test/formats/openai.test.ts +40 -28
  104. package/test/formats/responses.test.ts +2 -2
  105. package/test/history.test.ts +1 -1
  106. package/test/loader.test.ts +3 -3
  107. package/test/logger.test.ts +2 -2
  108. package/test/mock-server.test.ts +1 -1
  109. package/test/rule-engine.test.ts +1 -1
  110. package/tsconfig.json +2 -4
  111. package/typedoc.json +9 -0
  112. /package/src/formats/openai/{schema.ts → chat-completions/schema.ts} +0 -0
  113. /package/src/formats/{responses → openai/responses}/schema.ts +0 -0
@@ -0,0 +1,96 @@
1
+ 2026-03-15T18:56:13+00:00 run-start runner=codex parallel=True max_parallel=1 timeout=1200s heartbeat=15.0s stall_warning=0s stall_kill=120s retries=2 retry_backoff=2.0s upper_bound=200m selected=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
+ 2026-03-15T18:56:13+00:00 run-path /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613
3
+ 2026-03-15T18:56:13+00:00 packet /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packets/holistic_packet_20260315_185613.json
4
+ 2026-03-15T18:56:13+00:00 blind-packet /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packet_blind.json
5
+ 2026-03-15T18:56:13+00:00 batch-queued batch=1 position=1/10
6
+ 2026-03-15T18:56:13+00:00 batch-start batch=1 position=1/10
7
+ 2026-03-15T18:56:13+00:00 batch-queued batch=2 position=2/10
8
+ 2026-03-15T18:56:13+00:00 batch-queued batch=3 position=3/10
9
+ 2026-03-15T18:56:13+00:00 batch-queued batch=4 position=4/10
10
+ 2026-03-15T18:56:13+00:00 batch-queued batch=5 position=5/10
11
+ 2026-03-15T18:56:13+00:00 batch-queued batch=6 position=6/10
12
+ 2026-03-15T18:56:13+00:00 batch-queued batch=7 position=7/10
13
+ 2026-03-15T18:56:13+00:00 batch-queued batch=8 position=8/10
14
+ 2026-03-15T18:56:13+00:00 batch-queued batch=9 position=9/10
15
+ 2026-03-15T18:56:13+00:00 batch-queued batch=10 position=10/10
16
+ 2026-03-15T18:56:28+00:00 heartbeat active=[1] queued=[2, 3, 4, 5, 6, 7, 8, 9, 10] elapsed={1:15}
17
+ 2026-03-15T18:56:43+00:00 heartbeat active=[1] queued=[2, 3, 4, 5, 6, 7, 8, 9, 10] elapsed={1:30}
18
+ 2026-03-15T18:56:58+00:00 heartbeat active=[1] queued=[2, 3, 4, 5, 6, 7, 8, 9, 10] elapsed={1:45}
19
+ 2026-03-15T18:57:02+00:00 batch-start batch=2 position=2/10
20
+ 2026-03-15T18:57:02+00:00 batch-done batch=1 position=1/10 code=0 elapsed=49
21
+ 2026-03-15T18:57:17+00:00 heartbeat active=[2] queued=[3, 4, 5, 6, 7, 8, 9, 10] elapsed={2:15}
22
+ 2026-03-15T18:57:32+00:00 heartbeat active=[2] queued=[3, 4, 5, 6, 7, 8, 9, 10] elapsed={2:30}
23
+ 2026-03-15T18:57:47+00:00 heartbeat active=[2] queued=[3, 4, 5, 6, 7, 8, 9, 10] elapsed={2:45}
24
+ 2026-03-15T18:58:02+00:00 heartbeat active=[2] queued=[3, 4, 5, 6, 7, 8, 9, 10] elapsed={2:60}
25
+ 2026-03-15T18:58:17+00:00 heartbeat active=[2] queued=[3, 4, 5, 6, 7, 8, 9, 10] elapsed={2:75}
26
+ 2026-03-15T18:58:32+00:00 heartbeat active=[2] queued=[3, 4, 5, 6, 7, 8, 9, 10] elapsed={2:90}
27
+ 2026-03-15T18:58:47+00:00 heartbeat active=[2] queued=[3, 4, 5, 6, 7, 8, 9, 10] elapsed={2:105}
28
+ 2026-03-15T18:59:02+00:00 heartbeat active=[2] queued=[3, 4, 5, 6, 7, 8, 9, 10] elapsed={2:120}
29
+ 2026-03-15T18:59:03+00:00 batch-done batch=2 position=2/10 code=0 elapsed=121
30
+ 2026-03-15T18:59:03+00:00 batch-start batch=3 position=3/10
31
+ 2026-03-15T18:59:18+00:00 heartbeat active=[3] queued=[4, 5, 6, 7, 8, 9, 10] elapsed={3:15}
32
+ 2026-03-15T18:59:33+00:00 heartbeat active=[3] queued=[4, 5, 6, 7, 8, 9, 10] elapsed={3:30}
33
+ 2026-03-15T18:59:48+00:00 heartbeat active=[3] queued=[4, 5, 6, 7, 8, 9, 10] elapsed={3:45}
34
+ 2026-03-15T19:00:03+00:00 heartbeat active=[3] queued=[4, 5, 6, 7, 8, 9, 10] elapsed={3:60}
35
+ 2026-03-15T19:00:18+00:00 heartbeat active=[3] queued=[4, 5, 6, 7, 8, 9, 10] elapsed={3:75}
36
+ 2026-03-15T19:00:33+00:00 heartbeat active=[3] queued=[4, 5, 6, 7, 8, 9, 10] elapsed={3:90}
37
+ 2026-03-15T19:00:48+00:00 heartbeat active=[3] queued=[4, 5, 6, 7, 8, 9, 10] elapsed={3:105}
38
+ 2026-03-15T19:00:54+00:00 batch-done batch=3 position=3/10 code=0 elapsed=110
39
+ 2026-03-15T19:00:54+00:00 batch-start batch=4 position=4/10
40
+ 2026-03-15T19:01:09+00:00 heartbeat active=[4] queued=[5, 6, 7, 8, 9, 10] elapsed={4:15}
41
+ 2026-03-15T19:01:24+00:00 heartbeat active=[4] queued=[5, 6, 7, 8, 9, 10] elapsed={4:30}
42
+ 2026-03-15T19:01:39+00:00 heartbeat active=[4] queued=[5, 6, 7, 8, 9, 10] elapsed={4:45}
43
+ 2026-03-15T19:01:48+00:00 batch-done batch=4 position=4/10 code=0 elapsed=54
44
+ 2026-03-15T19:01:48+00:00 batch-start batch=5 position=5/10
45
+ 2026-03-15T19:02:03+00:00 heartbeat active=[5] queued=[6, 7, 8, 9, 10] elapsed={5:15}
46
+ 2026-03-15T19:02:18+00:00 heartbeat active=[5] queued=[6, 7, 8, 9, 10] elapsed={5:30}
47
+ 2026-03-15T19:02:28+00:00 batch-start batch=6 position=6/10
48
+ 2026-03-15T19:02:28+00:00 batch-done batch=5 position=5/10 code=0 elapsed=40
49
+ 2026-03-15T19:02:43+00:00 heartbeat active=[6] queued=[7, 8, 9, 10] elapsed={6:15}
50
+ 2026-03-15T19:02:58+00:00 heartbeat active=[6] queued=[7, 8, 9, 10] elapsed={6:30}
51
+ 2026-03-15T19:03:13+00:00 heartbeat active=[6] queued=[7, 8, 9, 10] elapsed={6:45}
52
+ 2026-03-15T19:03:19+00:00 batch-start batch=7 position=7/10
53
+ 2026-03-15T19:03:19+00:00 batch-done batch=6 position=6/10 code=0 elapsed=50
54
+ 2026-03-15T19:03:34+00:00 heartbeat active=[7] queued=[8, 9, 10] elapsed={7:15}
55
+ 2026-03-15T19:03:49+00:00 heartbeat active=[7] queued=[8, 9, 10] elapsed={7:30}
56
+ 2026-03-15T19:04:04+00:00 heartbeat active=[7] queued=[8, 9, 10] elapsed={7:45}
57
+ 2026-03-15T19:04:19+00:00 heartbeat active=[7] queued=[8, 9, 10] elapsed={7:60}
58
+ 2026-03-15T19:04:34+00:00 heartbeat active=[7] queued=[8, 9, 10] elapsed={7:75}
59
+ 2026-03-15T19:04:49+00:00 heartbeat active=[7] queued=[8, 9, 10] elapsed={7:90}
60
+ 2026-03-15T19:05:02+00:00 batch-done batch=7 position=7/10 code=0 elapsed=103
61
+ 2026-03-15T19:05:02+00:00 batch-start batch=8 position=8/10
62
+ 2026-03-15T19:05:17+00:00 heartbeat active=[8] queued=[9, 10] elapsed={8:15}
63
+ 2026-03-15T19:05:32+00:00 heartbeat active=[8] queued=[9, 10] elapsed={8:30}
64
+ 2026-03-15T19:05:47+00:00 heartbeat active=[8] queued=[9, 10] elapsed={8:45}
65
+ 2026-03-15T19:06:02+00:00 heartbeat active=[8] queued=[9, 10] elapsed={8:60}
66
+ 2026-03-15T19:06:17+00:00 heartbeat active=[8] queued=[9, 10] elapsed={8:75}
67
+ 2026-03-15T19:06:32+00:00 heartbeat active=[8] queued=[9, 10] elapsed={8:90}
68
+ 2026-03-15T19:06:47+00:00 heartbeat active=[8] queued=[9, 10] elapsed={8:105}
69
+ 2026-03-15T19:06:51+00:00 batch-start batch=9 position=9/10
70
+ 2026-03-15T19:06:51+00:00 batch-done batch=8 position=8/10 code=0 elapsed=108
71
+ 2026-03-15T19:07:06+00:00 heartbeat active=[9] queued=[10] elapsed={9:15}
72
+ 2026-03-15T19:07:21+00:00 heartbeat active=[9] queued=[10] elapsed={9:30}
73
+ 2026-03-15T19:07:36+00:00 heartbeat active=[9] queued=[10] elapsed={9:45}
74
+ 2026-03-15T19:07:51+00:00 heartbeat active=[9] queued=[10] elapsed={9:60}
75
+ 2026-03-15T19:08:06+00:00 heartbeat active=[9] queued=[10] elapsed={9:75}
76
+ 2026-03-15T19:08:21+00:00 heartbeat active=[9] queued=[10] elapsed={9:90}
77
+ 2026-03-15T19:08:35+00:00 batch-done batch=9 position=9/10 code=0 elapsed=103
78
+ 2026-03-15T19:08:35+00:00 batch-start batch=10 position=10/10
79
+ 2026-03-15T19:08:50+00:00 heartbeat active=[10] queued=[] elapsed={10:15}
80
+ 2026-03-15T19:09:05+00:00 heartbeat active=[10] queued=[] elapsed={10:30}
81
+ 2026-03-15T19:09:20+00:00 heartbeat active=[10] queued=[] elapsed={10:45}
82
+ 2026-03-15T19:09:35+00:00 heartbeat active=[10] queued=[] elapsed={10:60}
83
+ 2026-03-15T19:09:50+00:00 heartbeat active=[10] queued=[] elapsed={10:75}
84
+ 2026-03-15T19:10:05+00:00 heartbeat active=[10] queued=[] elapsed={10:90}
85
+ 2026-03-15T19:10:20+00:00 heartbeat active=[10] queued=[] elapsed={10:105}
86
+ 2026-03-15T19:10:35+00:00 heartbeat active=[10] queued=[] elapsed={10:120}
87
+ 2026-03-15T19:10:50+00:00 heartbeat active=[10] queued=[] elapsed={10:135}
88
+ 2026-03-15T19:11:05+00:00 heartbeat active=[10] queued=[] elapsed={10:150}
89
+ 2026-03-15T19:11:20+00:00 heartbeat active=[10] queued=[] elapsed={10:165}
90
+ 2026-03-15T19:11:35+00:00 heartbeat active=[10] queued=[] elapsed={10:180}
91
+ 2026-03-15T19:11:50+00:00 heartbeat active=[10] queued=[] elapsed={10:195}
92
+ 2026-03-15T19:12:05+00:00 heartbeat active=[10] queued=[] elapsed={10:210}
93
+ 2026-03-15T19:12:20+00:00 heartbeat active=[10] queued=[] elapsed={10:225}
94
+ 2026-03-15T19:12:25+00:00 batch-done batch=10 position=10/10 code=0 elapsed=230
95
+ 2026-03-15T19:12:25+00:00 run-summary /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/run_summary.json
96
+ 2026-03-15T19:12:25+00:00 run-finished successful=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] failed=[] imported=/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/holistic_findings_merged.json
@@ -0,0 +1,156 @@
1
+ {
2
+ "created_at": "2026-03-15T18:56:13+00:00",
3
+ "run_stamp": "20260315_185613",
4
+ "runner": "codex",
5
+ "parallel": true,
6
+ "selected_batches": [
7
+ 1,
8
+ 2,
9
+ 3,
10
+ 4,
11
+ 5,
12
+ 6,
13
+ 7,
14
+ 8,
15
+ 9,
16
+ 10
17
+ ],
18
+ "successful_batches": [
19
+ 1,
20
+ 2,
21
+ 3,
22
+ 4,
23
+ 5,
24
+ 6,
25
+ 7,
26
+ 8,
27
+ 9,
28
+ 10
29
+ ],
30
+ "failed_batches": [],
31
+ "allow_partial": false,
32
+ "max_parallel_batches": 1,
33
+ "batch_timeout_seconds": 1200,
34
+ "batch_max_retries": 2,
35
+ "batch_retry_backoff_seconds": 2.0,
36
+ "batch_heartbeat_seconds": 15.0,
37
+ "batch_stall_warning_seconds": 0,
38
+ "batch_stall_kill_seconds": 120,
39
+ "immutable_packet": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packets/holistic_packet_20260315_185613.json",
40
+ "blind_packet": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packet_blind.json",
41
+ "run_dir": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613",
42
+ "logs_dir": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs",
43
+ "run_log": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/run.log",
44
+ "batches": {
45
+ "1": {
46
+ "position": 1,
47
+ "status": "succeeded",
48
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-1.md",
49
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-1.raw.txt",
50
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-1.log",
51
+ "started_at": "2026-03-15T18:56:13+00:00",
52
+ "elapsed_seconds": 49,
53
+ "exit_code": 0,
54
+ "completed_at": "2026-03-15T18:57:02+00:00"
55
+ },
56
+ "2": {
57
+ "position": 2,
58
+ "status": "succeeded",
59
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-2.md",
60
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-2.raw.txt",
61
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-2.log",
62
+ "started_at": "2026-03-15T18:57:02+00:00",
63
+ "elapsed_seconds": 121,
64
+ "exit_code": 0,
65
+ "completed_at": "2026-03-15T18:59:03+00:00"
66
+ },
67
+ "3": {
68
+ "position": 3,
69
+ "status": "succeeded",
70
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-3.md",
71
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-3.raw.txt",
72
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-3.log",
73
+ "started_at": "2026-03-15T18:59:03+00:00",
74
+ "elapsed_seconds": 110,
75
+ "exit_code": 0,
76
+ "completed_at": "2026-03-15T19:00:54+00:00"
77
+ },
78
+ "4": {
79
+ "position": 4,
80
+ "status": "succeeded",
81
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-4.md",
82
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-4.raw.txt",
83
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-4.log",
84
+ "started_at": "2026-03-15T19:00:54+00:00",
85
+ "elapsed_seconds": 54,
86
+ "exit_code": 0,
87
+ "completed_at": "2026-03-15T19:01:48+00:00"
88
+ },
89
+ "5": {
90
+ "position": 5,
91
+ "status": "succeeded",
92
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-5.md",
93
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-5.raw.txt",
94
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-5.log",
95
+ "started_at": "2026-03-15T19:01:48+00:00",
96
+ "elapsed_seconds": 40,
97
+ "exit_code": 0,
98
+ "completed_at": "2026-03-15T19:02:28+00:00"
99
+ },
100
+ "6": {
101
+ "position": 6,
102
+ "status": "succeeded",
103
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-6.md",
104
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-6.raw.txt",
105
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-6.log",
106
+ "started_at": "2026-03-15T19:02:28+00:00",
107
+ "elapsed_seconds": 50,
108
+ "exit_code": 0,
109
+ "completed_at": "2026-03-15T19:03:19+00:00"
110
+ },
111
+ "7": {
112
+ "position": 7,
113
+ "status": "succeeded",
114
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-7.md",
115
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-7.raw.txt",
116
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-7.log",
117
+ "started_at": "2026-03-15T19:03:19+00:00",
118
+ "elapsed_seconds": 103,
119
+ "exit_code": 0,
120
+ "completed_at": "2026-03-15T19:05:02+00:00"
121
+ },
122
+ "8": {
123
+ "position": 8,
124
+ "status": "succeeded",
125
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-8.md",
126
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-8.raw.txt",
127
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log",
128
+ "started_at": "2026-03-15T19:05:02+00:00",
129
+ "elapsed_seconds": 108,
130
+ "exit_code": 0,
131
+ "completed_at": "2026-03-15T19:06:51+00:00"
132
+ },
133
+ "9": {
134
+ "position": 9,
135
+ "status": "succeeded",
136
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-9.md",
137
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-9.raw.txt",
138
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-9.log",
139
+ "started_at": "2026-03-15T19:06:51+00:00",
140
+ "elapsed_seconds": 103,
141
+ "exit_code": 0,
142
+ "completed_at": "2026-03-15T19:08:35+00:00"
143
+ },
144
+ "10": {
145
+ "position": 10,
146
+ "status": "succeeded",
147
+ "prompt_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/prompts/batch-10.md",
148
+ "result_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-10.raw.txt",
149
+ "log_path": "/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-10.log",
150
+ "started_at": "2026-03-15T19:08:35+00:00",
151
+ "elapsed_seconds": 230,
152
+ "exit_code": 0,
153
+ "completed_at": "2026-03-15T19:12:25+00:00"
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,46 @@
1
+ name: Deploy API docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: read
9
+ pages: write
10
+ id-token: write
11
+
12
+ concurrency:
13
+ group: pages
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ deploy:
18
+ runs-on: ubuntu-latest
19
+ environment:
20
+ name: github-pages
21
+ url: ${{ steps.deployment.outputs.page_url }}
22
+
23
+ steps:
24
+ - name: Checkout code
25
+ uses: actions/checkout@v6.0.2
26
+
27
+ - name: Setup Node.js
28
+ uses: actions/setup-node@v6.3.0
29
+ with:
30
+ node-version-file: 'package.json'
31
+ cache: 'npm'
32
+
33
+ - name: Install dependencies
34
+ run: npm ci
35
+
36
+ - name: Generate docs
37
+ run: npm run docs
38
+
39
+ - name: Upload pages artifact
40
+ uses: actions/upload-pages-artifact@v4
41
+ with:
42
+ path: docs/api
43
+
44
+ - name: Deploy to GitHub Pages
45
+ id: deployment
46
+ uses: actions/deploy-pages@v4
@@ -33,5 +33,8 @@ jobs:
33
33
  - name: Lint
34
34
  run: npm run lint
35
35
 
36
+ - name: Validate docs
37
+ run: npm run docs:check
38
+
36
39
  - name: Run tests
37
40
  run: npm test
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # llm-mock-server
1
+ # llm-mock-server [![npm version](https://img.shields.io/npm/v/llm-mock-server)](https://www.npmjs.com/package/llm-mock-server) ![npm downloads](https://img.shields.io/npm/d18m/llm-mock-server)
2
2
 
3
- A mock LLM server for testing. It handles OpenAI `/chat/completions`, Anthropic `/messages`, and OpenAI `/responses` API formats, with both streaming (SSE) and non-streaming responses. Point any client at it and get instant, deterministic replies.
3
+ A mock LLM server for testing. It handles OpenAI `/chat/completions`, Anthropic `/messages`, and OpenAI `/responses` API formats, with both streaming (SSE) and non-streaming responses. Point any client at it and get instant, deterministic replies. Used by [xcode-copilot-server](https://github.com/theblixguy/xcode-copilot-server) and [copilot-sdk-proxy](https://github.com/theblixguy/copilot-sdk-proxy) for their integration tests.
4
4
 
5
5
  ## Table of contents
6
6
 
@@ -25,6 +25,7 @@ A mock LLM server for testing. It handles OpenAI `/chat/completions`, Anthropic
25
25
  - [CLI](#cli)
26
26
  - [Security](#security)
27
27
  - [Architecture](#architecture)
28
+ - [API reference](#api-reference)
28
29
  - [Licence](#licence)
29
30
 
30
31
  ## Quick start
@@ -390,7 +391,6 @@ llm-mock-server [options]
390
391
  | `--port` | `-p` | `5555` | Port to listen on |
391
392
  | `--host` | `-H` | `127.0.0.1` | Host to bind to |
392
393
  | `--rules` | `-r` | | Path to rules file or directory |
393
- | `--handler` | | | Path to handler file |
394
394
  | `--latency` | `-l` | `0` | Ms between SSE chunks |
395
395
  | `--chunk-size` | `-c` | `0` | Characters per SSE chunk |
396
396
  | `--fallback` | `-f` | | Fallback reply text |
@@ -410,7 +410,7 @@ This is a testing tool, not a production service. It's designed to run locally o
410
410
 
411
411
  ### Handler files execute code
412
412
 
413
- When you call `server.load()` or pass `--handler` on the CLI, `.ts`/`.js` files are loaded via dynamic `import()`. They run with the same permissions as the rest of your Node.js process. Only load files you trust.
413
+ When you call `server.load()` or pass `--rules` on the CLI, `.ts`/`.js` files are loaded via dynamic `import()`. They run with the same permissions as the rest of your Node.js process. Only load files you trust.
414
414
 
415
415
  ### JSON5 rule files are data only
416
416
 
@@ -428,6 +428,10 @@ Request bodies are capped at 1 MB by Fastify's default. Responses are serialised
428
428
 
429
429
  See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for how the codebase is structured, the request lifecycle, rule matching, and response serialisation.
430
430
 
431
+ ## API reference
432
+
433
+ Full API docs are available [here](https://theblixguy.github.io/llm-mock-server/).
434
+
431
435
  ## Licence
432
436
 
433
437
  MIT License
@@ -1,6 +1,6 @@
1
1
  # llm-mock-server architecture
2
2
 
3
- A mock LLM server built on Fastify. Clients send requests in OpenAI, Anthropic, or Responses API format. The server normalises them into a common shape, matches against registered rules, and sends back responses in the right format.
3
+ A mock LLM server built on Fastify. Clients send requests in OpenAI Chat Completions, Anthropic Messages, or OpenAI Responses API format. The server normalises them into a common shape, matches against registered rules, and sends back responses in the right format.
4
4
 
5
5
  ```mermaid
6
6
  flowchart LR
@@ -8,7 +8,7 @@ flowchart LR
8
8
  Server["llm-mock-server<br/>(Fastify)"]
9
9
  Rules["Rule engine"]
10
10
 
11
- Client <-->|"OpenAI / Anthropic / Responses"| Server
11
+ Client <-->|"Chat Completions / Messages / Responses"| Server
12
12
  Server <--> Rules
13
13
  ```
14
14
 
@@ -26,9 +26,9 @@ flowchart LR
26
26
 
27
27
  ## Startup
28
28
 
29
- [`cli.ts`](src/cli.ts) is the CLI entry point. It parses flags with Commander, validates them through [`cli-validators.ts`](src/cli-validators.ts), creates a `MockServer`, loads any rule files, and handles SIGINT/SIGTERM. With `--watch`, it sets up `fs.watch()` on the rules path and reloads on changes.
29
+ [`cli.ts`](src/cli/cli.ts) is the CLI entry point. It parses flags with Commander, validates them through [`validators.ts`](src/cli/validators.ts), creates a `MockServer`, loads any rule files, and handles SIGINT/SIGTERM. With `--watch`, it sets up `fs.watch()` on the rules path and reloads on changes.
30
30
 
31
- [`MockServer`](src/mock-server.ts) is the main class. The constructor creates a Fastify instance and registers a route handler for each format. Consumers interact with it through `when()`, `fallback()`, `load()`, and the lifecycle methods.
31
+ [`MockServer`](src/mock-server.ts) is the main class. The constructor creates a Fastify instance and registers a route handler for each format. Rule authoring (`when()`, `whenTool()`, `whenToolResult()`, `nextError()`) lives in [`RuleBuilder`](src/rule-builder.ts) and is proxied onto `MockServer` via `.bind()`. Consumers interact with it through the rule methods, `fallback()`, `load()`, and the lifecycle methods.
32
32
 
33
33
  [`createMock()`](src/index.ts) is a convenience that creates a server and starts it in one call.
34
34
 
@@ -40,9 +40,9 @@ Three formats are included:
40
40
 
41
41
  | Format | Route | Directory |
42
42
  | ------ | ----- | --------- |
43
- | OpenAI | `POST /v1/chat/completions` | [`formats/openai/`](src/formats/openai/) |
44
- | Anthropic | `POST /v1/messages` | [`formats/anthropic/`](src/formats/anthropic/) |
45
- | OpenAI Responses | `POST /v1/responses` | [`formats/responses/`](src/formats/responses/) |
43
+ | OpenAI Chat Completions | `POST /v1/chat/completions` | [`formats/openai/chat-completions/`](src/formats/openai/chat-completions/) |
44
+ | Anthropic Messages | `POST /v1/messages` | [`formats/anthropic/`](src/formats/anthropic/) |
45
+ | OpenAI Responses | `POST /v1/responses` | [`formats/openai/responses/`](src/formats/openai/responses/) |
46
46
 
47
47
  Each format directory has three files:
48
48
 
@@ -50,7 +50,7 @@ Each format directory has three files:
50
50
  - `serialize.ts` takes a [`ReplyObject`](src/types/reply.ts) and produces SSE chunks or a JSON response
51
51
  - `index.ts` wires parse and serialize together into a `Format` object
52
52
 
53
- Shared helpers live in [`parse-helpers.ts`](src/formats/parse-helpers.ts). Functions like `buildMockRequest()`, `genId()`, `splitText()`, `shouldEmitText()`, and `finishReason()` are used by all three formats. `isStreaming()` is also shared because every format checks `body.stream !== false` the same way.
53
+ Shared helpers are split into request and response files: [`request-helpers.ts`](src/formats/request-helpers.ts) has `buildMockRequest()` and `isStreaming()` (used by parsers), and [`serialize-helpers.ts`](src/formats/serialize-helpers.ts) has `genId()`, `splitText()`, `shouldEmitText()`, and `finishReason()` (used by serialisers).
54
54
 
55
55
  To add a new format (say, Gemini), you would create a new directory with those three files and add it to the `formats` array in `mock-server.ts`. Everything else (rule matching, streaming, logging, history) works automatically.
56
56
 
@@ -91,7 +91,7 @@ Types are split across three files in [`src/types/`](src/types/):
91
91
  - [`reply.ts`](src/types/reply.ts) has `Reply`, `ReplyObject`, `ErrorReply`, `ToolCall`, `Resolver`, and `ReplyOptions`
92
92
  - [`rule.ts`](src/types/rule.ts) has `Match`, `MatchObject`, `PendingRule`, `RuleHandle`, `RuleSummary`, `Handler`, and `Rule`
93
93
 
94
- [`src/types.ts`](src/types.ts) re-exports everything from the barrel so internal files can import from a single place.
94
+ [`src/types.ts`](src/types.ts) re-exports everything as a barrel for the public API. Internal modules import directly from the leaf files.
95
95
 
96
96
  ## Streaming
97
97
 
@@ -116,10 +116,10 @@ Directories are read and processed in sorted order.
116
116
 
117
117
  The threat model is simple: the server runs locally or in CI, loading files written by the developer. There is no multi-tenant isolation or sandboxing.
118
118
 
119
- Handler files (`.ts`, `.js`, `.mjs`) are loaded via `import()` and execute with full process permissions. The trust boundary is the file system path passed to `load()` or `--handler`. If an attacker can write to that path, they already have code execution on the machine. No path restriction is enforced because legitimate setups often load rules from outside the project directory.
119
+ Handler files (`.ts`, `.js`, `.mjs`) are loaded via `import()` and execute with full process permissions. The trust boundary is the file system path passed to `load()` or `--rules`. If an attacker can write to that path, they already have code execution on the machine. No path restriction is enforced because legitimate setups often load rules from outside the project directory.
120
120
 
121
121
  JSON5 files go through Zod validation and never execute code. The only dynamic construction is `new RegExp()` for regex patterns in rule files, which could hang on pathological backtracking patterns but poses no injection risk.
122
122
 
123
123
  Fastify caps request bodies at 1 MB by default. The server binds to `127.0.0.1` unless explicitly configured otherwise. Responses are serialised through JSON, so reply text cannot break out of SSE framing.
124
124
 
125
- CLI inputs (port, host, latency, log level) are validated through [`cli-validators.ts`](src/cli-validators.ts) before use.
125
+ CLI inputs (port, host, latency, log level) are validated through [`validators.ts`](src/cli/validators.ts) before use.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-mock-server",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "A standalone mock LLM server for deterministic testing: OpenAI, Anthropic, and Responses API formats",
5
5
  "type": "module",
6
6
  "engines": {
@@ -15,8 +15,11 @@
15
15
  },
16
16
  "./package.json": "./package.json"
17
17
  },
18
+ "imports": {
19
+ "#/*": "./src/*"
20
+ },
18
21
  "bin": {
19
- "llm-mock-server": "./dist/cli.js"
22
+ "llm-mock-server": "./dist/cli/cli.js"
20
23
  },
21
24
  "scripts": {
22
25
  "build": "tsc",
@@ -26,8 +29,10 @@
26
29
  "test": "vitest run",
27
30
  "test:watch": "vitest",
28
31
  "check": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && npm run fmt:check && npm run lint && npm test",
29
- "dev": "tsx src/cli.ts",
30
- "start": "node dist/cli.js"
32
+ "docs": "typedoc",
33
+ "docs:check": "typedoc --emit none",
34
+ "dev": "tsx src/cli/cli.ts",
35
+ "start": "node dist/cli/cli.js"
31
36
  },
32
37
  "keywords": [
33
38
  "llm",
@@ -41,19 +46,21 @@
41
46
  "license": "MIT",
42
47
  "dependencies": {
43
48
  "commander": "14.0.3",
44
- "fastify": "5.8.2",
49
+ "fastify": "5.8.4",
45
50
  "json5": "2.2.3",
46
- "llm-schemas": "1.0.1",
51
+ "llm-schemas": "1.0.2",
47
52
  "picocolors": "1.1.1",
48
53
  "zod": "4.3.6"
49
54
  },
50
55
  "devDependencies": {
51
56
  "@types/node": "25.5.0",
52
- "@vitest/coverage-v8": "4.1.0",
53
- "oxfmt": "0.40.0",
54
- "oxlint": "1.55.0",
57
+ "@vitest/coverage-v8": "4.1.2",
58
+ "oxfmt": "0.42.0",
59
+ "oxlint": "1.57.0",
55
60
  "tsx": "4.21.0",
56
- "typescript": "5.9.3",
57
- "vitest": "4.1.0"
61
+ "typedoc": "0.28.18",
62
+ "typedoc-theme-oxide": "0.2.5",
63
+ "typescript": "6.0.2",
64
+ "vitest": "4.1.2"
58
65
  }
59
66
  }
package/scorecard.png CHANGED
Binary file
@@ -4,18 +4,18 @@ import { watch } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
5
  import { Command } from "commander";
6
6
  import pc from "picocolors";
7
- import { MockServer } from "./mock-server.js";
8
- import { Logger } from "./logger.js";
7
+ import { MockServer } from "#/mock-server.js";
8
+ import { Logger } from "#/logger.js";
9
9
  import {
10
10
  parsePort,
11
11
  parseHost,
12
12
  parseLogLevel,
13
13
  parseChunkSize,
14
14
  parseLatency,
15
- } from "./cli-validators.js";
15
+ } from "./validators.js";
16
16
 
17
17
  const require = createRequire(import.meta.url);
18
- const { version } = require("../package.json") as { version: string };
18
+ const { version } = require("../../package.json") as { version: string };
19
19
 
20
20
  const WATCH_DEBOUNCE_MS = 100;
21
21
 
@@ -23,7 +23,6 @@ interface StartOptions {
23
23
  port: string;
24
24
  host: string;
25
25
  rules?: string;
26
- handler?: string;
27
26
  latency: string;
28
27
  chunkSize: string;
29
28
  fallback?: string;
@@ -54,9 +53,6 @@ async function start(options: StartOptions): Promise<void> {
54
53
  if (options.rules) {
55
54
  await server.load(options.rules);
56
55
  }
57
- if (options.handler) {
58
- await server.load(options.handler);
59
- }
60
56
 
61
57
  const quiet = logLevel === "none";
62
58
 
@@ -78,7 +74,7 @@ async function start(options: StartOptions): Promise<void> {
78
74
  );
79
75
  }
80
76
  console.log(
81
- ` ${pc.dim("Endpoints")} ${pc.green("/v1/chat/completions")}, ${pc.green("/v1/messages")}, ${pc.green("/v1/responses")}`,
77
+ ` ${pc.dim("Endpoints")} ${server.routes.map((r) => pc.green(r)).join(", ")}`,
82
78
  );
83
79
  console.log();
84
80
  }
@@ -131,8 +127,7 @@ program
131
127
  .description("Start the mock server")
132
128
  .option("-p, --port <number>", "port to listen on", "5555")
133
129
  .option("-H, --host <address>", "host to bind to", "127.0.0.1")
134
- .option("-r, --rules <path>", "path to .json5 rules file or directory")
135
- .option("--handler <path>", "path to .ts handler file")
130
+ .option("-r, --rules <path>", "path to rules file or directory")
136
131
  .option("-l, --latency <ms>", "latency between SSE chunks (ms)", "0")
137
132
  .option("-c, --chunk-size <chars>", "characters per SSE chunk", "0")
138
133
  .option("-f, --fallback <text>", "fallback reply text")
@@ -1,17 +1,18 @@
1
1
  import { isIP } from "node:net";
2
2
  import { lookup } from "node:dns/promises";
3
- import { LEVEL_PRIORITY, type LogLevel } from "./logger.js";
3
+ import { LEVEL_PRIORITY, type LogLevel } from "#/logger.js";
4
4
 
5
- const VALID_LOG_LEVELS = Object.keys(LEVEL_PRIORITY) as LogLevel[];
5
+ const VALID_LOG_LEVELS: string[] = Object.keys(LEVEL_PRIORITY);
6
6
 
7
- function isLogLevel(value: string): value is LogLevel {
8
- return value in LEVEL_PRIORITY;
7
+ function parseStrictInt(value: string): number {
8
+ if (!/^\d+$/.test(value)) return NaN;
9
+ return Number(value);
9
10
  }
10
11
 
11
12
  const MAX_PORT = 65535;
12
13
 
13
14
  export function parsePort(value: string): number {
14
- const port = parseInt(value, 10);
15
+ const port = parseStrictInt(value);
15
16
  if (isNaN(port) || port < 1 || port > MAX_PORT) {
16
17
  throw new Error(`Invalid port "${value}". Must be 1-${String(MAX_PORT)}.`);
17
18
  }
@@ -19,12 +20,12 @@ export function parsePort(value: string): number {
19
20
  }
20
21
 
21
22
  export function parseLogLevel(value: string): LogLevel {
22
- if (!isLogLevel(value)) {
23
+ if (!VALID_LOG_LEVELS.includes(value)) {
23
24
  throw new Error(
24
25
  `Invalid log level "${value}". Valid: ${VALID_LOG_LEVELS.join(", ")}`,
25
26
  );
26
27
  }
27
- return value;
28
+ return value as LogLevel;
28
29
  }
29
30
 
30
31
  export async function parseHost(value: string): Promise<string> {
@@ -47,7 +48,7 @@ export async function parseHost(value: string): Promise<string> {
47
48
  }
48
49
 
49
50
  export function parseChunkSize(value: string): number {
50
- const size = parseInt(value, 10);
51
+ const size = parseStrictInt(value);
51
52
  if (isNaN(size) || size < 0) {
52
53
  throw new Error(
53
54
  `Invalid chunk size "${value}". Must be a non-negative integer.`,
@@ -57,7 +58,7 @@ export function parseChunkSize(value: string): number {
57
58
  }
58
59
 
59
60
  export function parseLatency(value: string): number {
60
- const ms = parseInt(value, 10);
61
+ const ms = parseStrictInt(value);
61
62
  if (isNaN(ms) || ms < 0) {
62
63
  throw new Error(
63
64
  `Invalid latency "${value}". Must be a non-negative integer (ms).`,
@@ -1,5 +1,5 @@
1
- import type { Format } from "../types.js";
2
- import { isStreaming } from "../request-helpers.js";
1
+ import type { Format } from "#/formats/types.js";
2
+ import { isStreaming } from "#/formats/request-helpers.js";
3
3
  import { parseRequest } from "./parse.js";
4
4
  import { serialize, serializeComplete, serializeError } from "./serialize.js";
5
5
 
@@ -1,5 +1,8 @@
1
- import type { MockRequest, Message, ToolDef } from "../../types.js";
2
- import { buildMockRequest, type RequestMeta } from "../request-helpers.js";
1
+ import type { MockRequest, Message, ToolDef } from "#/types/request.js";
2
+ import {
3
+ buildMockRequest,
4
+ type RequestMeta,
5
+ } from "#/formats/request-helpers.js";
3
6
  import { AnthropicRequestSchema, type AnthropicRequest } from "./schema.js";
4
7
 
5
8
  function extractSystem(system: AnthropicRequest["system"]): Message[] {