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 +12 -0
- package/.github/workflows/test.yml +3 -0
- package/.oxfmtrc.json +9 -0
- package/package.json +5 -2
- package/src/cli-validators.ts +12 -4
- package/src/cli.ts +22 -6
- package/src/formats/anthropic/parse.ts +24 -5
- package/src/formats/anthropic/schema.ts +16 -8
- package/src/formats/anthropic/serialize.ts +111 -27
- package/src/formats/openai/parse.ts +12 -2
- package/src/formats/openai/schema.ts +43 -30
- package/src/formats/openai/serialize.ts +73 -17
- package/src/formats/request-helpers.ts +2 -1
- package/src/formats/responses/parse.ts +17 -3
- package/src/formats/responses/schema.ts +34 -20
- package/src/formats/responses/serialize.ts +233 -38
- package/src/formats/serialize-helpers.ts +10 -2
- package/src/formats/types.ts +16 -3
- package/src/index.ts +3 -1
- package/src/loader.ts +36 -9
- package/src/logger.ts +25 -7
- package/src/mock-server.ts +28 -7
- package/src/route-handler.ts +49 -14
- package/src/rule-engine.ts +43 -12
- package/src/types/reply.ts +6 -2
- package/src/types.ts +24 -3
- package/test/cli-validators.test.ts +16 -4
- package/test/formats/anthropic.test.ts +80 -19
- package/test/formats/openai.test.ts +85 -24
- package/test/formats/parse-helpers.test.ts +47 -7
- package/test/formats/responses.test.ts +95 -30
- package/test/history.test.ts +18 -5
- package/test/loader.test.ts +33 -18
- package/test/logger.test.ts +59 -9
- package/test/mock-server.test.ts +76 -22
- package/test/rule-engine.test.ts +49 -19
package/.editorconfig
ADDED
package/.oxfmtrc.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-mock-server",
|
|
3
|
-
"version": "1.0.
|
|
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",
|
package/src/cli-validators.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
72
|
+
console.log(
|
|
73
|
+
` ${pc.dim("Rules")} ${pc.bold(String(server.ruleCount))} loaded`,
|
|
74
|
+
);
|
|
65
75
|
if (latency > 0) {
|
|
66
|
-
console.log(
|
|
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", () => {
|
|
107
|
-
|
|
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")
|
|
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(
|
|
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(
|
|
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 && {
|
|
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(
|
|
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 {
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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({
|
|
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 {
|
|
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(
|
|
16
|
+
function contentBlock(
|
|
17
|
+
index: number,
|
|
18
|
+
startBlock: unknown,
|
|
19
|
+
deltas: SSEChunk[],
|
|
20
|
+
): SSEChunk[] {
|
|
10
21
|
return [
|
|
11
|
-
{
|
|
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
|
-
{
|
|
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 {
|
|
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) =>
|
|
59
|
+
splitText(text, chunkSize).map((piece) =>
|
|
60
|
+
delta(i, { type: "text_delta", text: piece }),
|
|
61
|
+
),
|
|
32
62
|
);
|
|
33
63
|
}
|
|
34
64
|
|
|
35
|
-
function toolBlocks(
|
|
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
|
-
[
|
|
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(
|
|
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
|
|
53
|
-
|
|
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
|
-
{
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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(
|
|
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
|
|
79
|
-
|
|
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",
|
|
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,
|
|
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: {
|
|
94
|
-
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
finish_reason: z.string(),
|
|
65
|
-
})),
|
|
78
|
+
),
|
|
66
79
|
usage: UsageSchema.optional(),
|
|
67
80
|
});
|
|
68
81
|
|