vite-plugin-ai-mock 0.1.0
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/LICENSE +21 -0
- package/README.md +207 -0
- package/README.zh-CN.md +207 -0
- package/dist/index.cjs +350 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +315 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# vite-plugin-ai-mock
|
|
2
|
+
|
|
3
|
+
> English | [中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
A standalone Vite plugin for AI scene mocking. Returns streaming data in JSON format, simulating various AI scenarios.
|
|
6
|
+
|
|
7
|
+
- Reads mock files from `mock/ai/*.json`
|
|
8
|
+
- Auto returns SSE when request is SSE (`Accept: text/event-stream` or `?transport=sse`)
|
|
9
|
+
- Returns JSON for non-SSE calls
|
|
10
|
+
- Supports 11 streaming scenarios with request parameters
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i vite-plugin-ai-mock -D
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { defineConfig } from "vite";
|
|
22
|
+
import { aiMockPlugin } from "vite-plugin-ai-mock";
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
plugins: [
|
|
26
|
+
aiMockPlugin({
|
|
27
|
+
dataDir: "mock/ai",
|
|
28
|
+
endpoint: "/api/ai/mock",
|
|
29
|
+
}),
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Scenarios (11)
|
|
35
|
+
|
|
36
|
+
1. Normal completion (default)
|
|
37
|
+
2. First chunk delay: `firstChunkDelayMs=1800` or `scenario=first-delay`
|
|
38
|
+
3. Jitter: `minIntervalMs=100&maxIntervalMs=1500` or `scenario=jitter`
|
|
39
|
+
4. Mid-stream disconnect: `disconnectAt=3` or `scenario=disconnect`
|
|
40
|
+
5. Timeout/no response: `stallAfter=2&stallMs=30000` or `scenario=timeout`
|
|
41
|
+
6. Stream error event: `errorAt=2&errorMessage=xxx` or `scenario=error`
|
|
42
|
+
7. Malformed chunk: `malformedAt=2` or `scenario=malformed`
|
|
43
|
+
8. Duplicate chunk: `duplicateAt=2` or `scenario=duplicate`
|
|
44
|
+
9. User cancel: client aborts request (server handles `close`)
|
|
45
|
+
10. Reconnect/resume: `reconnect=true&lastEventId=1` or `scenario=reconnect`
|
|
46
|
+
11. Heartbeat: `heartbeatMs=2500` or `scenario=heartbeat`
|
|
47
|
+
|
|
48
|
+
Extra:
|
|
49
|
+
|
|
50
|
+
- HTTP error injection: `httpErrorStatus=401`
|
|
51
|
+
- Skip done event: `includeDone=false`
|
|
52
|
+
|
|
53
|
+
## Scenario Configuration
|
|
54
|
+
|
|
55
|
+
Scenarios can be configured in two ways, in order of precedence:
|
|
56
|
+
|
|
57
|
+
**1. URL parameters (per-request)**
|
|
58
|
+
|
|
59
|
+
Append parameters directly to the request URL, useful for debugging a single endpoint:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
/api/ai/mock/default?scenario=jitter
|
|
63
|
+
/api/ai/mock/default?firstChunkDelayMs=1000&errorAt=3
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**2. Plugin option `defaultScenario` (global)**
|
|
67
|
+
|
|
68
|
+
Configure in `vite.config.ts` to apply to all mock requests. URL parameters take precedence and override this:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
aiMockPlugin({
|
|
72
|
+
defaultScenario: {
|
|
73
|
+
scenario: "jitter", // Use a preset scenario name
|
|
74
|
+
// Individual options override the preset's corresponding values:
|
|
75
|
+
firstChunkDelayMs: 500,
|
|
76
|
+
minIntervalMs: 100,
|
|
77
|
+
maxIntervalMs: 800,
|
|
78
|
+
errorAt: 3,
|
|
79
|
+
errorMessage: "custom_error",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
When `defaultScenario` is not set, the `normal` scenario is used (no delay, completes normally).
|
|
85
|
+
|
|
86
|
+
## Mock file format
|
|
87
|
+
|
|
88
|
+
Each file is a JSON object with a `chunks` array. Every chunk maps to one SSE event:
|
|
89
|
+
|
|
90
|
+
| Field | Type | Description |
|
|
91
|
+
| --------- | ------ | --------------------------------------------------------------- |
|
|
92
|
+
| `id` | string | SSE `id:` field, used for reconnect resume |
|
|
93
|
+
| `event` | string | SSE `event:` field (default: `message`) |
|
|
94
|
+
| `data` | any | Payload — object/array is JSON-serialized, string is sent as-is |
|
|
95
|
+
| `delayMs` | number | Per-chunk delay override (ms) |
|
|
96
|
+
|
|
97
|
+
`default.json` is loaded when no file is specified. You can customize it with any `data` shape to match your own API format:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"chunks": [
|
|
102
|
+
{ "id": "1", "event": "message", "data": { "delta": "Hello" } },
|
|
103
|
+
{ "id": "2", "event": "message", "data": { "delta": ", " } },
|
|
104
|
+
{ "id": "3", "event": "message", "data": { "delta": "world!" } }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Real-world format examples
|
|
110
|
+
|
|
111
|
+
The `data` field can mirror any real API response. Built-in examples are available in `mock/ai/`:
|
|
112
|
+
|
|
113
|
+
**OpenAI / compatible** (`openai.json`) — `data` ends with `"[DONE]"` string:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"id": "1",
|
|
118
|
+
"event": "message",
|
|
119
|
+
"data": {
|
|
120
|
+
"id": "chatcmpl-001",
|
|
121
|
+
"object": "chat.completion.chunk",
|
|
122
|
+
"choices": [
|
|
123
|
+
{ "index": 0, "delta": { "content": "Hello" }, "finish_reason": null }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Anthropic Claude** (`claude.json`) — uses distinct `event` types:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"id": "3",
|
|
134
|
+
"event": "content_block_delta",
|
|
135
|
+
"data": {
|
|
136
|
+
"type": "content_block_delta",
|
|
137
|
+
"index": 0,
|
|
138
|
+
"delta": { "type": "text_delta", "text": "Hello" }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Google Gemini** (`gemini.json`) — all events share the same `message` event type:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"id": "1",
|
|
148
|
+
"event": "message",
|
|
149
|
+
"data": {
|
|
150
|
+
"candidates": [
|
|
151
|
+
{
|
|
152
|
+
"content": { "parts": [{ "text": "Hello" }], "role": "model" },
|
|
153
|
+
"finishReason": null
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Endpoint
|
|
161
|
+
|
|
162
|
+
`endpoint` accepts a `string`, `RegExp`, or `(string | RegExp)[]`.
|
|
163
|
+
|
|
164
|
+
| Type | `fileFromPath` extraction |
|
|
165
|
+
| ---------------------- | ------------------------------------------------------------------ |
|
|
166
|
+
| `string` | Strips the path prefix |
|
|
167
|
+
| `RegExp` | Always `""`, relies on `?file=` |
|
|
168
|
+
| `(string \| RegExp)[]` | Tries each in order; first match wins and follows its type's rules |
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// string (default)
|
|
172
|
+
endpoint: "/api/ai/mock";
|
|
173
|
+
// /api/ai/mock → file = "default"
|
|
174
|
+
// /api/ai/mock/chat → file = "chat"
|
|
175
|
+
// /api/ai/mock/deepseek → file = "deepseek"
|
|
176
|
+
|
|
177
|
+
// RegExp
|
|
178
|
+
endpoint: /^\/api\/ai\/.*/;
|
|
179
|
+
// relies on ?file= to pick the mock file
|
|
180
|
+
|
|
181
|
+
// array
|
|
182
|
+
endpoint: ["/api/chat", /^\/v2\/ai\/.*/];
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- `/api/ai/mock`
|
|
186
|
+
- `/api/ai/mock/<file>`
|
|
187
|
+
- `?file=<file>`
|
|
188
|
+
|
|
189
|
+
## Test
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npm test
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Build
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npm run build
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Publish
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npm run release:npm
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`prepublishOnly` will automatically run build, tests and typecheck..
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# vite-plugin-ai-mock
|
|
2
|
+
|
|
3
|
+
一个用于 AI 场景模拟的独立 Vite 插件。可以返回 JSON 格式的流式数据,模拟不同的 AI 场景。
|
|
4
|
+
|
|
5
|
+
> [English](./README.md) | 中文
|
|
6
|
+
|
|
7
|
+
- 从 `mock/ai/*.json` 读取 mock 文件
|
|
8
|
+
- 当请求为 SSE(`Accept: text/event-stream` 或 `?transport=sse`)时自动返回 SSE
|
|
9
|
+
- 非 SSE 请求返回 JSON
|
|
10
|
+
- 支持 11 种流式场景,通过请求参数控制
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i vite-plugin-ai-mock -D
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 使用
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { defineConfig } from "vite";
|
|
22
|
+
import { aiMockPlugin } from "vite-plugin-ai-mock";
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
plugins: [
|
|
26
|
+
aiMockPlugin({
|
|
27
|
+
dataDir: "mock/ai",
|
|
28
|
+
endpoint: "/api/ai/mock",
|
|
29
|
+
}),
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 场景(11 种)
|
|
35
|
+
|
|
36
|
+
1. 正常完成(默认)
|
|
37
|
+
2. 首包延迟:`firstChunkDelayMs=1800` 或 `scenario=first-delay`
|
|
38
|
+
3. 抖动:`minIntervalMs=100&maxIntervalMs=1500` 或 `scenario=jitter`
|
|
39
|
+
4. 中途断开:`disconnectAt=3` 或 `scenario=disconnect`
|
|
40
|
+
5. 超时/无响应:`stallAfter=2&stallMs=30000` 或 `scenario=timeout`
|
|
41
|
+
6. 流错误事件:`errorAt=2&errorMessage=xxx` 或 `scenario=error`
|
|
42
|
+
7. 格式错误的数据块:`malformedAt=2` 或 `scenario=malformed`
|
|
43
|
+
8. 重复数据块:`duplicateAt=2` 或 `scenario=duplicate`
|
|
44
|
+
9. 用户取消:客户端中止请求(服务端处理 `close`)
|
|
45
|
+
10. 重连/续传:`reconnect=true&lastEventId=1` 或 `scenario=reconnect`
|
|
46
|
+
11. 心跳:`heartbeatMs=2500` 或 `scenario=heartbeat`
|
|
47
|
+
|
|
48
|
+
额外参数:
|
|
49
|
+
|
|
50
|
+
- HTTP 错误注入:`httpErrorStatus=401`
|
|
51
|
+
- 跳过完成事件:`includeDone=false`
|
|
52
|
+
|
|
53
|
+
## 场景配置
|
|
54
|
+
|
|
55
|
+
场景有两种配置方式,优先级由高到低:
|
|
56
|
+
|
|
57
|
+
**1. URL 参数(仅对当前请求生效)**
|
|
58
|
+
|
|
59
|
+
直接在请求 URL 上附加参数,适合临时调试单个接口:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
/api/ai/mock/default?scenario=jitter
|
|
63
|
+
/api/ai/mock/default?firstChunkDelayMs=1000&errorAt=3
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**2. 插件选项 `defaultScenario`(全局生效)**
|
|
67
|
+
|
|
68
|
+
在 `vite.config.ts` 中配置,对所有 mock 请求生效,可被 URL 参数覆盖:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
aiMockPlugin({
|
|
72
|
+
defaultScenario: {
|
|
73
|
+
scenario: "jitter", // 使用预设场景名
|
|
74
|
+
// 也可单独配置参数,会覆盖预设场景的对应值:
|
|
75
|
+
firstChunkDelayMs: 500,
|
|
76
|
+
minIntervalMs: 100,
|
|
77
|
+
maxIntervalMs: 800,
|
|
78
|
+
errorAt: 3,
|
|
79
|
+
errorMessage: "custom_error",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
不配置 `defaultScenario` 时,默认使用 `normal` 场景(无延迟,正常完成)。
|
|
85
|
+
|
|
86
|
+
## Mock 文件格式
|
|
87
|
+
|
|
88
|
+
每个文件是一个包含 `chunks` 数组的 JSON 对象,每个 chunk 对应一条 SSE 事件:
|
|
89
|
+
|
|
90
|
+
| 字段 | 类型 | 说明 |
|
|
91
|
+
| --------- | ------ | --------------------------------------------------- |
|
|
92
|
+
| `id` | string | SSE `id:` 字段,用于断线重连续传 |
|
|
93
|
+
| `event` | string | SSE `event:` 字段(默认:`message`) |
|
|
94
|
+
| `data` | any | 数据载体——对象/数组会被 JSON 序列化,字符串原样发送 |
|
|
95
|
+
| `delayMs` | number | 单个 chunk 的延迟时间覆盖(毫秒) |
|
|
96
|
+
|
|
97
|
+
`default.json` 是未指定文件时加载的默认文件,`data` 字段可自由定义,用于匹配你自己的 API 格式:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"chunks": [
|
|
102
|
+
{ "id": "1", "event": "message", "data": { "delta": "Hello" } },
|
|
103
|
+
{ "id": "2", "event": "message", "data": { "delta": ", " } },
|
|
104
|
+
{ "id": "3", "event": "message", "data": { "delta": "world!" } }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 主流格式示例
|
|
110
|
+
|
|
111
|
+
`data` 字段可以完整模拟真实 API 的响应结构,内置示例文件位于 `mock/ai/`:
|
|
112
|
+
|
|
113
|
+
**OpenAI / 兼容格式**(`openai.json`)——最后一条 `data` 为字符串 `"[DONE]"`:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"id": "1",
|
|
118
|
+
"event": "message",
|
|
119
|
+
"data": {
|
|
120
|
+
"id": "chatcmpl-001",
|
|
121
|
+
"object": "chat.completion.chunk",
|
|
122
|
+
"choices": [
|
|
123
|
+
{ "index": 0, "delta": { "content": "Hello" }, "finish_reason": null }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Anthropic Claude**(`claude.json`)——通过不同 `event` 类型区分生命周期:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"id": "3",
|
|
134
|
+
"event": "content_block_delta",
|
|
135
|
+
"data": {
|
|
136
|
+
"type": "content_block_delta",
|
|
137
|
+
"index": 0,
|
|
138
|
+
"delta": { "type": "text_delta", "text": "Hello" }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Google Gemini**(`gemini.json`)——所有事件统一使用 `message` 事件类型:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"id": "1",
|
|
148
|
+
"event": "message",
|
|
149
|
+
"data": {
|
|
150
|
+
"candidates": [
|
|
151
|
+
{
|
|
152
|
+
"content": { "parts": [{ "text": "Hello" }], "role": "model" },
|
|
153
|
+
"finishReason": null
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## 接口地址
|
|
161
|
+
|
|
162
|
+
`endpoint` 支持 `string`、`RegExp` 或 `(string | RegExp)[]`。
|
|
163
|
+
|
|
164
|
+
| 类型 | `fileFromPath` 提取方式 |
|
|
165
|
+
| ---------------------- | -------------------------------------- |
|
|
166
|
+
| `string` | 去掉路径前缀 |
|
|
167
|
+
| `RegExp` | 始终为 `""`,依赖 `?file=` 参数 |
|
|
168
|
+
| `(string \| RegExp)[]` | 按序匹配,第一个命中者按其类型规则处理 |
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// string(默认)
|
|
172
|
+
endpoint: "/api/ai/mock";
|
|
173
|
+
// /api/ai/mock → file = "default"
|
|
174
|
+
// /api/ai/mock/chat → file = "chat"
|
|
175
|
+
// /api/ai/mock/deepseek → file = "deepseek"
|
|
176
|
+
|
|
177
|
+
// RegExp
|
|
178
|
+
endpoint: /^\/api\/ai\/.*/;
|
|
179
|
+
// 依赖 ?file= 参数选取 mock 文件
|
|
180
|
+
|
|
181
|
+
// 数组
|
|
182
|
+
endpoint: ["/api/chat", /^\/v2\/ai\/.*/];
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- `/api/ai/mock`
|
|
186
|
+
- `/api/ai/mock/<file>`
|
|
187
|
+
- `?file=<file>`
|
|
188
|
+
|
|
189
|
+
## 测试
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npm test
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 构建
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npm run build
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 发布
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npm run release:npm
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`prepublishOnly` 会自动执行构建、测试和类型检查。
|