llm-mock-server 1.0.2 → 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.
package/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 2
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -27,6 +27,9 @@ jobs:
27
27
  - name: Type check
28
28
  run: tsc --noEmit && tsc --noEmit -p tsconfig.test.json
29
29
 
30
+ - name: Format check
31
+ run: npm run fmt:check
32
+
30
33
  - name: Lint
31
34
  run: npm run lint
32
35
 
package/.oxfmtrc.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "all",
5
+ "printWidth": 80,
6
+ "tabWidth": 2,
7
+ "useTabs": false,
8
+ "ignorePatterns": ["dist/", "coverage/"]
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-mock-server",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A standalone mock LLM server for deterministic testing: OpenAI, Anthropic, and Responses API formats",
5
5
  "type": "module",
6
6
  "engines": {
@@ -20,10 +20,12 @@
20
20
  },
21
21
  "scripts": {
22
22
  "build": "tsc",
23
+ "fmt": "oxfmt --write src/ test/",
24
+ "fmt:check": "oxfmt --check src/ test/",
23
25
  "lint": "oxlint --tsconfig tsconfig.json --import-plugin --vitest-plugin src/ test/",
24
26
  "test": "vitest run",
25
27
  "test:watch": "vitest",
26
- "check": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && npm run lint && npm test",
28
+ "check": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && npm run fmt:check && npm run lint && npm test",
27
29
  "dev": "tsx src/cli.ts",
28
30
  "start": "node dist/cli.js"
29
31
  },
@@ -48,6 +50,7 @@
48
50
  "devDependencies": {
49
51
  "@types/node": "25.5.0",
50
52
  "@vitest/coverage-v8": "4.1.0",
53
+ "oxfmt": "0.40.0",
51
54
  "oxlint": "1.55.0",
52
55
  "tsx": "4.21.0",
53
56
  "typescript": "5.9.3",
@@ -29,7 +29,9 @@ export function parseLogLevel(value: string): LogLevel {
29
29
 
30
30
  export async function parseHost(value: string): Promise<string> {
31
31
  if (!value) {
32
- throw new Error(`Invalid host "${value}". Must be a resolvable hostname or IP address.`);
32
+ throw new Error(
33
+ `Invalid host "${value}". Must be a resolvable hostname or IP address.`,
34
+ );
33
35
  }
34
36
  if (value === "localhost" || isIP(value) !== 0) {
35
37
  return value;
@@ -38,14 +40,18 @@ export async function parseHost(value: string): Promise<string> {
38
40
  await lookup(value);
39
41
  return value;
40
42
  } catch {
41
- throw new Error(`Invalid host "${value}". Must be a resolvable hostname or IP address.`);
43
+ throw new Error(
44
+ `Invalid host "${value}". Must be a resolvable hostname or IP address.`,
45
+ );
42
46
  }
43
47
  }
44
48
 
45
49
  export function parseChunkSize(value: string): number {
46
50
  const size = parseInt(value, 10);
47
51
  if (isNaN(size) || size < 0) {
48
- throw new Error(`Invalid chunk size "${value}". Must be a non-negative integer.`);
52
+ throw new Error(
53
+ `Invalid chunk size "${value}". Must be a non-negative integer.`,
54
+ );
49
55
  }
50
56
  return size;
51
57
  }
@@ -53,7 +59,9 @@ export function parseChunkSize(value: string): number {
53
59
  export function parseLatency(value: string): number {
54
60
  const ms = parseInt(value, 10);
55
61
  if (isNaN(ms) || ms < 0) {
56
- throw new Error(`Invalid latency "${value}". Must be a non-negative integer (ms).`);
62
+ throw new Error(
63
+ `Invalid latency "${value}". Must be a non-negative integer (ms).`,
64
+ );
57
65
  }
58
66
  return ms;
59
67
  }
package/src/cli.ts CHANGED
@@ -6,7 +6,13 @@ import { Command } from "commander";
6
6
  import pc from "picocolors";
7
7
  import { MockServer } from "./mock-server.js";
8
8
  import { Logger } from "./logger.js";
9
- import { parsePort, parseHost, parseLogLevel, parseChunkSize, parseLatency } from "./cli-validators.js";
9
+ import {
10
+ parsePort,
11
+ parseHost,
12
+ parseLogLevel,
13
+ parseChunkSize,
14
+ parseLatency,
15
+ } from "./cli-validators.js";
10
16
 
11
17
  const require = createRequire(import.meta.url);
12
18
  const { version } = require("../package.json") as { version: string };
@@ -58,12 +64,18 @@ async function start(options: StartOptions): Promise<void> {
58
64
 
59
65
  if (!quiet) {
60
66
  console.log();
61
- console.log(` ${pc.bold(pc.cyan("llm-mock-server"))} ${pc.dim(`v${version}`)}`);
67
+ console.log(
68
+ ` ${pc.bold(pc.cyan("llm-mock-server"))} ${pc.dim(`v${version}`)}`,
69
+ );
62
70
  console.log();
63
71
  console.log(` ${pc.dim("Port")} ${pc.bold(String(port))}`);
64
- console.log(` ${pc.dim("Rules")} ${pc.bold(String(server.ruleCount))} loaded`);
72
+ console.log(
73
+ ` ${pc.dim("Rules")} ${pc.bold(String(server.ruleCount))} loaded`,
74
+ );
65
75
  if (latency > 0) {
66
- console.log(` ${pc.dim("Latency")} ${pc.bold(`${String(latency)}ms`)} per chunk`);
76
+ console.log(
77
+ ` ${pc.dim("Latency")} ${pc.bold(`${String(latency)}ms`)} per chunk`,
78
+ );
67
79
  }
68
80
  console.log(
69
81
  ` ${pc.dim("Endpoints")} ${pc.green("/v1/chat/completions")}, ${pc.green("/v1/messages")}, ${pc.green("/v1/responses")}`,
@@ -103,8 +115,12 @@ async function start(options: StartOptions): Promise<void> {
103
115
  process.exit(0);
104
116
  };
105
117
 
106
- process.on("SIGINT", () => { shutdown("SIGINT").catch(() => process.exit(1)); });
107
- process.on("SIGTERM", () => { shutdown("SIGTERM").catch(() => process.exit(1)); });
118
+ process.on("SIGINT", () => {
119
+ shutdown("SIGINT").catch(() => process.exit(1));
120
+ });
121
+ process.on("SIGTERM", () => {
122
+ shutdown("SIGTERM").catch(() => process.exit(1));
123
+ });
108
124
  }
109
125
 
110
126
  const program = new Command()
@@ -4,18 +4,27 @@ import { AnthropicRequestSchema, type AnthropicRequest } from "./schema.js";
4
4
 
5
5
  function extractSystem(system: AnthropicRequest["system"]): Message[] {
6
6
  if (system == null) return [];
7
- if (typeof system === "string") return system ? [{ role: "system", content: system }] : [];
7
+ if (typeof system === "string")
8
+ return system ? [{ role: "system", content: system }] : [];
8
9
  const text = system.map((b) => b.text).join("\n");
9
10
  return text ? [{ role: "system", content: text }] : [];
10
11
  }
11
12
 
12
- function extractContent(content: AnthropicRequest["messages"][number]["content"]): { content: string; toolCallId?: string | undefined } {
13
+ function extractContent(
14
+ content: AnthropicRequest["messages"][number]["content"],
15
+ ): {
16
+ content: string;
17
+ toolCallId?: string | undefined;
18
+ } {
13
19
  if (typeof content === "string") return { content };
14
20
  const text = content
15
21
  .filter((b): b is { type: "text"; text: string } => b.type === "text")
16
22
  .map((b) => b.text)
17
23
  .join("\n");
18
- const toolResult = content.find((b): b is { type: "tool_result"; tool_use_id: string } => b.type === "tool_result");
24
+ const toolResult = content.find(
25
+ (b): b is { type: "tool_result"; tool_use_id: string } =>
26
+ b.type === "tool_result",
27
+ );
19
28
  const toolCallId = toolResult?.tool_use_id;
20
29
  return { content: text, toolCallId };
21
30
  }
@@ -27,7 +36,9 @@ function parseMessages(req: AnthropicRequest): readonly Message[] {
27
36
  return {
28
37
  role: m.role,
29
38
  content: extracted.content,
30
- ...(extracted.toolCallId !== undefined && { toolCallId: extracted.toolCallId }),
39
+ ...(extracted.toolCallId !== undefined && {
40
+ toolCallId: extracted.toolCallId,
41
+ }),
31
42
  };
32
43
  });
33
44
  return [...system, ...conversation];
@@ -44,5 +55,13 @@ function parseTools(req: AnthropicRequest): readonly ToolDef[] | undefined {
44
55
 
45
56
  export function parseRequest(body: unknown, meta?: RequestMeta): MockRequest {
46
57
  const req = AnthropicRequestSchema.parse(body);
47
- return buildMockRequest("anthropic", req, parseMessages(req), parseTools(req), "claude-sonnet-4-6", body, meta);
58
+ return buildMockRequest(
59
+ "anthropic",
60
+ req,
61
+ parseMessages(req),
62
+ parseTools(req),
63
+ "claude-sonnet-4-6",
64
+ body,
65
+ meta,
66
+ );
48
67
  }
@@ -1,6 +1,9 @@
1
1
  import { z } from "zod";
2
2
 
3
- export { AnthropicRequestSchema, type AnthropicRequest } from "llm-schemas/anthropic";
3
+ export {
4
+ AnthropicRequestSchema,
5
+ type AnthropicRequest,
6
+ } from "llm-schemas/anthropic";
4
7
 
5
8
  const ResponseContentBlockSchema = z.object({
6
9
  type: z.string(),
@@ -28,18 +31,23 @@ export type AnthropicMessageStart = z.infer<typeof AnthropicMessageStartSchema>;
28
31
  export const AnthropicBlockEventSchema = z.object({
29
32
  index: z.number(),
30
33
  content_block: ResponseContentBlockSchema.optional(),
31
- delta: z.object({
32
- type: z.string(),
33
- text: z.string().optional(),
34
- thinking: z.string().optional(),
35
- partial_json: z.string().optional(),
36
- }).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(),
37
42
  });
38
43
 
39
44
  export type AnthropicBlockEvent = z.infer<typeof AnthropicBlockEventSchema>;
40
45
 
41
46
  export const AnthropicDeltaSchema = z.object({
42
- delta: z.object({ stop_reason: z.string(), stop_sequence: z.string().nullable() }),
47
+ delta: z.object({
48
+ stop_reason: z.string(),
49
+ stop_sequence: z.string().nullable(),
50
+ }),
43
51
  usage: z.object({ output_tokens: z.number() }),
44
52
  });
45
53
 
@@ -1,21 +1,49 @@
1
1
  import type { ReplyObject, ReplyOptions } from "../../types.js";
2
2
  import type { SSEChunk } from "../types.js";
3
- import { splitText, genId, toolId, shouldEmitText, finishReason, DEFAULT_USAGE } from "../serialize-helpers.js";
3
+ import {
4
+ splitText,
5
+ genId,
6
+ toolId,
7
+ shouldEmitText,
8
+ finishReason,
9
+ DEFAULT_USAGE,
10
+ } from "../serialize-helpers.js";
4
11
 
5
12
  function buildUsage(usage: { input: number; output: number }) {
6
13
  return { input_tokens: usage.input, output_tokens: usage.output };
7
14
  }
8
15
 
9
- function contentBlock(index: number, startBlock: unknown, deltas: SSEChunk[]): SSEChunk[] {
16
+ function contentBlock(
17
+ index: number,
18
+ startBlock: unknown,
19
+ deltas: SSEChunk[],
20
+ ): SSEChunk[] {
10
21
  return [
11
- { event: "content_block_start", data: JSON.stringify({ type: "content_block_start", index, content_block: startBlock }) },
22
+ {
23
+ event: "content_block_start",
24
+ data: JSON.stringify({
25
+ type: "content_block_start",
26
+ index,
27
+ content_block: startBlock,
28
+ }),
29
+ },
12
30
  ...deltas,
13
- { event: "content_block_stop", data: JSON.stringify({ type: "content_block_stop", index }) },
31
+ {
32
+ event: "content_block_stop",
33
+ data: JSON.stringify({ type: "content_block_stop", index }),
34
+ },
14
35
  ];
15
36
  }
16
37
 
17
38
  function delta(index: number, payload: Record<string, unknown>): SSEChunk {
18
- return { event: "content_block_delta", data: JSON.stringify({ type: "content_block_delta", index, delta: payload }) };
39
+ return {
40
+ event: "content_block_delta",
41
+ data: JSON.stringify({
42
+ type: "content_block_delta",
43
+ index,
44
+ delta: payload,
45
+ }),
46
+ };
19
47
  }
20
48
 
21
49
  function reasoningBlock(i: number, reasoning: string): SSEChunk[] {
@@ -28,68 +56,124 @@ function textBlock(i: number, text: string, chunkSize: number): SSEChunk[] {
28
56
  return contentBlock(
29
57
  i,
30
58
  { type: "text", text: "" },
31
- splitText(text, chunkSize).map((piece) => delta(i, { type: "text_delta", text: piece })),
59
+ splitText(text, chunkSize).map((piece) =>
60
+ delta(i, { type: "text_delta", text: piece }),
61
+ ),
32
62
  );
33
63
  }
34
64
 
35
- function toolBlocks(startIndex: number, tools: ReplyObject["tools"]): SSEChunk[] {
65
+ function toolBlocks(
66
+ startIndex: number,
67
+ tools: ReplyObject["tools"],
68
+ ): SSEChunk[] {
36
69
  return (tools ?? []).flatMap((tool, i) => {
37
70
  const idx = startIndex + i;
38
71
  const id = toolId(tool, "toolu", idx);
39
72
  return contentBlock(
40
73
  idx,
41
74
  { type: "tool_use", id, name: tool.name, input: {} },
42
- [delta(idx, { type: "input_json_delta", partial_json: JSON.stringify(tool.args) })],
75
+ [
76
+ delta(idx, {
77
+ type: "input_json_delta",
78
+ partial_json: JSON.stringify(tool.args),
79
+ }),
80
+ ],
43
81
  );
44
82
  });
45
83
  }
46
84
 
47
- export function serialize(reply: ReplyObject, model: string, options: ReplyOptions = {}): readonly SSEChunk[] {
85
+ export function serialize(
86
+ reply: ReplyObject,
87
+ model: string,
88
+ options: ReplyOptions = {},
89
+ ): readonly SSEChunk[] {
48
90
  const id = genId("msg");
49
91
  const usage = reply.usage ?? DEFAULT_USAGE;
50
92
  let idx = 0;
51
93
 
52
- const reasoningChunks = reply.reasoning ? reasoningBlock(idx++, reply.reasoning) : [];
53
- const textChunks = shouldEmitText(reply) ? textBlock(idx++, reply.text ?? "", options.chunkSize ?? 0) : [];
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
+ : [];
54
100
  const toolChunks = toolBlocks(idx, reply.tools);
55
101
 
56
102
  return [
57
- { event: "message_start", data: JSON.stringify({
58
- type: "message_start",
59
- message: { id, type: "message", role: "assistant", model, content: [], stop_reason: null, usage: { ...buildUsage(usage), output_tokens: 0 } },
60
- })},
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
+ },
61
118
  ...reasoningChunks,
62
119
  ...textChunks,
63
120
  ...toolChunks,
64
- { event: "message_delta", data: JSON.stringify({
65
- type: "message_delta",
66
- delta: { stop_reason: finishReason(reply, "tool_use", "end_turn"), stop_sequence: null },
67
- usage: { output_tokens: usage.output },
68
- })},
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
+ },
69
132
  { event: "message_stop", data: JSON.stringify({ type: "message_stop" }) },
70
133
  ];
71
134
  }
72
135
 
73
- export function serializeComplete(reply: ReplyObject, model: string): Record<string, unknown> {
136
+ export function serializeComplete(
137
+ reply: ReplyObject,
138
+ model: string,
139
+ ): Record<string, unknown> {
74
140
  const id = genId("msg");
75
141
  const usage = reply.usage ?? DEFAULT_USAGE;
76
142
 
77
143
  const content: unknown[] = [
78
- ...(reply.reasoning ? [{ type: "thinking", thinking: reply.reasoning }] : []),
79
- ...(shouldEmitText(reply) ? [{ type: "text", text: reply.text ?? "" }] : []),
144
+ ...(reply.reasoning
145
+ ? [{ type: "thinking", thinking: reply.reasoning }]
146
+ : []),
147
+ ...(shouldEmitText(reply)
148
+ ? [{ type: "text", text: reply.text ?? "" }]
149
+ : []),
80
150
  ...(reply.tools ?? []).map((tool) => ({
81
- type: "tool_use", id: toolId(tool, "toolu", 0), name: tool.name, input: tool.args,
151
+ type: "tool_use",
152
+ id: toolId(tool, "toolu", 0),
153
+ name: tool.name,
154
+ input: tool.args,
82
155
  })),
83
156
  ];
84
157
 
85
158
  return {
86
- id, type: "message", role: "assistant", model, content,
159
+ id,
160
+ type: "message",
161
+ role: "assistant",
162
+ model,
163
+ content,
87
164
  stop_reason: finishReason(reply, "tool_use", "end_turn"),
88
165
  stop_sequence: null,
89
166
  usage: buildUsage(usage),
90
167
  };
91
168
  }
92
169
 
93
- export function serializeError(error: { status: number; message: string; type?: string }): Record<string, unknown> {
94
- return { type: "error", error: { type: error.type ?? "api_error", message: error.message } };
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
+ };
95
179
  }
@@ -2,7 +2,9 @@ import type { MockRequest, Message, ToolDef } from "../../types.js";
2
2
  import { buildMockRequest, type RequestMeta } from "../request-helpers.js";
3
3
  import { OpenAIRequestSchema, type OpenAIRequest } from "./schema.js";
4
4
 
5
- function extractContent(content: OpenAIRequest["messages"][number]["content"]): string {
5
+ function extractContent(
6
+ content: OpenAIRequest["messages"][number]["content"],
7
+ ): string {
6
8
  if (content == null) return "";
7
9
  if (typeof content === "string") return content;
8
10
  return content
@@ -30,5 +32,13 @@ function parseTools(req: OpenAIRequest): readonly ToolDef[] | undefined {
30
32
 
31
33
  export function parseRequest(body: unknown, meta?: RequestMeta): MockRequest {
32
34
  const req = OpenAIRequestSchema.parse(body);
33
- return buildMockRequest("openai", req, parseMessages(req), parseTools(req), "gpt-5.4", body, meta);
35
+ return buildMockRequest(
36
+ "openai",
37
+ req,
38
+ parseMessages(req),
39
+ parseTools(req),
40
+ "gpt-5.4",
41
+ body,
42
+ meta,
43
+ );
34
44
  }
@@ -1,6 +1,9 @@
1
1
  import { z } from "zod";
2
2
 
3
- export { OpenAIRequestSchema, type OpenAIRequest } from "llm-schemas/openai/chat-completions";
3
+ export {
4
+ OpenAIRequestSchema,
5
+ type OpenAIRequest,
6
+ } from "llm-schemas/openai/chat-completions";
4
7
 
5
8
  const ToolCallResponseSchema = z.object({
6
9
  id: z.string(),
@@ -12,16 +15,20 @@ const UsageSchema = z.object({
12
15
  prompt_tokens: z.number(),
13
16
  completion_tokens: z.number(),
14
17
  total_tokens: z.number(),
15
- prompt_tokens_details: z.object({
16
- cached_tokens: z.number().optional(),
17
- audio_tokens: z.number().optional(),
18
- }).optional(),
19
- completion_tokens_details: z.object({
20
- reasoning_tokens: z.number().optional(),
21
- audio_tokens: z.number().optional(),
22
- accepted_prediction_tokens: z.number().optional(),
23
- rejected_prediction_tokens: z.number().optional(),
24
- }).optional(),
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(),
25
32
  });
26
33
 
27
34
  export const OpenAIChunkSchema = z.object({
@@ -31,16 +38,20 @@ export const OpenAIChunkSchema = z.object({
31
38
  model: z.string(),
32
39
  system_fingerprint: z.string().nullable().optional(),
33
40
  service_tier: z.string().optional(),
34
- choices: z.array(z.object({
35
- index: z.number(),
36
- delta: z.object({
37
- role: z.string(),
38
- content: z.string(),
39
- tool_calls: z.array(ToolCallResponseSchema),
40
- }).partial(),
41
- logprobs: z.unknown().nullable().optional(),
42
- finish_reason: z.string().nullable(),
43
- })),
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
+ ),
44
55
  usage: UsageSchema.nullable().optional(),
45
56
  });
46
57
 
@@ -53,16 +64,18 @@ export const OpenAICompleteSchema = z.object({
53
64
  model: z.string(),
54
65
  system_fingerprint: z.string().nullable().optional(),
55
66
  service_tier: z.string().optional(),
56
- choices: z.array(z.object({
57
- index: z.number(),
58
- message: z.object({
59
- role: z.string(),
60
- content: z.string().nullable(),
61
- tool_calls: z.array(ToolCallResponseSchema).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(),
62
77
  }),
63
- logprobs: z.unknown().nullable().optional(),
64
- finish_reason: z.string(),
65
- })),
78
+ ),
66
79
  usage: UsageSchema.optional(),
67
80
  });
68
81