llm-mock-server 1.0.1 → 1.0.3

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 (129) hide show
  1. package/.claude/skills/desloppify/SKILL.md +308 -0
  2. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000801.json +242 -0
  3. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000905.json +248 -0
  4. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000917.json +248 -0
  5. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000950.json +311 -0
  6. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/claude_launch_prompt.md +17 -0
  7. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.json +255 -0
  8. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.template.json +22 -0
  9. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/reviewer_instructions.md +20 -0
  10. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/session.json +20 -0
  11. package/.desloppify/query.json +284 -0
  12. package/.desloppify/review_packet_blind.json +1303 -0
  13. package/.desloppify/review_packets/holistic_packet_20260315_000339.json +1471 -0
  14. package/.desloppify/state-typescript.json +5114 -0
  15. package/.desloppify/state-typescript.json.bak +5108 -0
  16. package/.editorconfig +12 -0
  17. package/.github/workflows/test.yml +3 -0
  18. package/.oxfmtrc.json +9 -0
  19. package/dist/cli.js +5 -2
  20. package/dist/cli.js.map +1 -1
  21. package/dist/formats/anthropic/index.js +1 -1
  22. package/dist/formats/anthropic/index.js.map +1 -1
  23. package/dist/formats/anthropic/parse.d.ts +1 -1
  24. package/dist/formats/anthropic/parse.d.ts.map +1 -1
  25. package/dist/formats/anthropic/parse.js +1 -1
  26. package/dist/formats/anthropic/parse.js.map +1 -1
  27. package/dist/formats/anthropic/serialize.d.ts +2 -2
  28. package/dist/formats/anthropic/serialize.d.ts.map +1 -1
  29. package/dist/formats/anthropic/serialize.js +6 -3
  30. package/dist/formats/anthropic/serialize.js.map +1 -1
  31. package/dist/formats/openai/index.js +1 -1
  32. package/dist/formats/openai/index.js.map +1 -1
  33. package/dist/formats/openai/parse.d.ts +1 -1
  34. package/dist/formats/openai/parse.d.ts.map +1 -1
  35. package/dist/formats/openai/parse.js +1 -1
  36. package/dist/formats/openai/parse.js.map +1 -1
  37. package/dist/formats/openai/serialize.d.ts +2 -2
  38. package/dist/formats/openai/serialize.d.ts.map +1 -1
  39. package/dist/formats/openai/serialize.js +12 -15
  40. package/dist/formats/openai/serialize.js.map +1 -1
  41. package/dist/formats/request-helpers.d.ts +13 -0
  42. package/dist/formats/request-helpers.d.ts.map +1 -0
  43. package/dist/formats/request-helpers.js +28 -0
  44. package/dist/formats/request-helpers.js.map +1 -0
  45. package/dist/formats/responses/index.js +1 -1
  46. package/dist/formats/responses/index.js.map +1 -1
  47. package/dist/formats/responses/parse.d.ts +1 -1
  48. package/dist/formats/responses/parse.d.ts.map +1 -1
  49. package/dist/formats/responses/parse.js +1 -1
  50. package/dist/formats/responses/parse.js.map +1 -1
  51. package/dist/formats/responses/schema.d.ts +1 -20
  52. package/dist/formats/responses/schema.d.ts.map +1 -1
  53. package/dist/formats/responses/schema.js.map +1 -1
  54. package/dist/formats/responses/serialize.d.ts +2 -2
  55. package/dist/formats/responses/serialize.d.ts.map +1 -1
  56. package/dist/formats/responses/serialize.js +6 -3
  57. package/dist/formats/responses/serialize.js.map +1 -1
  58. package/dist/formats/serialize-helpers.d.ts +14 -0
  59. package/dist/formats/serialize-helpers.d.ts.map +1 -0
  60. package/dist/formats/serialize-helpers.js +25 -0
  61. package/dist/formats/serialize-helpers.js.map +1 -0
  62. package/dist/formats/types.d.ts +3 -3
  63. package/dist/formats/types.d.ts.map +1 -1
  64. package/dist/loader.d.ts +3 -2
  65. package/dist/loader.d.ts.map +1 -1
  66. package/dist/loader.js +6 -9
  67. package/dist/loader.js.map +1 -1
  68. package/dist/logger.d.ts +1 -0
  69. package/dist/logger.d.ts.map +1 -1
  70. package/dist/logger.js +17 -23
  71. package/dist/logger.js.map +1 -1
  72. package/dist/mock-server.d.ts.map +1 -1
  73. package/dist/mock-server.js +8 -15
  74. package/dist/mock-server.js.map +1 -1
  75. package/dist/route-handler.d.ts +2 -1
  76. package/dist/route-handler.d.ts.map +1 -1
  77. package/dist/rule-engine.d.ts +12 -1
  78. package/dist/rule-engine.d.ts.map +1 -1
  79. package/dist/rule-engine.js +14 -0
  80. package/dist/rule-engine.js.map +1 -1
  81. package/dist/types/reply.d.ts +6 -10
  82. package/dist/types/reply.d.ts.map +1 -1
  83. package/dist/types/request.d.ts +7 -11
  84. package/dist/types/request.d.ts.map +1 -1
  85. package/dist/types/rule.d.ts +3 -10
  86. package/dist/types/rule.d.ts.map +1 -1
  87. package/dist/types.d.ts +3 -1
  88. package/dist/types.d.ts.map +1 -1
  89. package/package.json +5 -2
  90. package/scorecard.png +0 -0
  91. package/src/cli-validators.ts +12 -4
  92. package/src/cli.ts +27 -7
  93. package/src/formats/anthropic/index.ts +1 -1
  94. package/src/formats/anthropic/parse.ts +25 -6
  95. package/src/formats/anthropic/schema.ts +16 -8
  96. package/src/formats/anthropic/serialize.ts +116 -28
  97. package/src/formats/openai/index.ts +1 -1
  98. package/src/formats/openai/parse.ts +13 -3
  99. package/src/formats/openai/schema.ts +43 -30
  100. package/src/formats/openai/serialize.ts +84 -30
  101. package/src/formats/{parse-helpers.ts → request-helpers.ts} +4 -32
  102. package/src/formats/responses/index.ts +1 -1
  103. package/src/formats/responses/parse.ts +18 -4
  104. package/src/formats/responses/schema.ts +34 -22
  105. package/src/formats/responses/serialize.ts +237 -38
  106. package/src/formats/serialize-helpers.ts +38 -0
  107. package/src/formats/types.ts +18 -5
  108. package/src/index.ts +3 -1
  109. package/src/loader.ts +43 -20
  110. package/src/logger.ts +31 -19
  111. package/src/mock-server.ts +38 -21
  112. package/src/route-handler.ts +50 -15
  113. package/src/rule-engine.ts +64 -11
  114. package/src/types/reply.ts +12 -12
  115. package/src/types/request.ts +7 -11
  116. package/src/types/rule.ts +3 -10
  117. package/src/types.ts +23 -4
  118. package/test/cli-validators.test.ts +16 -4
  119. package/test/formats/anthropic.test.ts +84 -23
  120. package/test/formats/openai.test.ts +85 -24
  121. package/test/formats/parse-helpers.test.ts +315 -0
  122. package/test/formats/responses.test.ts +99 -34
  123. package/test/helpers/make-req.ts +18 -0
  124. package/test/history.test.ts +361 -0
  125. package/test/loader.test.ts +44 -45
  126. package/test/logger.test.ts +344 -0
  127. package/test/mock-server.test.ts +77 -23
  128. package/test/rule-engine.test.ts +57 -41
  129. package/src/types/index.ts +0 -4
@@ -1,15 +1,43 @@
1
1
  import type { ReplyObject, ReplyOptions } from "../../types.js";
2
2
  import type { SSEChunk } from "../types.js";
3
- import { splitText, genId, toolId, finishReason, MS_PER_SECOND, DEFAULT_USAGE } from "../parse-helpers.js";
3
+ import {
4
+ splitText,
5
+ genId,
6
+ toolId,
7
+ finishReason,
8
+ MS_PER_SECOND,
9
+ DEFAULT_USAGE,
10
+ } from "../serialize-helpers.js";
11
+
12
+ function buildUsage(usage: { input: number; output: number }) {
13
+ return {
14
+ prompt_tokens: usage.input,
15
+ completion_tokens: usage.output,
16
+ total_tokens: usage.input + usage.output,
17
+ prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0 },
18
+ completion_tokens_details: {
19
+ reasoning_tokens: 0,
20
+ audio_tokens: 0,
21
+ accepted_prediction_tokens: 0,
22
+ rejected_prediction_tokens: 0,
23
+ },
24
+ };
25
+ }
4
26
 
5
27
  function chunkEnvelope(
6
- id: string, created: number, model: string,
7
- delta: Record<string, unknown>, finish_reason: string | null = null,
28
+ id: string,
29
+ created: number,
30
+ model: string,
31
+ delta: Record<string, unknown>,
32
+ finish_reason: string | null = null,
8
33
  usage: Record<string, unknown> | null = null,
9
34
  ): SSEChunk {
10
35
  return {
11
36
  data: JSON.stringify({
12
- id, object: "chat.completion.chunk", created, model,
37
+ id,
38
+ object: "chat.completion.chunk",
39
+ created,
40
+ model,
13
41
  system_fingerprint: null,
14
42
  service_tier: "default",
15
43
  choices: [{ index: 0, delta, logprobs: null, finish_reason }],
@@ -18,7 +46,11 @@ function chunkEnvelope(
18
46
  };
19
47
  }
20
48
 
21
- export function serialize(reply: ReplyObject, model: string, options: ReplyOptions = {}): readonly SSEChunk[] {
49
+ export function serialize(
50
+ reply: ReplyObject,
51
+ model: string,
52
+ options: ReplyOptions = {},
53
+ ): readonly SSEChunk[] {
22
54
  const id = genId("chatcmpl");
23
55
  const created = Math.floor(Date.now() / MS_PER_SECOND);
24
56
  const usage = reply.usage ?? DEFAULT_USAGE;
@@ -31,32 +63,39 @@ export function serialize(reply: ReplyObject, model: string, options: ReplyOptio
31
63
 
32
64
  const toolChunks = (reply.tools ?? []).map((tool, i) =>
33
65
  chunkEnvelope(id, created, model, {
34
- tool_calls: [{
35
- index: i, id: toolId(tool, "call", i), type: "function",
36
- function: { name: tool.name, arguments: JSON.stringify(tool.args) },
37
- }],
66
+ tool_calls: [
67
+ {
68
+ index: i,
69
+ id: toolId(tool, "call", i),
70
+ type: "function",
71
+ function: { name: tool.name, arguments: JSON.stringify(tool.args) },
72
+ },
73
+ ],
38
74
  }),
39
75
  );
40
76
 
41
- const usageChunk = {
42
- prompt_tokens: usage.input,
43
- completion_tokens: usage.output,
44
- total_tokens: usage.input + usage.output,
45
- prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0 },
46
- completion_tokens_details: { reasoning_tokens: 0, audio_tokens: 0, accepted_prediction_tokens: 0, rejected_prediction_tokens: 0 },
47
- };
77
+ const usageChunk = buildUsage(usage);
48
78
 
49
79
  return [
50
80
  chunkEnvelope(id, created, model, { role: "assistant" }),
51
81
  ...textChunks,
52
82
  ...toolChunks,
53
- chunkEnvelope(id, created, model, {}, finishReason(reply, "tool_calls", "stop")),
83
+ chunkEnvelope(
84
+ id,
85
+ created,
86
+ model,
87
+ {},
88
+ finishReason(reply, "tool_calls", "stop"),
89
+ ),
54
90
  chunkEnvelope(id, created, model, {}, null, usageChunk),
55
91
  { data: "[DONE]" },
56
92
  ];
57
93
  }
58
94
 
59
- export function serializeComplete(reply: ReplyObject, model: string): unknown {
95
+ export function serializeComplete(
96
+ reply: ReplyObject,
97
+ model: string,
98
+ ): Record<string, unknown> {
60
99
  const id = genId("chatcmpl");
61
100
  const created = Math.floor(Date.now() / MS_PER_SECOND);
62
101
  const usage = reply.usage ?? DEFAULT_USAGE;
@@ -66,27 +105,42 @@ export function serializeComplete(reply: ReplyObject, model: string): unknown {
66
105
  content: reply.text ?? null,
67
106
  ...(reply.tools?.length && {
68
107
  tool_calls: reply.tools.map((tool, i) => ({
69
- id: toolId(tool, "call", i), type: "function",
108
+ id: toolId(tool, "call", i),
109
+ type: "function",
70
110
  function: { name: tool.name, arguments: JSON.stringify(tool.args) },
71
111
  })),
72
112
  }),
73
113
  };
74
114
 
75
115
  return {
76
- id, object: "chat.completion", created, model,
116
+ id,
117
+ object: "chat.completion",
118
+ created,
119
+ model,
77
120
  system_fingerprint: null,
78
121
  service_tier: "default",
79
- choices: [{ index: 0, message, logprobs: null, finish_reason: finishReason(reply, "tool_calls", "stop") }],
80
- usage: {
81
- prompt_tokens: usage.input,
82
- completion_tokens: usage.output,
83
- total_tokens: usage.input + usage.output,
84
- prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0 },
85
- completion_tokens_details: { reasoning_tokens: 0, audio_tokens: 0, accepted_prediction_tokens: 0, rejected_prediction_tokens: 0 },
86
- },
122
+ choices: [
123
+ {
124
+ index: 0,
125
+ message,
126
+ logprobs: null,
127
+ finish_reason: finishReason(reply, "tool_calls", "stop"),
128
+ },
129
+ ],
130
+ usage: buildUsage(usage),
87
131
  };
88
132
  }
89
133
 
90
- export function serializeError(error: { status: number; message: string; type?: string }): unknown {
91
- return { error: { message: error.message, type: error.type ?? "server_error", code: null } };
134
+ export function serializeError(error: {
135
+ status: number;
136
+ message: string;
137
+ type?: string;
138
+ }): Record<string, unknown> {
139
+ return {
140
+ error: {
141
+ message: error.message,
142
+ type: error.type ?? "server_error",
143
+ code: null,
144
+ },
145
+ };
92
146
  }
@@ -1,39 +1,11 @@
1
- import type { FormatName, Message, MockRequest, ReplyObject, ToolDef } from "../types.js";
2
-
3
- export const MS_PER_SECOND = 1000;
4
- const BASE_36 = 36;
5
- export const DEFAULT_USAGE = { input: 10, output: 5 } as const;
1
+ import type { FormatName, Message, MockRequest, ToolDef } from "../types.js";
6
2
 
7
3
  function asRecord(body: unknown): Record<string, unknown> {
8
- if (typeof body === "object" && body !== null) return body as Record<string, unknown>;
4
+ if (typeof body === "object" && body !== null)
5
+ return body as Record<string, unknown>;
9
6
  return {};
10
7
  }
11
8
 
12
- export function splitText(text: string, chunkSize: number): string[] {
13
- if (chunkSize <= 0 || text.length <= chunkSize) return [text];
14
- const chunks: string[] = [];
15
- for (let i = 0; i < text.length; i += chunkSize) {
16
- chunks.push(text.slice(i, i + chunkSize));
17
- }
18
- return chunks;
19
- }
20
-
21
- export function genId(prefix: string): string {
22
- return `${prefix}_${Date.now().toString(BASE_36)}`;
23
- }
24
-
25
- export function toolId(tool: { id?: string | undefined }, prefix: string, index: number): string {
26
- return tool.id ?? `${prefix}_${Date.now().toString(BASE_36)}_${index}`;
27
- }
28
-
29
- export function shouldEmitText(reply: ReplyObject): boolean {
30
- return Boolean(reply.text) || (!reply.tools?.length && !reply.reasoning);
31
- }
32
-
33
- export function finishReason(reply: ReplyObject, onTools: string, onStop: string): string {
34
- return reply.tools?.length ? onTools : onStop;
35
- }
36
-
37
9
  export function isStreaming(body: unknown): boolean {
38
10
  return asRecord(body)["stream"] !== false;
39
11
  }
@@ -45,7 +17,7 @@ export interface RequestMeta {
45
17
 
46
18
  const EMPTY_META: RequestMeta = { headers: {}, path: "" };
47
19
 
48
- export interface ParsedBody {
20
+ interface ParsedBody {
49
21
  readonly model?: string | undefined;
50
22
  readonly stream?: boolean | undefined;
51
23
  }
@@ -1,5 +1,5 @@
1
1
  import type { Format } from "../types.js";
2
- import { isStreaming } from "../parse-helpers.js";
2
+ import { isStreaming } from "../request-helpers.js";
3
3
  import { parseRequest } from "./parse.js";
4
4
  import { serialize, serializeComplete, serializeError } from "./serialize.js";
5
5
 
@@ -1,8 +1,14 @@
1
1
  import type { MockRequest, Message, ToolDef } from "../../types.js";
2
- import { buildMockRequest, type RequestMeta } from "../parse-helpers.js";
3
- import { ResponsesRequestSchema, FunctionToolSchema, type ResponsesRequest } from "./schema.js";
2
+ import { buildMockRequest, type RequestMeta } from "../request-helpers.js";
3
+ import {
4
+ ResponsesRequestSchema,
5
+ FunctionToolSchema,
6
+ type ResponsesRequest,
7
+ } from "./schema.js";
4
8
 
5
- function extractInputContent(content: string | Record<string, unknown>[]): string {
9
+ function extractInputContent(
10
+ content: string | Record<string, unknown>[],
11
+ ): string {
6
12
  if (typeof content === "string") return content;
7
13
  return content
8
14
  .filter((b) => b["type"] === "input_text" || b["type"] === "text")
@@ -52,5 +58,13 @@ function parseTools(req: ResponsesRequest): readonly ToolDef[] | undefined {
52
58
 
53
59
  export function parseRequest(body: unknown, meta?: RequestMeta): MockRequest {
54
60
  const req = ResponsesRequestSchema.parse(body);
55
- return buildMockRequest("responses", req, parseInput(req), parseTools(req), "codex-mini", body, meta);
61
+ return buildMockRequest(
62
+ "responses",
63
+ req,
64
+ parseInput(req),
65
+ parseTools(req),
66
+ "codex-mini",
67
+ body,
68
+ meta,
69
+ );
56
70
  }
@@ -1,6 +1,10 @@
1
1
  import { z } from "zod";
2
2
 
3
- export { ResponsesRequestSchema, FunctionToolSchema, type FunctionTool, type ResponsesRequest } from "llm-schemas/openai/responses";
3
+ export {
4
+ ResponsesRequestSchema,
5
+ FunctionToolSchema,
6
+ type ResponsesRequest,
7
+ } from "llm-schemas/openai/responses";
4
8
 
5
9
  const OutputContentSchema = z.object({
6
10
  type: z.string(),
@@ -20,30 +24,34 @@ const OutputItemSchema = z.object({
20
24
  summary: z.array(z.object({ type: z.string(), text: z.string() })).optional(),
21
25
  });
22
26
 
23
- export type ResponsesOutputItem = z.infer<typeof OutputItemSchema>;
24
-
25
27
  export const ResponsesEventSchema = z.object({
26
28
  type: z.string(),
27
29
  sequence_number: z.number().optional(),
28
- response: z.object({
29
- id: z.string(),
30
- object: z.string(),
31
- created_at: z.number(),
32
- model: z.string(),
33
- status: z.string(),
34
- output: z.array(OutputItemSchema),
35
- usage: z.object({
36
- input_tokens: z.number(),
37
- output_tokens: z.number(),
38
- total_tokens: z.number(),
39
- }).optional(),
40
- }).optional(),
30
+ response: z
31
+ .object({
32
+ id: z.string(),
33
+ object: z.string(),
34
+ created_at: z.number(),
35
+ model: z.string(),
36
+ status: z.string(),
37
+ output: z.array(OutputItemSchema),
38
+ usage: z
39
+ .object({
40
+ input_tokens: z.number(),
41
+ output_tokens: z.number(),
42
+ total_tokens: z.number(),
43
+ })
44
+ .optional(),
45
+ })
46
+ .optional(),
41
47
  item: OutputItemSchema.optional(),
42
- part: z.object({
43
- type: z.string(),
44
- text: z.string().optional(),
45
- annotations: z.array(z.unknown()).optional(),
46
- }).optional(),
48
+ part: z
49
+ .object({
50
+ type: z.string(),
51
+ text: z.string().optional(),
52
+ annotations: z.array(z.unknown()).optional(),
53
+ })
54
+ .optional(),
47
55
  delta: z.string().optional(),
48
56
  item_id: z.string().optional(),
49
57
  });
@@ -68,7 +76,11 @@ export type ResponsesComplete = z.infer<typeof ResponsesCompleteSchema>;
68
76
 
69
77
  export const ResponsesErrorSchema = z.object({
70
78
  type: z.literal("error"),
71
- error: z.object({ message: z.string(), type: z.string().optional(), code: z.string().optional() }),
79
+ error: z.object({
80
+ message: z.string(),
81
+ type: z.string().optional(),
82
+ code: z.string().optional(),
83
+ }),
72
84
  });
73
85
 
74
86
  export type ResponsesError = z.infer<typeof ResponsesErrorSchema>;