@deepseek-ai/dsh-llm-mock-server 0.1.6-alpha.2 → 0.1.7-alpha.1
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/README.i18n.yaml +2 -2
- package/README.md +6 -6
- package/README.zh.md +6 -6
- package/lib/index.js +120 -59
- package/lib/types/index.d.ts +8 -8
- package/package.json +4 -4
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/test-support/llm-mock-server/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 2f83cf66c02728820a4f8aa6f14b1a091d092626
|
|
6
|
+
README.zh.md: ad85853fb4a20c3ccdb444508da8032c18d7538a
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Scriptable
|
|
2
|
+
description: "Scriptable Messages-compatible fault server for testing LLM adapters and recovery policy without a provider key, for test authors and demos."
|
|
3
3
|
kind: "package-library"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
This package gives tests and demos a scriptable
|
|
12
|
+
This package gives tests and demos a scriptable Messages-compatible HTTP/SSE endpoint, so they can exercise model-provider failures and successes without a provider key. Each accepted `/v1/messages` request consumes the next scripted behavior, including resets, stalls, malformed chunks, rate limits, server errors, completions, and tool calls. Test authors can run it with `pnpm run mock:llm` or call `startMockLlmServer`, which returns captured requests for assertions. Seeded `random` behavior supports reproducible mixed-failure stress runs.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -39,7 +39,7 @@ pnpm run mock:llm \
|
|
|
39
39
|
--partial-text "discard this half"
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Point the shipping DeepSeek adapter at the server; it
|
|
42
|
+
Point the shipping DeepSeek adapter at the server; it sends requests to `/messages` beneath the server's `/v1` root:
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
45
|
DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1 \
|
|
@@ -88,13 +88,13 @@ Omitting `--seed` generates one and prints it in the `ready` record. `--random-w
|
|
|
88
88
|
|
|
89
89
|
### Timing and content controls
|
|
90
90
|
|
|
91
|
-
The CLI exposes `--success-text`, `--partial-text`, `--reasoning-text`, `--chunk-size`, `--chunk-delay-ms`, `--disconnect-delay-ms`, `--retry-after-ms`, `--request-id`, `--tool-name`, and `--tool-arguments`. Millisecond delays are bounded integers within Node's timer range; `retryAfterMs` must also be positive. The library accepts the same camel-case options. An optional exact `apiKey` validates `
|
|
91
|
+
The CLI exposes `--success-text`, `--partial-text`, `--reasoning-text`, `--chunk-size`, `--chunk-delay-ms`, `--disconnect-delay-ms`, `--retry-after-ms`, `--request-id`, `--tool-name`, and `--tool-arguments`. Millisecond delays are bounded integers within Node's timer range; `retryAfterMs` must also be positive. The library accepts the same camel-case options. An optional exact `apiKey` validates `x-api-key: <token>`; omission accepts any token.
|
|
92
92
|
|
|
93
93
|
### What can go wrong
|
|
94
94
|
|
|
95
95
|
- **The script runs out** — exhaustion returns a structured HTTP 500; set `--repeat-last` or lengthen the sequence when a run needs more requests.
|
|
96
96
|
- **Random weights without a positive concrete behavior are rejected** — every entry must name an existing behavior and at least one must carry positive weight.
|
|
97
|
-
- **Invalid requests do not consume the script** — wrong methods, paths,
|
|
97
|
+
- **Invalid requests do not consume the script** — wrong methods, paths, API keys, and malformed JSON get ordinary 4xx responses, so a misconfigured client can burn retries without advancing the sequence.
|
|
98
98
|
|
|
99
99
|
-----
|
|
100
100
|
|
|
@@ -108,7 +108,7 @@ This section explains the design of the server; the observable behavior is fully
|
|
|
108
108
|
|
|
109
109
|
### Design
|
|
110
110
|
|
|
111
|
-
The server is built on one rule: each accepted
|
|
111
|
+
The server is built on one rule: each accepted Messages request consumes exactly one behavior from an arrival-ordered FIFO cursor, and the server never retries or interprets harness policy. Validation happens before the cursor advances — only a `POST` whose path ends in `/v1/messages`, with a valid API key when one is configured and a parseable JSON body, consumes the script; everything else receives an ordinary 4xx. `random` entries resolve at request time through a seeded PRNG over the configured weights, so a run is reproducible from its printed seed.
|
|
112
112
|
|
|
113
113
|
### Source map
|
|
114
114
|
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "用于在无提供方密钥的情况下测试 LLM(大语言模型)适配器与恢复策略的可通过脚本控制的
|
|
2
|
+
description: "用于在无提供方密钥的情况下测试 LLM(大语言模型)适配器与恢复策略的可通过脚本控制的 Messages 兼容故障服务器,面向测试作者与演示。"
|
|
3
3
|
kind: "package-library"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ kind: "package-library"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
本包为测试与演示提供可编脚本的
|
|
12
|
+
本包为测试与演示提供可编脚本的 Messages 兼容 HTTP/SSE(Server-Sent Events)端点,使其无需提供方密钥即可检验模型提供方的失败与成功。每个已接受的 `/v1/messages` 请求依次消费下一个脚本行为,包括重置、停滞、畸形分片、限流、服务器错误、补全与工具调用。测试作者可以通过 `pnpm run mock:llm` 运行服务器,也可以调用 `startMockLlmServer`,后者会返回捕获的请求供断言使用。带种子的 `random` 行为支持可复现的混合故障压力运行。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -39,7 +39,7 @@ pnpm run mock:llm \
|
|
|
39
39
|
--partial-text "discard this half"
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
将发布的 DeepSeek
|
|
42
|
+
将发布的 DeepSeek 适配器指向服务器;它会向服务器 `/v1` 根路径下的 `/messages` 发送请求:
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
45
|
DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1 \
|
|
@@ -88,13 +88,13 @@ pnpm run mock:llm \
|
|
|
88
88
|
|
|
89
89
|
### 时序与内容控制
|
|
90
90
|
|
|
91
|
-
CLI 公开 `--success-text`、`--partial-text`、`--reasoning-text`、`--chunk-size`、`--chunk-delay-ms`、`--disconnect-delay-ms`、`--retry-after-ms`、`--request-id`、`--tool-name` 与 `--tool-arguments`。毫秒延迟是 Node 定时器范围内的有界整数;`retryAfterMs` 还必须为正数。库接受相同的 camel-case 选项。可选的 `apiKey` 会精确验证 `
|
|
91
|
+
CLI 公开 `--success-text`、`--partial-text`、`--reasoning-text`、`--chunk-size`、`--chunk-delay-ms`、`--disconnect-delay-ms`、`--retry-after-ms`、`--request-id`、`--tool-name` 与 `--tool-arguments`。毫秒延迟是 Node 定时器范围内的有界整数;`retryAfterMs` 还必须为正数。库接受相同的 camel-case 选项。可选的 `apiKey` 会精确验证 `x-api-key: <token>`;省略时接受任何 token。
|
|
92
92
|
|
|
93
93
|
### 可能出什么问题
|
|
94
94
|
|
|
95
95
|
- **脚本耗尽**——耗尽时返回结构化 HTTP 500;当一次运行需要更多请求时设置 `--repeat-last` 或加长序列。
|
|
96
96
|
- **没有正权重具体行为的随机权重会被拒绝**——每个条目都必须命名现有行为,且至少一个条目带正权重。
|
|
97
|
-
- **无效请求不消费脚本**——错误方法、路径、
|
|
97
|
+
- **无效请求不消费脚本**——错误方法、路径、API key 与畸形 JSON 会收到普通 4xx 响应,因此配置错误的客户端可能耗尽重试却不推进序列。
|
|
98
98
|
|
|
99
99
|
-----
|
|
100
100
|
|
|
@@ -108,7 +108,7 @@ CLI 公开 `--success-text`、`--partial-text`、`--reasoning-text`、`--chunk-s
|
|
|
108
108
|
|
|
109
109
|
### 设计
|
|
110
110
|
|
|
111
|
-
服务器建立在一个规则之上:每个已接受的
|
|
111
|
+
服务器建立在一个规则之上:每个已接受的 Messages 请求从按到达顺序排列的 FIFO 游标消费恰好一个行为,服务器从不重试或解读 harness 策略。校验先于游标推进——只有 `POST` 且路径以 `/v1/messages` 结尾、配置密钥时携带有效 API key、且 JSON 正文可解析的请求才消费脚本;其余请求都收到普通 4xx。`random` 条目在请求时通过带种子的 PRNG 按配置权重解析,因此一次运行可由其打印出的种子复现。
|
|
112
112
|
|
|
113
113
|
### 源码地图
|
|
114
114
|
|
package/lib/index.js
CHANGED
|
@@ -4,8 +4,8 @@ import { isIP } from "node:net";
|
|
|
4
4
|
import { setTimeout } from "node:timers/promises";
|
|
5
5
|
//#region lib/types/index.js
|
|
6
6
|
/**
|
|
7
|
-
* Scriptable
|
|
8
|
-
* semantic-empty LLM recovery tests. Each accepted
|
|
7
|
+
* Scriptable Messages HTTP/SSE server for transport, protocol, and
|
|
8
|
+
* semantic-empty LLM recovery tests. Each accepted Messages request
|
|
9
9
|
* consumes one behavior; the server never retries or interprets harness policy.
|
|
10
10
|
*
|
|
11
11
|
* @module @deepseek-ai/dsh-llm-mock-server
|
|
@@ -153,7 +153,7 @@ function writeSse(record, response, payload) {
|
|
|
153
153
|
record.chunksSent += 1;
|
|
154
154
|
}
|
|
155
155
|
function writeDone(record, response) {
|
|
156
|
-
writeSse(record, response, "
|
|
156
|
+
writeSse(record, response, { type: "message_stop" });
|
|
157
157
|
}
|
|
158
158
|
function finishRecord(options, record, outcome) {
|
|
159
159
|
if (record.outcome !== void 0) return;
|
|
@@ -179,17 +179,40 @@ function httpError(options, record, response, status, message, code, type = "moc
|
|
|
179
179
|
} }));
|
|
180
180
|
finishRecord(options, record, "completed");
|
|
181
181
|
}
|
|
182
|
+
function startMessage(record, response) {
|
|
183
|
+
writeSse(record, response, {
|
|
184
|
+
type: "message_start",
|
|
185
|
+
message: {
|
|
186
|
+
id: "mock-message",
|
|
187
|
+
type: "message",
|
|
188
|
+
role: "assistant",
|
|
189
|
+
model: "mock-model",
|
|
190
|
+
content: [],
|
|
191
|
+
usage: {
|
|
192
|
+
input_tokens: 3,
|
|
193
|
+
output_tokens: 0
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function startText(record, response, index) {
|
|
199
|
+
writeSse(record, response, {
|
|
200
|
+
type: "content_block_start",
|
|
201
|
+
index,
|
|
202
|
+
content_block: {
|
|
203
|
+
type: "text",
|
|
204
|
+
text: ""
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
182
208
|
function terminalChunk(reason, outputTokens) {
|
|
183
209
|
return {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
usage: {
|
|
190
|
-
prompt_tokens: 3,
|
|
191
|
-
completion_tokens: outputTokens
|
|
192
|
-
}
|
|
210
|
+
type: "message_delta",
|
|
211
|
+
delta: {
|
|
212
|
+
stop_reason: reason,
|
|
213
|
+
stop_sequence: null
|
|
214
|
+
},
|
|
215
|
+
usage: { output_tokens: outputTokens }
|
|
193
216
|
};
|
|
194
217
|
}
|
|
195
218
|
async function pause(milliseconds, response) {
|
|
@@ -208,22 +231,30 @@ async function pause(milliseconds, response) {
|
|
|
208
231
|
response.off("close", stop);
|
|
209
232
|
}
|
|
210
233
|
}
|
|
211
|
-
async function streamText(options, record, response, text, delayMs) {
|
|
234
|
+
async function streamText(options, record, response, text, delayMs, index = 0) {
|
|
212
235
|
for (const chunk of splitText(text, options.chunkSize)) {
|
|
213
|
-
writeSse(record, response, {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
236
|
+
writeSse(record, response, {
|
|
237
|
+
type: "content_block_delta",
|
|
238
|
+
index,
|
|
239
|
+
delta: {
|
|
240
|
+
type: "text_delta",
|
|
241
|
+
text: chunk
|
|
242
|
+
}
|
|
243
|
+
});
|
|
218
244
|
if (!await pause(delayMs, response)) return false;
|
|
219
245
|
}
|
|
220
246
|
return true;
|
|
221
247
|
}
|
|
222
|
-
async function completeText(options, record, response, reason, delayMs) {
|
|
223
|
-
|
|
248
|
+
async function completeText(options, record, response, reason, delayMs, index = 0) {
|
|
249
|
+
startText(record, response, index);
|
|
250
|
+
if (!await streamText(options, record, response, options.successText, delayMs, index)) {
|
|
224
251
|
finishRecord(options, record, "client_closed");
|
|
225
252
|
return;
|
|
226
253
|
}
|
|
254
|
+
writeSse(record, response, {
|
|
255
|
+
type: "content_block_stop",
|
|
256
|
+
index
|
|
257
|
+
});
|
|
227
258
|
writeSse(record, response, terminalChunk(reason, Array.from(options.successText).length));
|
|
228
259
|
writeDone(record, response);
|
|
229
260
|
response.end();
|
|
@@ -239,26 +270,30 @@ async function disconnect(options, record, response) {
|
|
|
239
270
|
}
|
|
240
271
|
function toolCallChunks(options) {
|
|
241
272
|
const midpoint = Math.max(1, Math.floor(options.toolArguments.length / 2));
|
|
242
|
-
return [
|
|
243
|
-
|
|
244
|
-
|
|
273
|
+
return [
|
|
274
|
+
{
|
|
275
|
+
type: "content_block_start",
|
|
245
276
|
index: 0,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
277
|
+
content_block: {
|
|
278
|
+
type: "tool_use",
|
|
279
|
+
id: "mock-call-1",
|
|
249
280
|
name: options.toolName,
|
|
250
|
-
|
|
281
|
+
input: {}
|
|
251
282
|
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
index: 0,
|
|
256
|
-
delta: { tool_calls: [{
|
|
283
|
+
},
|
|
284
|
+
...[options.toolArguments.slice(0, midpoint), options.toolArguments.slice(midpoint)].map((partial) => ({
|
|
285
|
+
type: "content_block_delta",
|
|
257
286
|
index: 0,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
287
|
+
delta: {
|
|
288
|
+
type: "input_json_delta",
|
|
289
|
+
partial_json: partial
|
|
290
|
+
}
|
|
291
|
+
})),
|
|
292
|
+
{
|
|
293
|
+
type: "content_block_stop",
|
|
294
|
+
index: 0
|
|
295
|
+
}
|
|
296
|
+
];
|
|
262
297
|
}
|
|
263
298
|
async function runBehavior(options, record, request, response) {
|
|
264
299
|
switch (record.behavior) {
|
|
@@ -275,7 +310,8 @@ async function runBehavior(options, record, request, response) {
|
|
|
275
310
|
return;
|
|
276
311
|
case "empty":
|
|
277
312
|
openSse(response);
|
|
278
|
-
|
|
313
|
+
startMessage(record, response);
|
|
314
|
+
writeSse(record, response, terminalChunk("end_turn", 0));
|
|
279
315
|
writeDone(record, response);
|
|
280
316
|
response.end();
|
|
281
317
|
finishRecord(options, record, "completed");
|
|
@@ -287,22 +323,22 @@ async function runBehavior(options, record, request, response) {
|
|
|
287
323
|
return;
|
|
288
324
|
case "stream_eof":
|
|
289
325
|
openSse(response);
|
|
290
|
-
|
|
291
|
-
index: 0,
|
|
292
|
-
delta: { role: "assistant" },
|
|
293
|
-
finish_reason: null
|
|
294
|
-
}] });
|
|
326
|
+
startMessage(record, response);
|
|
295
327
|
response.end();
|
|
296
328
|
finishRecord(options, record, "completed");
|
|
297
329
|
return;
|
|
298
330
|
case "partial_eof":
|
|
299
331
|
openSse(response);
|
|
332
|
+
startMessage(record, response);
|
|
333
|
+
startText(record, response, 0);
|
|
300
334
|
await streamText(options, record, response, options.partialText, 0);
|
|
301
335
|
response.end();
|
|
302
336
|
finishRecord(options, record, "completed");
|
|
303
337
|
return;
|
|
304
338
|
case "partial_disconnect":
|
|
305
339
|
openSse(response);
|
|
340
|
+
startMessage(record, response);
|
|
341
|
+
startText(record, response, 0);
|
|
306
342
|
if (!await streamText(options, record, response, options.partialText, options.chunkDelayMs)) return;
|
|
307
343
|
await disconnect(options, record, response);
|
|
308
344
|
return;
|
|
@@ -319,14 +355,19 @@ async function runBehavior(options, record, request, response) {
|
|
|
319
355
|
return;
|
|
320
356
|
case "malformed_event":
|
|
321
357
|
openSse(response);
|
|
322
|
-
writeSse(record, response, {
|
|
358
|
+
writeSse(record, response, {
|
|
359
|
+
type: "content_block_start",
|
|
360
|
+
index: 0,
|
|
361
|
+
content_block: null
|
|
362
|
+
});
|
|
323
363
|
writeDone(record, response);
|
|
324
364
|
response.end();
|
|
325
365
|
finishRecord(options, record, "completed");
|
|
326
366
|
return;
|
|
327
367
|
case "wrong_content_type":
|
|
328
368
|
openSse(response, "application/json");
|
|
329
|
-
|
|
369
|
+
startMessage(record, response);
|
|
370
|
+
await completeText(options, record, response, "end_turn", 0);
|
|
330
371
|
return;
|
|
331
372
|
case "rate_limit":
|
|
332
373
|
httpError(options, record, response, 429, "mock rate limit", "rate_limit");
|
|
@@ -351,32 +392,52 @@ async function runBehavior(options, record, request, response) {
|
|
|
351
392
|
return;
|
|
352
393
|
case "success":
|
|
353
394
|
openSse(response);
|
|
354
|
-
|
|
395
|
+
startMessage(record, response);
|
|
396
|
+
await completeText(options, record, response, "end_turn", 0);
|
|
355
397
|
return;
|
|
356
398
|
case "reasoning_success":
|
|
357
399
|
openSse(response);
|
|
358
|
-
|
|
400
|
+
startMessage(record, response);
|
|
401
|
+
writeSse(record, response, {
|
|
402
|
+
type: "content_block_start",
|
|
403
|
+
index: 0,
|
|
404
|
+
content_block: {
|
|
405
|
+
type: "thinking",
|
|
406
|
+
thinking: ""
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
for (const chunk of splitText(options.reasoningText, options.chunkSize)) writeSse(record, response, {
|
|
410
|
+
type: "content_block_delta",
|
|
359
411
|
index: 0,
|
|
360
|
-
delta: {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
412
|
+
delta: {
|
|
413
|
+
type: "thinking_delta",
|
|
414
|
+
thinking: chunk
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
writeSse(record, response, {
|
|
418
|
+
type: "content_block_stop",
|
|
419
|
+
index: 0
|
|
420
|
+
});
|
|
421
|
+
await completeText(options, record, response, "end_turn", 0, 1);
|
|
364
422
|
return;
|
|
365
423
|
case "tool_call_success":
|
|
366
424
|
openSse(response);
|
|
425
|
+
startMessage(record, response);
|
|
367
426
|
for (const chunk of toolCallChunks(options)) writeSse(record, response, chunk);
|
|
368
|
-
writeSse(record, response, terminalChunk("
|
|
427
|
+
writeSse(record, response, terminalChunk("tool_use", 2));
|
|
369
428
|
writeDone(record, response);
|
|
370
429
|
response.end();
|
|
371
430
|
finishRecord(options, record, "completed");
|
|
372
431
|
return;
|
|
373
432
|
case "max_tokens":
|
|
374
433
|
openSse(response);
|
|
375
|
-
|
|
434
|
+
startMessage(record, response);
|
|
435
|
+
await completeText(options, record, response, "max_tokens", 0);
|
|
376
436
|
return;
|
|
377
437
|
case "slow_success":
|
|
378
438
|
openSse(response);
|
|
379
|
-
|
|
439
|
+
startMessage(record, response);
|
|
440
|
+
await completeText(options, record, response, "end_turn", options.chunkDelayMs);
|
|
380
441
|
return;
|
|
381
442
|
}
|
|
382
443
|
}
|
|
@@ -401,9 +462,9 @@ function chooseRandomBehavior(weights, random) {
|
|
|
401
462
|
return weights.at(-1)[0];
|
|
402
463
|
}
|
|
403
464
|
/**
|
|
404
|
-
* Start a local
|
|
405
|
-
* per accepted request. Only a `POST` path ending in `/
|
|
406
|
-
* invalid routes, methods,
|
|
465
|
+
* Start a local Messages server that consumes one configured behavior
|
|
466
|
+
* per accepted request. Only a `POST` path ending in `/v1/messages` consumes the script;
|
|
467
|
+
* invalid routes, methods, API keys, and JSON receive ordinary 4xx
|
|
407
468
|
* responses. Closing the handle terminates stalled connections.
|
|
408
469
|
*
|
|
409
470
|
* @param options - listener, script, response content, timing, and telemetry options.
|
|
@@ -430,14 +491,14 @@ async function startMockLlmServer(options) {
|
|
|
430
491
|
response.writeHead(405, { allow: "POST" }).end();
|
|
431
492
|
return;
|
|
432
493
|
}
|
|
433
|
-
if (!path.endsWith("/
|
|
494
|
+
if (!path.endsWith("/v1/messages")) {
|
|
434
495
|
response.writeHead(404).end();
|
|
435
496
|
return;
|
|
436
497
|
}
|
|
437
|
-
if (resolved.apiKey !== void 0 && request.headers
|
|
498
|
+
if (resolved.apiKey !== void 0 && request.headers["x-api-key"] !== resolved.apiKey) {
|
|
438
499
|
response.writeHead(401, { "content-type": "application/json" });
|
|
439
500
|
response.end(JSON.stringify({ error: {
|
|
440
|
-
message: "invalid mock
|
|
501
|
+
message: "invalid mock API key",
|
|
441
502
|
code: "invalid_api_key"
|
|
442
503
|
} }));
|
|
443
504
|
return;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scriptable
|
|
3
|
-
* semantic-empty LLM recovery tests. Each accepted
|
|
2
|
+
* Scriptable Messages HTTP/SSE server for transport, protocol, and
|
|
3
|
+
* semantic-empty LLM recovery tests. Each accepted Messages request
|
|
4
4
|
* consumes one behavior; the server never retries or interprets harness policy.
|
|
5
5
|
*
|
|
6
6
|
* @module @deepseek-ai/dsh-llm-mock-server
|
|
@@ -40,7 +40,7 @@ export type MockLlmServerEvent = {
|
|
|
40
40
|
};
|
|
41
41
|
/** Captured wire request and its final server-side outcome. */
|
|
42
42
|
export interface MockLlmRequestRecord {
|
|
43
|
-
/** One-based accepted
|
|
43
|
+
/** One-based accepted Messages request number. */
|
|
44
44
|
readonly attempt: number;
|
|
45
45
|
/** Script entry consumed for this request before random resolution. */
|
|
46
46
|
readonly scriptBehavior: MockLlmBehavior | 'script_exhausted';
|
|
@@ -63,7 +63,7 @@ export interface MockLlmServerOptions {
|
|
|
63
63
|
readonly host?: string;
|
|
64
64
|
/** TCP port; zero requests an OS-assigned port. */
|
|
65
65
|
readonly port?: number;
|
|
66
|
-
/** Optional exact
|
|
66
|
+
/** Optional exact API key; omission accepts any x-api-key header. */
|
|
67
67
|
readonly apiKey?: string;
|
|
68
68
|
/** Ordered request behaviors; exhaustion fails loud unless `repeatLast` is true. */
|
|
69
69
|
readonly sequence: readonly MockLlmBehavior[];
|
|
@@ -98,7 +98,7 @@ export interface MockLlmServerOptions {
|
|
|
98
98
|
}
|
|
99
99
|
/** Running mock server and captured request state. */
|
|
100
100
|
export interface MockLlmServer {
|
|
101
|
-
/** Base URL without `/v1`;
|
|
101
|
+
/** Base URL without `/v1`; the endpoint is `/v1/messages`. */
|
|
102
102
|
readonly baseURL: string;
|
|
103
103
|
/** Actual bound port, including an OS-assigned value. */
|
|
104
104
|
readonly port: number;
|
|
@@ -110,9 +110,9 @@ export interface MockLlmServer {
|
|
|
110
110
|
close(): Promise<void>;
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
|
-
* Start a local
|
|
114
|
-
* per accepted request. Only a `POST` path ending in `/
|
|
115
|
-
* invalid routes, methods,
|
|
113
|
+
* Start a local Messages server that consumes one configured behavior
|
|
114
|
+
* per accepted request. Only a `POST` path ending in `/v1/messages` consumes the script;
|
|
115
|
+
* invalid routes, methods, API keys, and JSON receive ordinary 4xx
|
|
116
116
|
* responses. Closing the handle terminates stalled connections.
|
|
117
117
|
*
|
|
118
118
|
* @param options - listener, script, response content, timing, and telemetry options.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-llm-mock-server",
|
|
3
|
-
"description": "Scriptable
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "Scriptable Messages HTTP/SSE fault server for LLM recovery tests",
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
30
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
33
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
34
34
|
}
|
|
35
35
|
}
|