llm-mock-server 1.0.6 → 1.0.7

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 (250) hide show
  1. package/dist/cli/cli.d.ts +3 -0
  2. package/dist/cli/cli.d.ts.map +1 -0
  3. package/dist/cli/cli.js +103 -0
  4. package/dist/cli/cli.js.map +1 -0
  5. package/dist/cli/validators.d.ts +7 -0
  6. package/dist/cli/validators.d.ts.map +1 -0
  7. package/dist/cli/validators.js +53 -0
  8. package/dist/cli/validators.js.map +1 -0
  9. package/dist/formats/anthropic/index.d.ts +1 -1
  10. package/dist/formats/anthropic/index.d.ts.map +1 -1
  11. package/dist/formats/anthropic/index.js +1 -1
  12. package/dist/formats/anthropic/index.js.map +1 -1
  13. package/dist/formats/anthropic/parse.d.ts +2 -2
  14. package/dist/formats/anthropic/parse.d.ts.map +1 -1
  15. package/dist/formats/anthropic/parse.js +4 -2
  16. package/dist/formats/anthropic/parse.js.map +1 -1
  17. package/dist/formats/anthropic/schema.d.ts +1 -1
  18. package/dist/formats/anthropic/schema.d.ts.map +1 -1
  19. package/dist/formats/anthropic/schema.js +9 -4
  20. package/dist/formats/anthropic/schema.js.map +1 -1
  21. package/dist/formats/anthropic/serialize.d.ts +2 -2
  22. package/dist/formats/anthropic/serialize.d.ts.map +1 -1
  23. package/dist/formats/anthropic/serialize.js +76 -19
  24. package/dist/formats/anthropic/serialize.js.map +1 -1
  25. package/dist/formats/openai/chat-completions/index.d.ts +3 -0
  26. package/dist/formats/openai/chat-completions/index.d.ts.map +1 -0
  27. package/dist/formats/openai/chat-completions/index.js +13 -0
  28. package/dist/formats/openai/chat-completions/index.js.map +1 -0
  29. package/dist/formats/openai/chat-completions/parse.d.ts +4 -0
  30. package/dist/formats/openai/chat-completions/parse.d.ts.map +1 -0
  31. package/dist/formats/openai/chat-completions/parse.js +33 -0
  32. package/dist/formats/openai/chat-completions/parse.js.map +1 -0
  33. package/dist/formats/openai/chat-completions/schema.d.ts +93 -0
  34. package/dist/formats/openai/chat-completions/schema.d.ts.map +1 -0
  35. package/dist/formats/openai/chat-completions/schema.js +74 -0
  36. package/dist/formats/openai/chat-completions/schema.js.map +1 -0
  37. package/dist/formats/openai/chat-completions/serialize.d.ts +10 -0
  38. package/dist/formats/openai/chat-completions/serialize.d.ts.map +1 -0
  39. package/dist/formats/openai/chat-completions/serialize.js +99 -0
  40. package/dist/formats/openai/chat-completions/serialize.js.map +1 -0
  41. package/dist/formats/openai/responses/index.d.ts +3 -0
  42. package/dist/formats/openai/responses/index.d.ts.map +1 -0
  43. package/dist/formats/openai/responses/index.js +13 -0
  44. package/dist/formats/openai/responses/index.js.map +1 -0
  45. package/dist/formats/openai/responses/parse.d.ts +4 -0
  46. package/dist/formats/openai/responses/parse.d.ts.map +1 -0
  47. package/dist/formats/openai/responses/parse.js +51 -0
  48. package/dist/formats/openai/responses/parse.js.map +1 -0
  49. package/dist/formats/openai/responses/schema.d.ts +103 -0
  50. package/dist/formats/openai/responses/schema.d.ts.map +1 -0
  51. package/dist/formats/openai/responses/schema.js +71 -0
  52. package/dist/formats/openai/responses/schema.js.map +1 -0
  53. package/dist/formats/openai/responses/serialize.d.ts +10 -0
  54. package/dist/formats/openai/responses/serialize.d.ts.map +1 -0
  55. package/dist/formats/openai/responses/serialize.js +273 -0
  56. package/dist/formats/openai/responses/serialize.js.map +1 -0
  57. package/dist/formats/request-helpers.d.ts +1 -1
  58. package/dist/formats/request-helpers.d.ts.map +1 -1
  59. package/dist/formats/request-helpers.js.map +1 -1
  60. package/dist/formats/serialize-helpers.d.ts +1 -1
  61. package/dist/formats/serialize-helpers.d.ts.map +1 -1
  62. package/dist/formats/serialize-helpers.js +6 -3
  63. package/dist/formats/serialize-helpers.js.map +1 -1
  64. package/dist/formats/types.d.ts +2 -1
  65. package/dist/formats/types.d.ts.map +1 -1
  66. package/dist/history.d.ts +6 -2
  67. package/dist/history.d.ts.map +1 -1
  68. package/dist/history.js +2 -0
  69. package/dist/history.js.map +1 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js.map +1 -1
  72. package/dist/loader.d.ts +1 -1
  73. package/dist/loader.d.ts.map +1 -1
  74. package/dist/loader.js +26 -9
  75. package/dist/loader.js.map +1 -1
  76. package/dist/logger.d.ts.map +1 -1
  77. package/dist/logger.js +12 -4
  78. package/dist/logger.js.map +1 -1
  79. package/dist/mock-server.d.ts +44 -48
  80. package/dist/mock-server.d.ts.map +1 -1
  81. package/dist/mock-server.js +37 -85
  82. package/dist/mock-server.js.map +1 -1
  83. package/dist/route-handler.d.ts +1 -1
  84. package/dist/route-handler.d.ts.map +1 -1
  85. package/dist/route-handler.js +19 -7
  86. package/dist/route-handler.js.map +1 -1
  87. package/dist/rule-builder.d.ts +21 -0
  88. package/dist/rule-builder.d.ts.map +1 -0
  89. package/dist/rule-builder.js +58 -0
  90. package/dist/rule-builder.js.map +1 -0
  91. package/dist/rule-engine.d.ts +3 -1
  92. package/dist/rule-engine.d.ts.map +1 -1
  93. package/dist/rule-engine.js +7 -2
  94. package/dist/rule-engine.js.map +1 -1
  95. package/dist/sse-writer.d.ts +1 -1
  96. package/dist/sse-writer.d.ts.map +1 -1
  97. package/dist/types/reply.d.ts +51 -8
  98. package/dist/types/reply.d.ts.map +1 -1
  99. package/dist/types/request.d.ts +21 -6
  100. package/dist/types/request.d.ts.map +1 -1
  101. package/dist/types/rule.d.ts +65 -7
  102. package/dist/types/rule.d.ts.map +1 -1
  103. package/dist/types.d.ts +3 -3
  104. package/dist/types.d.ts.map +1 -1
  105. package/package.json +15 -9
  106. package/.claude/skills/desloppify/SKILL.md +0 -308
  107. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000801.json +0 -242
  108. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000905.json +0 -248
  109. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000917.json +0 -248
  110. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000950.json +0 -311
  111. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/claude_launch_prompt.md +0 -17
  112. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.json +0 -255
  113. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.template.json +0 -22
  114. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/reviewer_instructions.md +0 -20
  115. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/session.json +0 -20
  116. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/canonical_import_20260315_050000.json +0 -286
  117. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/canonical_import_20260315_050028.json +0 -303
  118. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/claude_launch_prompt.md +0 -17
  119. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/review_result.json +0 -297
  120. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/review_result.template.json +0 -22
  121. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/reviewer_instructions.md +0 -20
  122. package/.desloppify/external_review_sessions/ext_20260315_045546_0587ea3b/session.json +0 -20
  123. package/.desloppify/query.json +0 -1312
  124. package/.desloppify/review_packet_blind.json +0 -1249
  125. package/.desloppify/review_packets/holistic_packet_20260315_000339.json +0 -1471
  126. package/.desloppify/review_packets/holistic_packet_20260315_045546.json +0 -1480
  127. package/.desloppify/review_packets/holistic_packet_20260315_185401.json +0 -1407
  128. package/.desloppify/review_packets/holistic_packet_20260315_185613.json +0 -1407
  129. package/.desloppify/state-typescript.json +0 -8438
  130. package/.desloppify/state-typescript.json.bak +0 -8432
  131. package/.desloppify/subagents/runs/20260315_185401/logs/batch-1.log +0 -384
  132. package/.desloppify/subagents/runs/20260315_185401/logs/batch-10.log +0 -484
  133. package/.desloppify/subagents/runs/20260315_185401/logs/batch-2.log +0 -408
  134. package/.desloppify/subagents/runs/20260315_185401/logs/batch-3.log +0 -416
  135. package/.desloppify/subagents/runs/20260315_185401/logs/batch-4.log +0 -360
  136. package/.desloppify/subagents/runs/20260315_185401/logs/batch-5.log +0 -360
  137. package/.desloppify/subagents/runs/20260315_185401/logs/batch-6.log +0 -364
  138. package/.desloppify/subagents/runs/20260315_185401/logs/batch-7.log +0 -428
  139. package/.desloppify/subagents/runs/20260315_185401/logs/batch-8.log +0 -388
  140. package/.desloppify/subagents/runs/20260315_185401/logs/batch-9.log +0 -500
  141. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-1.md +0 -83
  142. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-10.md +0 -108
  143. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-2.md +0 -89
  144. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-3.md +0 -91
  145. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-4.md +0 -77
  146. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-5.md +0 -77
  147. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-6.md +0 -78
  148. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-7.md +0 -94
  149. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-8.md +0 -84
  150. package/.desloppify/subagents/runs/20260315_185401/prompts/batch-9.md +0 -112
  151. package/.desloppify/subagents/runs/20260315_185401/results/batch-1.raw.txt +0 -0
  152. package/.desloppify/subagents/runs/20260315_185401/results/batch-10.raw.txt +0 -0
  153. package/.desloppify/subagents/runs/20260315_185401/results/batch-2.raw.txt +0 -0
  154. package/.desloppify/subagents/runs/20260315_185401/results/batch-3.raw.txt +0 -0
  155. package/.desloppify/subagents/runs/20260315_185401/results/batch-4.raw.txt +0 -0
  156. package/.desloppify/subagents/runs/20260315_185401/results/batch-5.raw.txt +0 -0
  157. package/.desloppify/subagents/runs/20260315_185401/results/batch-6.raw.txt +0 -0
  158. package/.desloppify/subagents/runs/20260315_185401/results/batch-7.raw.txt +0 -0
  159. package/.desloppify/subagents/runs/20260315_185401/results/batch-8.raw.txt +0 -0
  160. package/.desloppify/subagents/runs/20260315_185401/results/batch-9.raw.txt +0 -0
  161. package/.desloppify/subagents/runs/20260315_185401/run.log +0 -36
  162. package/.desloppify/subagents/runs/20260315_185401/run_summary.json +0 -156
  163. package/.desloppify/subagents/runs/20260315_185613/holistic_findings_merged.json +0 -741
  164. package/.desloppify/subagents/runs/20260315_185613/logs/batch-1.log +0 -579
  165. package/.desloppify/subagents/runs/20260315_185613/logs/batch-10.log +0 -1537
  166. package/.desloppify/subagents/runs/20260315_185613/logs/batch-2.log +0 -829
  167. package/.desloppify/subagents/runs/20260315_185613/logs/batch-3.log +0 -927
  168. package/.desloppify/subagents/runs/20260315_185613/logs/batch-4.log +0 -429
  169. package/.desloppify/subagents/runs/20260315_185613/logs/batch-5.log +0 -276
  170. package/.desloppify/subagents/runs/20260315_185613/logs/batch-6.log +0 -450
  171. package/.desloppify/subagents/runs/20260315_185613/logs/batch-7.log +0 -730
  172. package/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log +0 -698
  173. package/.desloppify/subagents/runs/20260315_185613/logs/batch-9.log +0 -938
  174. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-1.md +0 -83
  175. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-10.md +0 -108
  176. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-2.md +0 -89
  177. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-3.md +0 -91
  178. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-4.md +0 -77
  179. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-5.md +0 -77
  180. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-6.md +0 -78
  181. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-7.md +0 -94
  182. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-8.md +0 -84
  183. package/.desloppify/subagents/runs/20260315_185613/prompts/batch-9.md +0 -112
  184. package/.desloppify/subagents/runs/20260315_185613/results/batch-1.raw.txt +0 -78
  185. package/.desloppify/subagents/runs/20260315_185613/results/batch-10.raw.txt +0 -242
  186. package/.desloppify/subagents/runs/20260315_185613/results/batch-2.raw.txt +0 -102
  187. package/.desloppify/subagents/runs/20260315_185613/results/batch-3.raw.txt +0 -94
  188. package/.desloppify/subagents/runs/20260315_185613/results/batch-4.raw.txt +0 -86
  189. package/.desloppify/subagents/runs/20260315_185613/results/batch-5.raw.txt +0 -1
  190. package/.desloppify/subagents/runs/20260315_185613/results/batch-6.raw.txt +0 -87
  191. package/.desloppify/subagents/runs/20260315_185613/results/batch-7.raw.txt +0 -1
  192. package/.desloppify/subagents/runs/20260315_185613/results/batch-8.raw.txt +0 -107
  193. package/.desloppify/subagents/runs/20260315_185613/results/batch-9.raw.txt +0 -67
  194. package/.desloppify/subagents/runs/20260315_185613/run.log +0 -96
  195. package/.desloppify/subagents/runs/20260315_185613/run_summary.json +0 -156
  196. package/.editorconfig +0 -12
  197. package/.github/dependabot.yml +0 -11
  198. package/.github/workflows/docs.yml +0 -46
  199. package/.github/workflows/test.yml +0 -40
  200. package/.markdownlint.jsonc +0 -11
  201. package/.node-version +0 -1
  202. package/.oxfmtrc.json +0 -9
  203. package/.oxlintrc.json +0 -35
  204. package/docs/ARCHITECTURE.md +0 -125
  205. package/scorecard.png +0 -0
  206. package/src/cli/cli.ts +0 -141
  207. package/src/cli/validators.ts +0 -68
  208. package/src/formats/anthropic/index.ts +0 -14
  209. package/src/formats/anthropic/parse.ts +0 -70
  210. package/src/formats/anthropic/schema.ts +0 -74
  211. package/src/formats/anthropic/serialize.ts +0 -179
  212. package/src/formats/openai/chat-completions/index.ts +0 -14
  213. package/src/formats/openai/chat-completions/parse.ts +0 -47
  214. package/src/formats/openai/chat-completions/schema.ts +0 -92
  215. package/src/formats/openai/chat-completions/serialize.ts +0 -146
  216. package/src/formats/openai/responses/index.ts +0 -14
  217. package/src/formats/openai/responses/parse.ts +0 -73
  218. package/src/formats/openai/responses/schema.ts +0 -86
  219. package/src/formats/openai/responses/serialize.ts +0 -328
  220. package/src/formats/request-helpers.ts +0 -56
  221. package/src/formats/serialize-helpers.ts +0 -43
  222. package/src/formats/types.ts +0 -26
  223. package/src/history.ts +0 -70
  224. package/src/index.ts +0 -46
  225. package/src/loader.ts +0 -246
  226. package/src/logger.ts +0 -70
  227. package/src/mock-server.ts +0 -203
  228. package/src/route-handler.ts +0 -144
  229. package/src/rule-builder.ts +0 -73
  230. package/src/rule-engine.ts +0 -165
  231. package/src/sse-writer.ts +0 -35
  232. package/src/types/reply.ts +0 -92
  233. package/src/types/request.ts +0 -56
  234. package/src/types/rule.ts +0 -125
  235. package/src/types.ts +0 -24
  236. package/test/cli-validators.test.ts +0 -151
  237. package/test/formats/anthropic.test.ts +0 -336
  238. package/test/formats/openai.test.ts +0 -316
  239. package/test/formats/parse-helpers.test.ts +0 -315
  240. package/test/formats/responses.test.ts +0 -380
  241. package/test/helpers/make-req.ts +0 -18
  242. package/test/history.test.ts +0 -361
  243. package/test/loader.test.ts +0 -333
  244. package/test/logger.test.ts +0 -344
  245. package/test/mock-server.test.ts +0 -619
  246. package/test/rule-engine.test.ts +0 -229
  247. package/tsconfig.json +0 -24
  248. package/tsconfig.test.json +0 -11
  249. package/typedoc.json +0 -9
  250. 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>;