llm-mock-server 1.0.6 → 1.0.8

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 (251) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/cli.d.ts +3 -0
  3. package/dist/cli/cli.d.ts.map +1 -0
  4. package/dist/cli/cli.js +103 -0
  5. package/dist/cli/cli.js.map +1 -0
  6. package/dist/cli/validators.d.ts +7 -0
  7. package/dist/cli/validators.d.ts.map +1 -0
  8. package/dist/cli/validators.js +53 -0
  9. package/dist/cli/validators.js.map +1 -0
  10. package/dist/formats/anthropic/index.d.ts +1 -1
  11. package/dist/formats/anthropic/index.d.ts.map +1 -1
  12. package/dist/formats/anthropic/index.js +1 -1
  13. package/dist/formats/anthropic/index.js.map +1 -1
  14. package/dist/formats/anthropic/parse.d.ts +2 -2
  15. package/dist/formats/anthropic/parse.d.ts.map +1 -1
  16. package/dist/formats/anthropic/parse.js +4 -2
  17. package/dist/formats/anthropic/parse.js.map +1 -1
  18. package/dist/formats/anthropic/schema.d.ts +1 -1
  19. package/dist/formats/anthropic/schema.d.ts.map +1 -1
  20. package/dist/formats/anthropic/schema.js +9 -4
  21. package/dist/formats/anthropic/schema.js.map +1 -1
  22. package/dist/formats/anthropic/serialize.d.ts +2 -2
  23. package/dist/formats/anthropic/serialize.d.ts.map +1 -1
  24. package/dist/formats/anthropic/serialize.js +76 -19
  25. package/dist/formats/anthropic/serialize.js.map +1 -1
  26. package/dist/formats/openai/chat-completions/index.d.ts +3 -0
  27. package/dist/formats/openai/chat-completions/index.d.ts.map +1 -0
  28. package/dist/formats/openai/chat-completions/index.js +13 -0
  29. package/dist/formats/openai/chat-completions/index.js.map +1 -0
  30. package/dist/formats/openai/chat-completions/parse.d.ts +4 -0
  31. package/dist/formats/openai/chat-completions/parse.d.ts.map +1 -0
  32. package/dist/formats/openai/chat-completions/parse.js +33 -0
  33. package/dist/formats/openai/chat-completions/parse.js.map +1 -0
  34. package/dist/formats/openai/chat-completions/schema.d.ts +93 -0
  35. package/dist/formats/openai/chat-completions/schema.d.ts.map +1 -0
  36. package/dist/formats/openai/chat-completions/schema.js +74 -0
  37. package/dist/formats/openai/chat-completions/schema.js.map +1 -0
  38. package/dist/formats/openai/chat-completions/serialize.d.ts +10 -0
  39. package/dist/formats/openai/chat-completions/serialize.d.ts.map +1 -0
  40. package/dist/formats/openai/chat-completions/serialize.js +99 -0
  41. package/dist/formats/openai/chat-completions/serialize.js.map +1 -0
  42. package/dist/formats/openai/responses/index.d.ts +3 -0
  43. package/dist/formats/openai/responses/index.d.ts.map +1 -0
  44. package/dist/formats/openai/responses/index.js +13 -0
  45. package/dist/formats/openai/responses/index.js.map +1 -0
  46. package/dist/formats/openai/responses/parse.d.ts +4 -0
  47. package/dist/formats/openai/responses/parse.d.ts.map +1 -0
  48. package/dist/formats/openai/responses/parse.js +51 -0
  49. package/dist/formats/openai/responses/parse.js.map +1 -0
  50. package/dist/formats/openai/responses/schema.d.ts +103 -0
  51. package/dist/formats/openai/responses/schema.d.ts.map +1 -0
  52. package/dist/formats/openai/responses/schema.js +71 -0
  53. package/dist/formats/openai/responses/schema.js.map +1 -0
  54. package/dist/formats/openai/responses/serialize.d.ts +10 -0
  55. package/dist/formats/openai/responses/serialize.d.ts.map +1 -0
  56. package/dist/formats/openai/responses/serialize.js +273 -0
  57. package/dist/formats/openai/responses/serialize.js.map +1 -0
  58. package/dist/formats/request-helpers.d.ts +1 -1
  59. package/dist/formats/request-helpers.d.ts.map +1 -1
  60. package/dist/formats/request-helpers.js.map +1 -1
  61. package/dist/formats/serialize-helpers.d.ts +1 -1
  62. package/dist/formats/serialize-helpers.d.ts.map +1 -1
  63. package/dist/formats/serialize-helpers.js +6 -3
  64. package/dist/formats/serialize-helpers.js.map +1 -1
  65. package/dist/formats/types.d.ts +2 -1
  66. package/dist/formats/types.d.ts.map +1 -1
  67. package/dist/history.d.ts +6 -2
  68. package/dist/history.d.ts.map +1 -1
  69. package/dist/history.js +2 -0
  70. package/dist/history.js.map +1 -1
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/loader.d.ts +1 -1
  74. package/dist/loader.d.ts.map +1 -1
  75. package/dist/loader.js +26 -9
  76. package/dist/loader.js.map +1 -1
  77. package/dist/logger.d.ts.map +1 -1
  78. package/dist/logger.js +12 -4
  79. package/dist/logger.js.map +1 -1
  80. package/dist/mock-server.d.ts +44 -48
  81. package/dist/mock-server.d.ts.map +1 -1
  82. package/dist/mock-server.js +37 -85
  83. package/dist/mock-server.js.map +1 -1
  84. package/dist/route-handler.d.ts +1 -1
  85. package/dist/route-handler.d.ts.map +1 -1
  86. package/dist/route-handler.js +19 -7
  87. package/dist/route-handler.js.map +1 -1
  88. package/dist/rule-builder.d.ts +21 -0
  89. package/dist/rule-builder.d.ts.map +1 -0
  90. package/dist/rule-builder.js +58 -0
  91. package/dist/rule-builder.js.map +1 -0
  92. package/dist/rule-engine.d.ts +3 -1
  93. package/dist/rule-engine.d.ts.map +1 -1
  94. package/dist/rule-engine.js +7 -2
  95. package/dist/rule-engine.js.map +1 -1
  96. package/dist/sse-writer.d.ts +1 -1
  97. package/dist/sse-writer.d.ts.map +1 -1
  98. package/dist/types/reply.d.ts +51 -8
  99. package/dist/types/reply.d.ts.map +1 -1
  100. package/dist/types/request.d.ts +21 -6
  101. package/dist/types/request.d.ts.map +1 -1
  102. package/dist/types/rule.d.ts +65 -7
  103. package/dist/types/rule.d.ts.map +1 -1
  104. package/dist/types.d.ts +3 -3
  105. package/dist/types.d.ts.map +1 -1
  106. package/package.json +15 -9
  107. package/.claude/skills/desloppify/SKILL.md +0 -308
  108. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000801.json +0 -242
  109. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000905.json +0 -248
  110. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000917.json +0 -248
  111. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000950.json +0 -311
  112. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/claude_launch_prompt.md +0 -17
  113. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.json +0 -255
  114. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.template.json +0 -22
  115. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/reviewer_instructions.md +0 -20
  116. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/session.json +0 -20
  117. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/canonical_import_20260315_050000.json +0 -286
  118. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/canonical_import_20260315_050028.json +0 -303
  119. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/claude_launch_prompt.md +0 -17
  120. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/review_result.json +0 -297
  121. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/review_result.template.json +0 -22
  122. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/reviewer_instructions.md +0 -20
  123. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/session.json +0 -20
  124. package/.desloppify/query.json +0 -1312
  125. package/.desloppify/review_packet_blind.json +0 -1249
  126. package/.desloppify/review_packets/holistic_packet_20260315_000339.json +0 -1471
  127. package/.desloppify/review_packets/holistic_packet_20260315_045546.json +0 -1480
  128. package/.desloppify/review_packets/holistic_packet_20260315_185401.json +0 -1407
  129. package/.desloppify/review_packets/holistic_packet_20260315_185613.json +0 -1407
  130. package/.desloppify/state-typescript.json +0 -8438
  131. package/.desloppify/state-typescript.json.bak +0 -8432
  132. package/.desloppify/subagents/runs/20260315_185401/logs/batch-1.log +0 -384
  133. package/.desloppify/subagents/runs/20260315_185401/logs/batch-10.log +0 -484
  134. package/.desloppify/subagents/runs/20260315_185401/logs/batch-2.log +0 -408
  135. package/.desloppify/subagents/runs/20260315_185401/logs/batch-3.log +0 -416
  136. package/.desloppify/subagents/runs/20260315_185401/logs/batch-4.log +0 -360
  137. package/.desloppify/subagents/runs/20260315_185401/logs/batch-5.log +0 -360
  138. package/.desloppify/subagents/runs/20260315_185401/logs/batch-6.log +0 -364
  139. package/.desloppify/subagents/runs/20260315_185401/logs/batch-7.log +0 -428
  140. package/.desloppify/subagents/runs/20260315_185401/logs/batch-8.log +0 -388
  141. package/.desloppify/subagents/runs/20260315_185401/logs/batch-9.log +0 -500
  142. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-1.md +0 -83
  143. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-10.md +0 -108
  144. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-2.md +0 -89
  145. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-3.md +0 -91
  146. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-4.md +0 -77
  147. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-5.md +0 -77
  148. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-6.md +0 -78
  149. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-7.md +0 -94
  150. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-8.md +0 -84
  151. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-9.md +0 -112
  152. package/.desloppify/subagents/runs/20260315_185401/results/batch-1.raw.txt +0 -0
  153. package/.desloppify/subagents/runs/20260315_185401/results/batch-10.raw.txt +0 -0
  154. package/.desloppify/subagents/runs/20260315_185401/results/batch-2.raw.txt +0 -0
  155. package/.desloppify/subagents/runs/20260315_185401/results/batch-3.raw.txt +0 -0
  156. package/.desloppify/subagents/runs/20260315_185401/results/batch-4.raw.txt +0 -0
  157. package/.desloppify/subagents/runs/20260315_185401/results/batch-5.raw.txt +0 -0
  158. package/.desloppify/subagents/runs/20260315_185401/results/batch-6.raw.txt +0 -0
  159. package/.desloppify/subagents/runs/20260315_185401/results/batch-7.raw.txt +0 -0
  160. package/.desloppify/subagents/runs/20260315_185401/results/batch-8.raw.txt +0 -0
  161. package/.desloppify/subagents/runs/20260315_185401/results/batch-9.raw.txt +0 -0
  162. package/.desloppify/subagents/runs/20260315_185401/run.log +0 -36
  163. package/.desloppify/subagents/runs/20260315_185401/run_summary.json +0 -156
  164. package/.desloppify/subagents/runs/20260315_185613/holistic_findings_merged.json +0 -741
  165. package/.desloppify/subagents/runs/20260315_185613/logs/batch-1.log +0 -579
  166. package/.desloppify/subagents/runs/20260315_185613/logs/batch-10.log +0 -1537
  167. package/.desloppify/subagents/runs/20260315_185613/logs/batch-2.log +0 -829
  168. package/.desloppify/subagents/runs/20260315_185613/logs/batch-3.log +0 -927
  169. package/.desloppify/subagents/runs/20260315_185613/logs/batch-4.log +0 -429
  170. package/.desloppify/subagents/runs/20260315_185613/logs/batch-5.log +0 -276
  171. package/.desloppify/subagents/runs/20260315_185613/logs/batch-6.log +0 -450
  172. package/.desloppify/subagents/runs/20260315_185613/logs/batch-7.log +0 -730
  173. package/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log +0 -698
  174. package/.desloppify/subagents/runs/20260315_185613/logs/batch-9.log +0 -938
  175. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-1.md +0 -83
  176. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-10.md +0 -108
  177. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-2.md +0 -89
  178. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-3.md +0 -91
  179. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-4.md +0 -77
  180. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-5.md +0 -77
  181. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-6.md +0 -78
  182. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-7.md +0 -94
  183. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-8.md +0 -84
  184. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-9.md +0 -112
  185. package/.desloppify/subagents/runs/20260315_185613/results/batch-1.raw.txt +0 -78
  186. package/.desloppify/subagents/runs/20260315_185613/results/batch-10.raw.txt +0 -242
  187. package/.desloppify/subagents/runs/20260315_185613/results/batch-2.raw.txt +0 -102
  188. package/.desloppify/subagents/runs/20260315_185613/results/batch-3.raw.txt +0 -94
  189. package/.desloppify/subagents/runs/20260315_185613/results/batch-4.raw.txt +0 -86
  190. package/.desloppify/subagents/runs/20260315_185613/results/batch-5.raw.txt +0 -1
  191. package/.desloppify/subagents/runs/20260315_185613/results/batch-6.raw.txt +0 -87
  192. package/.desloppify/subagents/runs/20260315_185613/results/batch-7.raw.txt +0 -1
  193. package/.desloppify/subagents/runs/20260315_185613/results/batch-8.raw.txt +0 -107
  194. package/.desloppify/subagents/runs/20260315_185613/results/batch-9.raw.txt +0 -67
  195. package/.desloppify/subagents/runs/20260315_185613/run.log +0 -96
  196. package/.desloppify/subagents/runs/20260315_185613/run_summary.json +0 -156
  197. package/.editorconfig +0 -12
  198. package/.github/dependabot.yml +0 -11
  199. package/.github/workflows/docs.yml +0 -46
  200. package/.github/workflows/test.yml +0 -40
  201. package/.markdownlint.jsonc +0 -11
  202. package/.node-version +0 -1
  203. package/.oxfmtrc.json +0 -9
  204. package/.oxlintrc.json +0 -35
  205. package/docs/ARCHITECTURE.md +0 -125
  206. package/scorecard.png +0 -0
  207. package/src/cli/cli.ts +0 -141
  208. package/src/cli/validators.ts +0 -68
  209. package/src/formats/anthropic/index.ts +0 -14
  210. package/src/formats/anthropic/parse.ts +0 -70
  211. package/src/formats/anthropic/schema.ts +0 -74
  212. package/src/formats/anthropic/serialize.ts +0 -179
  213. package/src/formats/openai/chat-completions/index.ts +0 -14
  214. package/src/formats/openai/chat-completions/parse.ts +0 -47
  215. package/src/formats/openai/chat-completions/schema.ts +0 -92
  216. package/src/formats/openai/chat-completions/serialize.ts +0 -146
  217. package/src/formats/openai/responses/index.ts +0 -14
  218. package/src/formats/openai/responses/parse.ts +0 -73
  219. package/src/formats/openai/responses/schema.ts +0 -86
  220. package/src/formats/openai/responses/serialize.ts +0 -328
  221. package/src/formats/request-helpers.ts +0 -56
  222. package/src/formats/serialize-helpers.ts +0 -43
  223. package/src/formats/types.ts +0 -26
  224. package/src/history.ts +0 -70
  225. package/src/index.ts +0 -46
  226. package/src/loader.ts +0 -246
  227. package/src/logger.ts +0 -70
  228. package/src/mock-server.ts +0 -203
  229. package/src/route-handler.ts +0 -144
  230. package/src/rule-builder.ts +0 -73
  231. package/src/rule-engine.ts +0 -165
  232. package/src/sse-writer.ts +0 -35
  233. package/src/types/reply.ts +0 -92
  234. package/src/types/request.ts +0 -56
  235. package/src/types/rule.ts +0 -125
  236. package/src/types.ts +0 -24
  237. package/test/cli-validators.test.ts +0 -151
  238. package/test/formats/anthropic.test.ts +0 -336
  239. package/test/formats/openai.test.ts +0 -316
  240. package/test/formats/parse-helpers.test.ts +0 -315
  241. package/test/formats/responses.test.ts +0 -380
  242. package/test/helpers/make-req.ts +0 -18
  243. package/test/history.test.ts +0 -361
  244. package/test/loader.test.ts +0 -333
  245. package/test/logger.test.ts +0 -344
  246. package/test/mock-server.test.ts +0 -619
  247. package/test/rule-engine.test.ts +0 -229
  248. package/tsconfig.json +0 -24
  249. package/tsconfig.test.json +0 -11
  250. package/typedoc.json +0 -9
  251. package/vitest.config.ts +0 -18
package/src/cli/cli.ts DELETED
@@ -1,141 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { watch } from "node:fs";
4
- import { createRequire } from "node:module";
5
- import { Command } from "commander";
6
- import pc from "picocolors";
7
- import { MockServer } from "#/mock-server.js";
8
- import { Logger } from "#/logger.js";
9
- import {
10
- parsePort,
11
- parseHost,
12
- parseLogLevel,
13
- parseChunkSize,
14
- parseLatency,
15
- } from "./validators.js";
16
-
17
- const require = createRequire(import.meta.url);
18
- const { version } = require("../../package.json") as { version: string };
19
-
20
- const WATCH_DEBOUNCE_MS = 100;
21
-
22
- interface StartOptions {
23
- port: string;
24
- host: string;
25
- rules?: string;
26
- latency: string;
27
- chunkSize: string;
28
- fallback?: string;
29
- logLevel: string;
30
- watch?: boolean;
31
- }
32
-
33
- async function start(options: StartOptions): Promise<void> {
34
- const logLevel = parseLogLevel(options.logLevel);
35
- const logger = new Logger(logLevel);
36
- const port = parsePort(options.port);
37
- const host = await parseHost(options.host);
38
- const latency = parseLatency(options.latency);
39
- const chunkSize = parseChunkSize(options.chunkSize);
40
-
41
- const server = new MockServer({
42
- port,
43
- host,
44
- logLevel,
45
- ...(latency > 0 && { defaultLatency: latency }),
46
- ...(chunkSize > 0 && { defaultChunkSize: chunkSize }),
47
- });
48
-
49
- if (options.fallback) {
50
- server.fallback(options.fallback);
51
- }
52
-
53
- if (options.rules) {
54
- await server.load(options.rules);
55
- }
56
-
57
- const quiet = logLevel === "none";
58
-
59
- await server.start(port);
60
-
61
- if (!quiet) {
62
- console.log();
63
- console.log(
64
- ` ${pc.bold(pc.cyan("llm-mock-server"))} ${pc.dim(`v${version}`)}`,
65
- );
66
- console.log();
67
- console.log(` ${pc.dim("Port")} ${pc.bold(String(port))}`);
68
- console.log(
69
- ` ${pc.dim("Rules")} ${pc.bold(String(server.ruleCount))} loaded`,
70
- );
71
- if (latency > 0) {
72
- console.log(
73
- ` ${pc.dim("Latency")} ${pc.bold(`${String(latency)}ms`)} per chunk`,
74
- );
75
- }
76
- console.log(
77
- ` ${pc.dim("Endpoints")} ${server.routes.map((r) => pc.green(r)).join(", ")}`,
78
- );
79
- console.log();
80
- }
81
-
82
- if (options.watch && options.rules) {
83
- const rulesPath = options.rules;
84
- let timer: ReturnType<typeof setTimeout> | undefined;
85
- watch(rulesPath, { recursive: true }, () => {
86
- clearTimeout(timer);
87
- timer = setTimeout(async () => {
88
- try {
89
- server.reset();
90
- await server.load(rulesPath);
91
- if (options.fallback) server.fallback(options.fallback);
92
- logger.info(`Reloaded rules from ${rulesPath}`);
93
- } catch (err) {
94
- logger.error("Failed to reload rules", err);
95
- }
96
- }, WATCH_DEBOUNCE_MS);
97
- });
98
- logger.info(`Watching ${rulesPath} for changes`);
99
- }
100
-
101
- let shuttingDown = false;
102
- const shutdown = async (signal: string) => {
103
- if (shuttingDown) return;
104
- shuttingDown = true;
105
-
106
- logger.info(`Got ${signal}, shutting down...`);
107
- await server.stop();
108
- logger.info("Clean shutdown complete");
109
- process.exit(0);
110
- };
111
-
112
- process.on("SIGINT", () => {
113
- shutdown("SIGINT").catch(() => process.exit(1));
114
- });
115
- process.on("SIGTERM", () => {
116
- shutdown("SIGTERM").catch(() => process.exit(1));
117
- });
118
- }
119
-
120
- const program = new Command()
121
- .name("llm-mock-server")
122
- .description("Mock LLM server for deterministic testing")
123
- .version(version);
124
-
125
- program
126
- .command("start", { isDefault: true })
127
- .description("Start the mock server")
128
- .option("-p, --port <number>", "port to listen on", "5555")
129
- .option("-H, --host <address>", "host to bind to", "127.0.0.1")
130
- .option("-r, --rules <path>", "path to rules file or directory")
131
- .option("-l, --latency <ms>", "latency between SSE chunks (ms)", "0")
132
- .option("-c, --chunk-size <chars>", "characters per SSE chunk", "0")
133
- .option("-f, --fallback <text>", "fallback reply text")
134
- .option("-w, --watch", "watch rules path and reload on changes")
135
- .option("--log-level <level>", "log verbosity", "info")
136
- .action((options: StartOptions) => start(options));
137
-
138
- program.parseAsync().catch((err: unknown) => {
139
- console.error("Fatal error:", err);
140
- process.exit(1);
141
- });
@@ -1,68 +0,0 @@
1
- import { isIP } from "node:net";
2
- import { lookup } from "node:dns/promises";
3
- import { LEVEL_PRIORITY, type LogLevel } from "#/logger.js";
4
-
5
- const VALID_LOG_LEVELS: string[] = Object.keys(LEVEL_PRIORITY);
6
-
7
- function parseStrictInt(value: string): number {
8
- if (!/^\d+$/.test(value)) return NaN;
9
- return Number(value);
10
- }
11
-
12
- const MAX_PORT = 65535;
13
-
14
- export function parsePort(value: string): number {
15
- const port = parseStrictInt(value);
16
- if (isNaN(port) || port < 1 || port > MAX_PORT) {
17
- throw new Error(`Invalid port "${value}". Must be 1-${String(MAX_PORT)}.`);
18
- }
19
- return port;
20
- }
21
-
22
- export function parseLogLevel(value: string): LogLevel {
23
- if (!VALID_LOG_LEVELS.includes(value)) {
24
- throw new Error(
25
- `Invalid log level "${value}". Valid: ${VALID_LOG_LEVELS.join(", ")}`,
26
- );
27
- }
28
- return value as LogLevel;
29
- }
30
-
31
- export async function parseHost(value: string): Promise<string> {
32
- if (!value) {
33
- throw new Error(
34
- `Invalid host "${value}". Must be a resolvable hostname or IP address.`,
35
- );
36
- }
37
- if (value === "localhost" || isIP(value) !== 0) {
38
- return value;
39
- }
40
- try {
41
- await lookup(value);
42
- return value;
43
- } catch {
44
- throw new Error(
45
- `Invalid host "${value}". Must be a resolvable hostname or IP address.`,
46
- );
47
- }
48
- }
49
-
50
- export function parseChunkSize(value: string): number {
51
- const size = parseStrictInt(value);
52
- if (isNaN(size) || size < 0) {
53
- throw new Error(
54
- `Invalid chunk size "${value}". Must be a non-negative integer.`,
55
- );
56
- }
57
- return size;
58
- }
59
-
60
- export function parseLatency(value: string): number {
61
- const ms = parseStrictInt(value);
62
- if (isNaN(ms) || ms < 0) {
63
- throw new Error(
64
- `Invalid latency "${value}". Must be a non-negative integer (ms).`,
65
- );
66
- }
67
- return ms;
68
- }
@@ -1,14 +0,0 @@
1
- import type { Format } from "#/formats/types.js";
2
- import { isStreaming } from "#/formats/request-helpers.js";
3
- import { parseRequest } from "./parse.js";
4
- import { serialize, serializeComplete, serializeError } from "./serialize.js";
5
-
6
- export const anthropicFormat: Format = {
7
- name: "anthropic",
8
- route: "/v1/messages",
9
- parseRequest,
10
- isStreaming,
11
- serialize,
12
- serializeComplete,
13
- serializeError,
14
- };
@@ -1,70 +0,0 @@
1
- import type { MockRequest, Message, ToolDef } from "#/types/request.js";
2
- import {
3
- buildMockRequest,
4
- type RequestMeta,
5
- } from "#/formats/request-helpers.js";
6
- import { AnthropicRequestSchema, type AnthropicRequest } from "./schema.js";
7
-
8
- function extractSystem(system: AnthropicRequest["system"]): Message[] {
9
- if (system == null) return [];
10
- if (typeof system === "string")
11
- return system ? [{ role: "system", content: system }] : [];
12
- const text = system.map((b) => b.text).join("\n");
13
- return text ? [{ role: "system", content: text }] : [];
14
- }
15
-
16
- function extractContent(
17
- content: AnthropicRequest["messages"][number]["content"],
18
- ): {
19
- content: string;
20
- toolCallId?: string | undefined;
21
- } {
22
- if (typeof content === "string") return { content };
23
- const text = content
24
- .filter((b): b is { type: "text"; text: string } => b.type === "text")
25
- .map((b) => b.text)
26
- .join("\n");
27
- const toolResult = content.find(
28
- (b): b is { type: "tool_result"; tool_use_id: string } =>
29
- b.type === "tool_result",
30
- );
31
- const toolCallId = toolResult?.tool_use_id;
32
- return { content: text, toolCallId };
33
- }
34
-
35
- function parseMessages(req: AnthropicRequest): readonly Message[] {
36
- const system = extractSystem(req.system);
37
- const conversation = req.messages.map((m) => {
38
- const extracted = extractContent(m.content);
39
- return {
40
- role: m.role,
41
- content: extracted.content,
42
- ...(extracted.toolCallId !== undefined && {
43
- toolCallId: extracted.toolCallId,
44
- }),
45
- };
46
- });
47
- return [...system, ...conversation];
48
- }
49
-
50
- function parseTools(req: AnthropicRequest): readonly ToolDef[] | undefined {
51
- if (!req.tools) return undefined;
52
- return req.tools.map((t) => ({
53
- name: t.name,
54
- description: t.description,
55
- parameters: t.input_schema,
56
- }));
57
- }
58
-
59
- export function parseRequest(body: unknown, meta?: RequestMeta): MockRequest {
60
- const req = AnthropicRequestSchema.parse(body);
61
- return buildMockRequest(
62
- "anthropic",
63
- req,
64
- parseMessages(req),
65
- parseTools(req),
66
- "claude-sonnet-4-6",
67
- body,
68
- meta,
69
- );
70
- }
@@ -1,74 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export {
4
- AnthropicRequestSchema,
5
- type AnthropicRequest,
6
- } from "llm-schemas/anthropic";
7
-
8
- const ResponseContentBlockSchema = z.object({
9
- type: z.string(),
10
- text: z.string().optional(),
11
- thinking: z.string().optional(),
12
- id: z.string().optional(),
13
- name: z.string().optional(),
14
- input: z.unknown().optional(),
15
- });
16
-
17
- export const AnthropicMessageStartSchema = z.object({
18
- message: z.object({
19
- id: z.string(),
20
- type: z.literal("message"),
21
- role: z.literal("assistant"),
22
- content: z.array(z.unknown()),
23
- model: z.string(),
24
- stop_reason: z.string().nullable(),
25
- usage: z.object({ input_tokens: z.number(), output_tokens: z.number() }),
26
- }),
27
- });
28
-
29
- export type AnthropicMessageStart = z.infer<typeof AnthropicMessageStartSchema>;
30
-
31
- export const AnthropicBlockEventSchema = z.object({
32
- index: z.number(),
33
- content_block: ResponseContentBlockSchema.optional(),
34
- delta: z
35
- .object({
36
- type: z.string(),
37
- text: z.string().optional(),
38
- thinking: z.string().optional(),
39
- partial_json: z.string().optional(),
40
- })
41
- .optional(),
42
- });
43
-
44
- export type AnthropicBlockEvent = z.infer<typeof AnthropicBlockEventSchema>;
45
-
46
- export const AnthropicDeltaSchema = z.object({
47
- delta: z.object({
48
- stop_reason: z.string(),
49
- stop_sequence: z.string().nullable(),
50
- }),
51
- usage: z.object({ output_tokens: z.number() }),
52
- });
53
-
54
- export type AnthropicDelta = z.infer<typeof AnthropicDeltaSchema>;
55
-
56
- export const AnthropicCompleteSchema = z.object({
57
- id: z.string(),
58
- type: z.literal("message"),
59
- role: z.literal("assistant"),
60
- model: z.string(),
61
- content: z.array(ResponseContentBlockSchema),
62
- stop_reason: z.string(),
63
- stop_sequence: z.string().nullable(),
64
- usage: z.object({ input_tokens: z.number(), output_tokens: z.number() }),
65
- });
66
-
67
- export type AnthropicComplete = z.infer<typeof AnthropicCompleteSchema>;
68
-
69
- export const AnthropicErrorSchema = z.object({
70
- type: z.literal("error"),
71
- error: z.object({ type: z.string(), message: z.string() }),
72
- });
73
-
74
- export type AnthropicError = z.infer<typeof AnthropicErrorSchema>;
@@ -1,179 +0,0 @@
1
- import type { ReplyObject, ReplyOptions } from "#/types/reply.js";
2
- import type { SSEChunk } from "#/formats/types.js";
3
- import {
4
- splitText,
5
- genId,
6
- toolId,
7
- shouldEmitText,
8
- finishReason,
9
- DEFAULT_USAGE,
10
- } from "#/formats/serialize-helpers.js";
11
-
12
- function buildUsage(usage: { input: number; output: number }) {
13
- return { input_tokens: usage.input, output_tokens: usage.output };
14
- }
15
-
16
- function contentBlock(
17
- index: number,
18
- startBlock: unknown,
19
- deltas: SSEChunk[],
20
- ): SSEChunk[] {
21
- return [
22
- {
23
- event: "content_block_start",
24
- data: JSON.stringify({
25
- type: "content_block_start",
26
- index,
27
- content_block: startBlock,
28
- }),
29
- },
30
- ...deltas,
31
- {
32
- event: "content_block_stop",
33
- data: JSON.stringify({ type: "content_block_stop", index }),
34
- },
35
- ];
36
- }
37
-
38
- function delta(index: number, payload: Record<string, unknown>): SSEChunk {
39
- return {
40
- event: "content_block_delta",
41
- data: JSON.stringify({
42
- type: "content_block_delta",
43
- index,
44
- delta: payload,
45
- }),
46
- };
47
- }
48
-
49
- function reasoningBlock(i: number, reasoning: string): SSEChunk[] {
50
- return contentBlock(i, { type: "thinking", thinking: "" }, [
51
- delta(i, { type: "thinking_delta", thinking: reasoning }),
52
- ]);
53
- }
54
-
55
- function textBlock(i: number, text: string, chunkSize: number): SSEChunk[] {
56
- return contentBlock(
57
- i,
58
- { type: "text", text: "" },
59
- splitText(text, chunkSize).map((piece) =>
60
- delta(i, { type: "text_delta", text: piece }),
61
- ),
62
- );
63
- }
64
-
65
- function toolBlocks(
66
- startIndex: number,
67
- tools: ReplyObject["tools"],
68
- ): SSEChunk[] {
69
- return (tools ?? []).flatMap((tool, i) => {
70
- const idx = startIndex + i;
71
- const id = toolId(tool, "toolu", idx);
72
- return contentBlock(
73
- idx,
74
- { type: "tool_use", id, name: tool.name, input: {} },
75
- [
76
- delta(idx, {
77
- type: "input_json_delta",
78
- partial_json: JSON.stringify(tool.args),
79
- }),
80
- ],
81
- );
82
- });
83
- }
84
-
85
- export function serialize(
86
- reply: ReplyObject,
87
- model: string,
88
- options: ReplyOptions = {},
89
- ): readonly SSEChunk[] {
90
- const id = genId("msg");
91
- const usage = reply.usage ?? DEFAULT_USAGE;
92
- let idx = 0;
93
-
94
- const reasoningChunks = reply.reasoning
95
- ? reasoningBlock(idx++, reply.reasoning)
96
- : [];
97
- const textChunks = shouldEmitText(reply)
98
- ? textBlock(idx++, reply.text ?? "", options.chunkSize ?? 0)
99
- : [];
100
- const toolChunks = toolBlocks(idx, reply.tools);
101
-
102
- return [
103
- {
104
- event: "message_start",
105
- data: JSON.stringify({
106
- type: "message_start",
107
- message: {
108
- id,
109
- type: "message",
110
- role: "assistant",
111
- model,
112
- content: [],
113
- stop_reason: null,
114
- usage: { ...buildUsage(usage), output_tokens: 0 },
115
- },
116
- }),
117
- },
118
- ...reasoningChunks,
119
- ...textChunks,
120
- ...toolChunks,
121
- {
122
- event: "message_delta",
123
- data: JSON.stringify({
124
- type: "message_delta",
125
- delta: {
126
- stop_reason: finishReason(reply, "tool_use", "end_turn"),
127
- stop_sequence: null,
128
- },
129
- usage: { output_tokens: usage.output },
130
- }),
131
- },
132
- { event: "message_stop", data: JSON.stringify({ type: "message_stop" }) },
133
- ];
134
- }
135
-
136
- export function serializeComplete(
137
- reply: ReplyObject,
138
- model: string,
139
- ): Record<string, unknown> {
140
- const id = genId("msg");
141
- const usage = reply.usage ?? DEFAULT_USAGE;
142
-
143
- const content: unknown[] = [
144
- ...(reply.reasoning
145
- ? [{ type: "thinking", thinking: reply.reasoning }]
146
- : []),
147
- ...(shouldEmitText(reply)
148
- ? [{ type: "text", text: reply.text ?? "" }]
149
- : []),
150
- ...(reply.tools ?? []).map((tool, i) => ({
151
- type: "tool_use",
152
- id: toolId(tool, "toolu", i),
153
- name: tool.name,
154
- input: tool.args,
155
- })),
156
- ];
157
-
158
- return {
159
- id,
160
- type: "message",
161
- role: "assistant",
162
- model,
163
- content,
164
- stop_reason: finishReason(reply, "tool_use", "end_turn"),
165
- stop_sequence: null,
166
- usage: buildUsage(usage),
167
- };
168
- }
169
-
170
- export function serializeError(error: {
171
- status: number;
172
- message: string;
173
- type?: string;
174
- }): Record<string, unknown> {
175
- return {
176
- type: "error",
177
- error: { type: error.type ?? "api_error", message: error.message },
178
- };
179
- }
@@ -1,14 +0,0 @@
1
- import type { Format } from "#/formats/types.js";
2
- import { isStreaming } from "#/formats/request-helpers.js";
3
- import { parseRequest } from "./parse.js";
4
- import { serialize, serializeComplete, serializeError } from "./serialize.js";
5
-
6
- export const chatCompletionsFormat: Format = {
7
- name: "openai",
8
- route: "/v1/chat/completions",
9
- parseRequest,
10
- isStreaming,
11
- serialize,
12
- serializeComplete,
13
- serializeError,
14
- };
@@ -1,47 +0,0 @@
1
- import type { MockRequest, Message, ToolDef } from "#/types/request.js";
2
- import {
3
- buildMockRequest,
4
- type RequestMeta,
5
- } from "#/formats/request-helpers.js";
6
- import { OpenAIRequestSchema, type OpenAIRequest } from "./schema.js";
7
-
8
- function extractContent(
9
- content: OpenAIRequest["messages"][number]["content"],
10
- ): string {
11
- if (content == null) return "";
12
- if (typeof content === "string") return content;
13
- return content
14
- .filter((p) => p.type === "text" && p.text !== undefined)
15
- .map((p) => p.text!)
16
- .join("\n");
17
- }
18
-
19
- function parseMessages(req: OpenAIRequest): readonly Message[] {
20
- return req.messages.map((m) => ({
21
- role: m.role === "developer" ? "system" : (m.role ?? "user"),
22
- content: extractContent(m.content),
23
- ...(m.tool_call_id !== undefined && { toolCallId: m.tool_call_id }),
24
- }));
25
- }
26
-
27
- function parseTools(req: OpenAIRequest): readonly ToolDef[] | undefined {
28
- if (!req.tools) return undefined;
29
- return req.tools.map((t) => ({
30
- name: t.function.name,
31
- description: t.function.description,
32
- parameters: t.function.parameters,
33
- }));
34
- }
35
-
36
- export function parseRequest(body: unknown, meta?: RequestMeta): MockRequest {
37
- const req = OpenAIRequestSchema.parse(body);
38
- return buildMockRequest(
39
- "openai",
40
- req,
41
- parseMessages(req),
42
- parseTools(req),
43
- "gpt-5.4",
44
- body,
45
- meta,
46
- );
47
- }
@@ -1,92 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export {
4
- OpenAIRequestSchema,
5
- type OpenAIRequest,
6
- } from "llm-schemas/openai/chat-completions";
7
-
8
- const ToolCallResponseSchema = z.object({
9
- id: z.string(),
10
- type: z.string(),
11
- function: z.object({ name: z.string(), arguments: z.string() }),
12
- });
13
-
14
- const UsageSchema = z.object({
15
- prompt_tokens: z.number(),
16
- completion_tokens: z.number(),
17
- total_tokens: z.number(),
18
- prompt_tokens_details: z
19
- .object({
20
- cached_tokens: z.number().optional(),
21
- audio_tokens: z.number().optional(),
22
- })
23
- .optional(),
24
- completion_tokens_details: z
25
- .object({
26
- reasoning_tokens: z.number().optional(),
27
- audio_tokens: z.number().optional(),
28
- accepted_prediction_tokens: z.number().optional(),
29
- rejected_prediction_tokens: z.number().optional(),
30
- })
31
- .optional(),
32
- });
33
-
34
- export const OpenAIChunkSchema = z.object({
35
- id: z.string(),
36
- object: z.literal("chat.completion.chunk"),
37
- created: z.number(),
38
- model: z.string(),
39
- system_fingerprint: z.string().nullable().optional(),
40
- service_tier: z.string().optional(),
41
- choices: z.array(
42
- z.object({
43
- index: z.number(),
44
- delta: z
45
- .object({
46
- role: z.string(),
47
- content: z.string(),
48
- tool_calls: z.array(ToolCallResponseSchema),
49
- })
50
- .partial(),
51
- logprobs: z.unknown().nullable().optional(),
52
- finish_reason: z.string().nullable(),
53
- }),
54
- ),
55
- usage: UsageSchema.nullable().optional(),
56
- });
57
-
58
- export type OpenAIChunk = z.infer<typeof OpenAIChunkSchema>;
59
-
60
- export const OpenAICompleteSchema = z.object({
61
- id: z.string(),
62
- object: z.literal("chat.completion"),
63
- created: z.number(),
64
- model: z.string(),
65
- system_fingerprint: z.string().nullable().optional(),
66
- service_tier: z.string().optional(),
67
- choices: z.array(
68
- z.object({
69
- index: z.number(),
70
- message: z.object({
71
- role: z.string(),
72
- content: z.string().nullable(),
73
- tool_calls: z.array(ToolCallResponseSchema).optional(),
74
- }),
75
- logprobs: z.unknown().nullable().optional(),
76
- finish_reason: z.string(),
77
- }),
78
- ),
79
- usage: UsageSchema.optional(),
80
- });
81
-
82
- export type OpenAIComplete = z.infer<typeof OpenAICompleteSchema>;
83
-
84
- export const OpenAIErrorSchema = z.object({
85
- error: z.object({
86
- message: z.string(),
87
- type: z.string(),
88
- code: z.string().nullable(),
89
- }),
90
- });
91
-
92
- export type OpenAIError = z.infer<typeof OpenAIErrorSchema>;