my-ai-chat-framework 2.0.0 → 3.0.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/CHANGELOG.md +65 -0
- package/LICENSE +20 -20
- package/README.md +329 -45
- package/README_ZH.md +375 -0
- package/dist/my-ai-chat-framework.browser.es.js +1581 -207
- package/dist/my-ai-chat-framework.browser.es.js.map +1 -1
- package/dist/my-ai-chat-framework.browser.umd.js +1594 -211
- package/dist/my-ai-chat-framework.browser.umd.js.map +1 -1
- package/dist/my-ai-chat-framework.node.cjs.js +1594 -211
- package/dist/my-ai-chat-framework.node.cjs.js.map +1 -1
- package/docs/DEVELOPER.md +479 -0
- package/package.json +26 -3
- package/src/adapters/openai.js +289 -64
- package/src/core/ChatService.js +652 -110
- package/src/core/Errors.js +60 -0
- package/src/core/EventEmitter.js +19 -0
- package/src/core/MessageStore.js +66 -0
- package/src/core/Pipeline.js +48 -0
- package/src/core/SystemPromptStore.js +118 -0
- package/src/index.js +12 -14
- package/src/plugins/model-registry.js +223 -0
- package/src/plugins/tool-calling.js +229 -105
- package/src/utils/MessageFormatter.js +204 -0
- package/src/utils/typeCheck.js +12 -0
- package/src/utils/url.js +18 -0
- package/.env +0 -15
- package/test.js +0 -107
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [3.0.0] - 2026-08-?
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **公开管道 API**:`chat.pipe({ name, phase?, run(ctx) })` / `chat.unpipe(name)` / `chat.pipelineStages`
|
|
9
|
+
- phase `beforeSend`(默认):内部 beforeRequest 之后、构建请求体之前
|
|
10
|
+
- phase `afterSend`:发送完成、结果落位之后(可改 ctx.result)
|
|
11
|
+
- 不注册任何车间 = 行为与 2.8.x 完全一致
|
|
12
|
+
- **协议固化**:`assertAdapter` 校验 buildRequest/send/stream/parseResponse,缺方法抛 ConfigurationError 并列出方法名;JSDoc @typedef ChatAdapter / PipelineContext / PipeStage
|
|
13
|
+
- **插件车间化**:tool-calling 经 afterSend 车间、model-registry 经 beforeSend 车间,不再占用 `_processResponse` 单座槽位
|
|
14
|
+
- 新增测试 `tests/pipeline.test.js`(8 例:pipe 顺序/冲突/unpipe/改ctx/SSE 冒烟/工具循环端到端)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- `ChatService._request` 固定线拆为内部管道:prepareInput → beforeSend → autoContinue → buildRequest → send(行为等价)
|
|
18
|
+
- `_hooks` beforeRequest 保留为兼容桥;`_processResponse` 保留为旧通道(新代码请用 afterSend 车间)
|
|
19
|
+
- 版本号 2.8.0 → 3.0.0(新增公共 API 面)
|
|
20
|
+
|
|
21
|
+
### Known delta(流式 tool-calling 时序)
|
|
22
|
+
- 流式工具调用时,初始回复的 `message` 事件在工具循环前发出(旧版循环后发)。非流式时序与旧版一致。
|
|
23
|
+
|
|
24
|
+
## [2.8.0] - 2026-08-08
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **配置归属系统**:配置按层归属(运输/请求/会话/插件),谁消费谁声明
|
|
28
|
+
- **工厂函数**:`createOpenAIAdapter(opts)` / `createToolCallingPlugin(opts)` / `createModelRegistryPlugin(opts)`——每个实例自持状态,规避模块级单例互踩陷阱
|
|
29
|
+
- **请求级参数覆盖**:`chat.send(input, params)` / `chat.stream(input, params, onProgress, onDone)` / `sendExisting(params)` / `sendExistingStream(params, ...)`,合并链 = 适配器默认 ← 会话配置 ← 本次覆盖(近者优先)
|
|
30
|
+
- **capabilities 按生效 model 解析**:model-registry 在 beforeRequest 钩子里按"本次请求的 model"(含请求级覆盖)查表
|
|
31
|
+
- `chat.use(plugin, options)`:options 透传 `plugin.install(chat, options)`
|
|
32
|
+
- `new ChatService({ adapter })`:构造时直接注入适配器
|
|
33
|
+
- 工具定义移入插件实例(不再写 `chat.config.tools`),工具超时改走 `createToolCallingPlugin({ timeout })`
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- `updateConfig` 收窄为会话层字段(model/temperature/maxTokens/modelParams/system/retry/ephemeralContinue),非白名单或非法值抛 `ConfigurationError`(修复绕过校验问题)
|
|
37
|
+
- openaiAdapter 的 transport(apiKey/baseUrl/headers)从实例自持,单例 `openaiAdapter` 保留为平铺兼容路径
|
|
38
|
+
- `buildRequest(messages, config, systemPrompts)` 的 config 现为合并后的请求参数
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- TODO.md 高危 #1:插件共享可变状态——工厂化后实例隔离(单例保留为兼容路径)
|
|
42
|
+
- TODO.md 高危 #3:updateConfig 绕过校验——现在白名单 + 校验
|
|
43
|
+
|
|
44
|
+
## [2.7.0] - 2026-05-25
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- **Ephemeral 临时消息**:_ephemeral: true 标记,仅底部连续时参与请求,其余自动过滤
|
|
48
|
+
- **续写转正**:continueLast / continueLastStream 续写 ephemeral 消息后自动转正(delete _ephemeral)
|
|
49
|
+
- **EventEmitter.emitAsync**:异步触发事件,支持 beforeRequest 等钩子
|
|
50
|
+
- **beforeRequest 钩子**:chat._hooks.on('beforeRequest', ...) 在 buildRequest 前修改 messages
|
|
51
|
+
- **_processResponse 钩子**:tool-calling 改为注册此钩子,不再覆盖 send/stream
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
- MessageFormatter 过滤 ephemeral:识别底部连续段,中间 ephemeral 自动跳过
|
|
55
|
+
- ChatService._request 拆为 4 个私有方法(_addUserMessage / _withRetry / _stream / _handleResult)
|
|
56
|
+
- ool-calling.js 改为注册 _processResponse,不再替换 send/stream
|
|
57
|
+
- dapter.stream 不再接收/操作 streamEntry,_complete 由 ChatService 管理
|
|
58
|
+
- openaiAdapter.stream 约减 20 行(删除所有 entry 相关代码)
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- retry 循环重复 push 占位消息(已修复:提到循环外)
|
|
62
|
+
- continueLastStream 被续写消息丢失(已修复:独立占位,ChatService 管理)
|
|
63
|
+
- prefix 续写空 content 被过滤(已修复:MessageFormatter 保留 prefix)
|
|
64
|
+
|
|
65
|
+
## [2.6.0] - 2026-05-23
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 My Name
|
|
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
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 My Name
|
|
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
21
|
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,19 +1,66 @@
|
|
|
1
1
|
|
|
2
|
-
**⚠️ Note**: This project is created for **learning purposes** and is **AI‑generated**. It is not intended for production use. No backward compatibility is guaranteed. Use at your own risk.
|
|
3
|
-
|
|
4
2
|
# 🤖 My AI Chat Framework
|
|
5
3
|
|
|
6
4
|
A lightweight, modular AI chat framework with plugin system, unified message format, and tool calling support.
|
|
7
5
|
|
|
6
|
+
> **⚠️ Note**: This project is created for **learning purposes** and is **AI‑generated**. It is not intended for production use. No backward compatibility is guaranteed. Use at your own risk.
|
|
7
|
+
|
|
8
8
|
## ✨ Features
|
|
9
9
|
|
|
10
|
-
- **Lightweight Core** –
|
|
11
|
-
- **Plugin System** – Add features
|
|
10
|
+
- **Lightweight Core** – ~300 lines, easy to understand and extend.
|
|
11
|
+
- **Plugin System** – Add features (tool calling, reasoning, custom adapters) without touching the core.
|
|
12
12
|
- **Unified Message Format** – Consistent data structure across all components.
|
|
13
|
-
- **Multi‑Environment** – Builds ES module, UMD, and CommonJS
|
|
13
|
+
- **Multi‑Environment** – Builds ES module, UMD, and CommonJS for browser & Node.js.
|
|
14
14
|
- **No External Dependencies** – Uses native `fetch` (Node 18+ & modern browsers).
|
|
15
15
|
- **Tool Calling** – Built‑in plugin to handle function calls from AI models.
|
|
16
16
|
- **Streaming** – Full support for real‑time responses.
|
|
17
|
+
- **Flexible Configuration** – Supports both flat and nested `modelParams` structure.
|
|
18
|
+
- **Event‑Driven** – Built‑in EventEmitter for `message`, `sending`, `error`, `stream-progress` events.
|
|
19
|
+
- **Pipeline Stages (v3.0)** – `chat.pipe()` to hook custom logic at `beforeSend` / `afterSend` without touching the core.
|
|
20
|
+
- **Custom Error Classes** – `APIError`, `NetworkError`, `ConfigurationError`, `ParsingError` for fine‑grained error handling.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🧱 Architecture
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
src/
|
|
28
|
+
├── index.js # Public entry point (re-exports)
|
|
29
|
+
├── core/
|
|
30
|
+
│ ├── ChatService.js # Main service: config, pipeline, send/stream, plugin hosting
|
|
31
|
+
│ ├── Pipeline.js # Sequential pipeline (v3.0: ctx flows through stages)
|
|
32
|
+
│ ├── MessageStore.js # In-memory message list with CRUD helpers
|
|
33
|
+
│ ├── SystemPromptStore.js # Toggleable system prompts
|
|
34
|
+
│ ├── EventEmitter.js # Minimal pub/sub (on/off/emit)
|
|
35
|
+
│ └── Errors.js # Custom error classes
|
|
36
|
+
├── adapters/
|
|
37
|
+
│ └── openai.js # OpenAI‑compatible API adapter (protocol via assertAdapter)
|
|
38
|
+
├── plugins/
|
|
39
|
+
│ ├── tool-calling.js # Tool calling plugin (afterSend stage, auto‑detect & loop)
|
|
40
|
+
│ └── model-registry.js # Model capability table (beforeSend stage)
|
|
41
|
+
└── utils/
|
|
42
|
+
├── MessageFormatter.js # Message format conversion (registrable)
|
|
43
|
+
├── typeCheck.js # Type checking helpers
|
|
44
|
+
└── url.js # URL joining utility
|
|
45
|
+
tests/ # node:test suites (core / pipeline / integration)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Data flow**:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
User calls chat.send(input)
|
|
52
|
+
→ ChatService._request() → ctx flows through stages:
|
|
53
|
+
→ prepareInput (add user msg, merge config, emit 'sending')
|
|
54
|
+
→ beforeSend (internal hook + user beforeSend stages)
|
|
55
|
+
→ autoContinue / buildRequest (→ request body)
|
|
56
|
+
→ send (adapter.send / stream + retry + placeholder)
|
|
57
|
+
→ afterSend (user stages + tool-calling loop)
|
|
58
|
+
→ returns result, emitting 'message' along the way
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
With `toolCallingPlugin`, the flow loops: response → detect tool_calls → execute tools → add tool results → sendExisting → repeat (max 5 iterations).
|
|
62
|
+
|
|
63
|
+
---
|
|
17
64
|
|
|
18
65
|
## 📦 Installation
|
|
19
66
|
|
|
@@ -21,72 +68,303 @@ A lightweight, modular AI chat framework with plugin system, unified message for
|
|
|
21
68
|
npm install my-ai-chat-framework
|
|
22
69
|
```
|
|
23
70
|
|
|
71
|
+
---
|
|
72
|
+
|
|
24
73
|
## 🚀 Quick Start
|
|
25
74
|
|
|
75
|
+
### Basic Usage (Flat Configuration)
|
|
76
|
+
|
|
26
77
|
```javascript
|
|
27
78
|
import { ChatService, openaiAdapter, toolCallingPlugin } from 'my-ai-chat-framework';
|
|
28
79
|
|
|
29
80
|
const chat = new ChatService({
|
|
30
81
|
apiKey: 'your-api-key',
|
|
82
|
+
baseUrl: 'https://api.deepseek.com', // optional, defaults to OpenAI
|
|
31
83
|
model: 'deepseek-chat',
|
|
32
|
-
|
|
84
|
+
temperature: 0.7,
|
|
85
|
+
maxTokens: 2000
|
|
33
86
|
});
|
|
34
87
|
|
|
35
88
|
chat.use(openaiAdapter);
|
|
36
89
|
chat.use(toolCallingPlugin);
|
|
37
90
|
|
|
38
|
-
chat.
|
|
39
|
-
|
|
91
|
+
chat.on('message', msg => console.log(msg.content));
|
|
92
|
+
await chat.send('Hello!');
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Factory Mode (Owned Configuration, Recommended)
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
import { ChatService, createOpenAIAdapter, createToolCallingPlugin } from 'my-ai-chat-framework';
|
|
99
|
+
|
|
100
|
+
// transport + request defaults belong to the adapter
|
|
101
|
+
const adapter = createOpenAIAdapter({
|
|
102
|
+
apiKey: 'your-api-key',
|
|
103
|
+
baseUrl: 'https://api.deepseek.com',
|
|
104
|
+
modelParams: { temperature: 0.8, maxTokens: 2000 }
|
|
40
105
|
});
|
|
41
106
|
|
|
42
|
-
chat
|
|
107
|
+
const chat = new ChatService({
|
|
108
|
+
adapter, // injected at construction (same as chat.use(adapter))
|
|
109
|
+
model: 'deepseek-chat',
|
|
110
|
+
system: 'You are a helpful assistant'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
chat.use(createToolCallingPlugin({ timeout: 30000 })); // plugin options
|
|
114
|
+
|
|
115
|
+
// per-request override (this request only)
|
|
116
|
+
await chat.send('Hello', { temperature: 0.2 });
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
> ⚠️ The default exports `openaiAdapter` / `toolCallingPlugin` / `modelRegistryPlugin` are module-level singletons — installing one on multiple `ChatService` instances will clobber shared state. Use the factories (`createXxx`) for multiple instances.
|
|
120
|
+
|
|
121
|
+
### Using `modelParams` (Recommended for Many Parameters)
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
const chat = new ChatService({
|
|
125
|
+
apiKey: 'your-api-key',
|
|
126
|
+
baseUrl: 'https://api.deepseek.com',
|
|
127
|
+
model: 'deepseek-chat', // still at top level for convenience
|
|
128
|
+
modelParams: { // optional parameters grouped
|
|
129
|
+
temperature: 0.8,
|
|
130
|
+
maxTokens: 1500,
|
|
131
|
+
reasoningEffort: 'medium' // for deepseek-reasoner
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
```
|
|
43
135
|
|
|
44
|
-
|
|
136
|
+
### Registering a Tool
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
chat.registerTool('get_weather', 'Get current weather for a city',
|
|
140
|
+
async (args) => {
|
|
141
|
+
// args = { city: 'Beijing' }
|
|
142
|
+
return `Weather in ${args.city}: 22°C, sunny`;
|
|
143
|
+
},
|
|
144
|
+
{ // parameter schema (optional but recommended)
|
|
145
|
+
city: { type: 'string', description: 'City name', required: true }
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
await chat.send('What\'s the weather in Beijing?');
|
|
150
|
+
// → AI calls get_weather, framework executes it, AI responds with weather info
|
|
45
151
|
```
|
|
46
152
|
|
|
153
|
+
---
|
|
154
|
+
|
|
47
155
|
## 🔌 Plugins & Adapters
|
|
48
156
|
|
|
49
|
-
|
|
50
|
-
|
|
157
|
+
### openaiAdapter
|
|
158
|
+
|
|
159
|
+
Converts internal messages to OpenAI‑compatible format. Supports:
|
|
160
|
+
- `apiUrl` – full URL (highest priority)
|
|
161
|
+
- `baseUrl` + `path` – base domain + API path
|
|
162
|
+
- Defaults to `https://api.openai.com/v1/chat/completions`
|
|
163
|
+
|
|
164
|
+
| Config field | Type | Default | Description |
|
|
165
|
+
|-------------|------|---------|-------------|
|
|
166
|
+
| `apiKey` | string | **required** | Bearer token for Authorization header |
|
|
167
|
+
| `apiUrl` | string | – | Full request URL (overrides baseUrl+path) |
|
|
168
|
+
| `baseUrl` | string | `https://api.openai.com` | API base domain |
|
|
169
|
+
| `path` | string | `/v1/chat/completions` | API endpoint path |
|
|
170
|
+
|
|
171
|
+
### toolCallingPlugin
|
|
172
|
+
|
|
173
|
+
Detects `tool_calls` in assistant responses, executes registered tools, feeds results back, and continues the conversation (up to `maxIterations` = 5).
|
|
174
|
+
|
|
175
|
+
- `chat.registerTool(name, description, executor, parameters?)` – register a tool
|
|
176
|
+
- Automatically injects `tool` role messages into the conversation
|
|
177
|
+
- Recovers from tool execution errors gracefully (logs error, returns error message to model)
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 🔧 Extending with Pipeline (v3.0)
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
// Inject a role card before every request
|
|
185
|
+
chat.pipe({ name: 'role-card', phase: 'beforeSend', async run(ctx) {
|
|
186
|
+
ctx.messages.add({ role: 'system', content: 'You are a tsundere CEO.' });
|
|
187
|
+
}});
|
|
188
|
+
|
|
189
|
+
// Post-process the reply
|
|
190
|
+
chat.pipe({ name: 'stamp', phase: 'afterSend', run(ctx) {
|
|
191
|
+
if (ctx.result?.content) ctx.result.content += ' — ' + Date.now();
|
|
192
|
+
}});
|
|
193
|
+
|
|
194
|
+
chat.unpipe('role-card'); // remove
|
|
195
|
+
console.log(chat.pipelineStages); // inspect stage order
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
See `docs/DEVELOPER.md` \u201cWrite a Stage\u201d for the full guide.
|
|
199
|
+
|
|
200
|
+
## 📡 Events (EventEmitter)
|
|
201
|
+
|
|
202
|
+
`ChatService` extends `EventEmitter`. Subscribe with `chat.on(event, handler)`:
|
|
203
|
+
|
|
204
|
+
| Event | Payload | When |
|
|
205
|
+
|-------|---------|------|
|
|
206
|
+
| `sending` | `{ addUser, userInput, timestamp }` | Before each request |
|
|
207
|
+
| `message` | `{ role, content, ... }` | Full assistant message received |
|
|
208
|
+
| `stream-progress` | chunk object | Each streaming chunk arrives |
|
|
209
|
+
| `error` | `{ error, timestamp }` | Any error during request |
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
chat.on('sending', ({ userInput }) => console.log('Sending:', userInput));
|
|
213
|
+
chat.on('message', msg => console.log('Got:', msg.content));
|
|
214
|
+
chat.on('error', ({ error }) => console.error('Error:', error.message));
|
|
215
|
+
|
|
216
|
+
// on() returns an unsubscribe function
|
|
217
|
+
const unsubscribe = chat.on('message', handler);
|
|
218
|
+
unsubscribe(); // stop listening
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 🧩 ChatService API
|
|
224
|
+
|
|
225
|
+
| Method | Returns | Description |
|
|
226
|
+
|--------|---------|-------------|
|
|
227
|
+
| `chat.send(userInput)` | `Promise<Message>` | Send a message, get reply (non‑streaming) |
|
|
228
|
+
| `chat.stream(userInput, onProgress, onDone)` | `Promise<Message>` | Send a message, get streaming reply |
|
|
229
|
+
| `chat.sendExisting()` | `Promise<Message>` | Re‑send current messages without adding user input |
|
|
230
|
+
| `chat.sendExistingStream(onProgress, onDone)` | `Promise<Message>` | Same as above, streaming |
|
|
231
|
+
| `chat.use(plugin)` | `this` | Install a plugin/adapter |
|
|
232
|
+
| `chat.setAdapter(adapter)` | `void` | Manually set the adapter |
|
|
233
|
+
| `chat.on(event, handler)` | `unsubscribe function` | Subscribe to events |
|
|
234
|
+
| `chat.registerTool(name, desc, fn, params?)` | `this` | Register a tool (requires toolCallingPlugin) |
|
|
235
|
+
| `chat.messages` | `MessageStore` | Access the message store directly |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 🗄️ MessageStore API
|
|
240
|
+
|
|
241
|
+
| Method | Description |
|
|
242
|
+
|--------|-------------|
|
|
243
|
+
| `add(message)` | Add a raw message object |
|
|
244
|
+
| `addUser(content, meta?)` | Add a user message |
|
|
245
|
+
| `addAssistant(content, meta?)` | Add an assistant message |
|
|
246
|
+
| `addSystem(content, meta?)` | Add a system message |
|
|
247
|
+
| `addTool(content, toolCallId, meta?)` | Add a tool result message |
|
|
248
|
+
| `addOnceAssistant(content, options?)` | Add a one-shot assistant guide message (defaults to `_ephemeral: true` + `prefix: true`; options: `{ reasoningContent, prefix }`) |
|
|
249
|
+
| `getAll()` | Return a shallow copy of all messages |
|
|
250
|
+
| `getLast()` | Return the last message (or null) |
|
|
251
|
+
| `clear()` | Remove all messages |
|
|
252
|
+
| `undoToLastAssistant()` | Remove messages after the last assistant message |
|
|
253
|
+
| `undoToPreviousUser(id)` | Remove messages from the message with `id` (inclusive) back to, but excluding, the previous user message — useful before resending |
|
|
254
|
+
|
|
255
|
+
**Message format**:
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
{
|
|
259
|
+
id: string, // auto‑generated if not provided
|
|
260
|
+
role: 'user' | 'assistant' | 'system' | 'tool',
|
|
261
|
+
content: string,
|
|
262
|
+
toolCalls?: Array, // assistant messages with tool calls
|
|
263
|
+
toolCallId?: string, // tool messages
|
|
264
|
+
reasoningContent?: string, // deepseek-reasoner
|
|
265
|
+
timestamp?: number,
|
|
266
|
+
metadata?: any
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## ❌ Error Handling
|
|
51
273
|
|
|
52
|
-
|
|
274
|
+
The framework throws typed errors for different failure modes:
|
|
53
275
|
|
|
54
|
-
|
|
276
|
+
| Error Class | `.name` | When |
|
|
277
|
+
|------------|---------|------|
|
|
278
|
+
| `APIError` | `'APIError'` | Non‑2xx HTTP responses (401, 429, 500, etc.) |
|
|
279
|
+
| `NetworkError` | `'NetworkError'` | `fetch` failures, connection timeouts |
|
|
280
|
+
| `ConfigurationError` | `'ConfigurationError'` | Missing required config |
|
|
281
|
+
| `ParsingError` | `'ParsingError'` | Malformed API response |
|
|
55
282
|
|
|
56
|
-
|
|
283
|
+
```javascript
|
|
284
|
+
import { APIError, NetworkError, ConfigurationError, ParsingError } from 'my-ai-chat-framework';
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
await chat.send('Hello');
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (error instanceof APIError) {
|
|
290
|
+
console.error(`API ${error.statusCode}: ${error.message}`);
|
|
291
|
+
} else if (error instanceof NetworkError) {
|
|
292
|
+
console.error('Network issue:', error.message);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 📚 Configuration Reference
|
|
300
|
+
|
|
301
|
+
`new ChatService(config)` accepts:
|
|
302
|
+
|
|
303
|
+
| Option | Type | Default | Description |
|
|
304
|
+
|--------|------|---------|-------------|
|
|
305
|
+
| `apiKey` | string | **required** | Your API key |
|
|
306
|
+
| `baseUrl` | string | `'https://api.openai.com'` | API base URL (used with `path`) |
|
|
307
|
+
| `path` | string | `'/v1/chat/completions'` | API path (used with `baseUrl`) |
|
|
308
|
+
| `apiUrl` | string | – | Full API URL (overrides `baseUrl`+`path`) |
|
|
309
|
+
| `model` | string | **required** | Model name (e.g., `deepseek-chat`) |
|
|
310
|
+
| `modelParams` | object | `{}` | Grouped model parameters (see below) |
|
|
311
|
+
| `temperature` | number | `0.7` | Sampling temperature (0–2) |
|
|
312
|
+
| `maxTokens` | number | `2000` | Max tokens to generate |
|
|
313
|
+
| `reasoningEffort` | string | – | For `deepseek-reasoner`: `'low'`, `'medium'`, `'high'` |
|
|
314
|
+
|
|
315
|
+
> Both flat and `modelParams` styles work. `modelParams` takes precedence over top‑level values.
|
|
316
|
+
|
|
317
|
+
### modelParams
|
|
318
|
+
|
|
319
|
+
`modelParams` groups all model-related parameters. Supported fields:
|
|
320
|
+
|
|
321
|
+
| Field | Type | Default | Description |
|
|
322
|
+
|-------|------|---------|-------------|
|
|
323
|
+
| `model` | string | – | Model name (overrides top-level `model`) |
|
|
324
|
+
| `temperature` | number | `0.7` | Sampling temperature (0–2) |
|
|
325
|
+
| `maxTokens` | number | `2000` | Max tokens to generate |
|
|
326
|
+
| `reasoningEffort` | string | – | For `deepseek-reasoner`: `'low'`, `'medium'`, `'high'` |
|
|
327
|
+
| `topP` | number | – | Nucleus sampling (0–1) |
|
|
328
|
+
| `frequencyPenalty` | number | – | Penalize frequent tokens (-2.0–2.0) |
|
|
329
|
+
| `presencePenalty` | number | – | Penalize repeated tokens (-2.0–2.0) |
|
|
330
|
+
| `stop` | string \| string[] | – | Stop sequences |
|
|
331
|
+
| `responseFormat` | object | – | e.g., `{ type: 'json_object' }` |
|
|
332
|
+
| `seed` | number | – | For reproducible results |
|
|
333
|
+
|
|
334
|
+
**Naming convention**: CamelCase fields (`maxTokens`, `topP`) are managed by the adapter (converted to API format). Any unknown field is passed through as‑is—use the provider's native naming (e.g., `snake_case` for OpenAI).
|
|
57
335
|
|
|
58
|
-
```
|
|
59
|
-
new ChatService(
|
|
60
|
-
apiKey:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
336
|
+
```javascript
|
|
337
|
+
const chat = new ChatService({
|
|
338
|
+
apiKey: 'your-api-key',
|
|
339
|
+
baseUrl: 'https://api.deepseek.com',
|
|
340
|
+
model: 'deepseek-chat',
|
|
341
|
+
modelParams: {
|
|
342
|
+
temperature: 0.8,
|
|
343
|
+
maxTokens: 1500,
|
|
344
|
+
topP: 0.9,
|
|
345
|
+
frequencyPenalty: 0.5,
|
|
346
|
+
// unknown fields pass through directly (use API's native names):
|
|
347
|
+
logprobs: true,
|
|
348
|
+
top_logprobs: 5
|
|
349
|
+
}
|
|
350
|
+
});
|
|
66
351
|
```
|
|
67
352
|
|
|
68
|
-
|
|
69
|
-
- `send(message)` – Send a message and wait for the complete response.
|
|
70
|
-
- `stream(message, onProgress, onDone)` – Stream the response.
|
|
71
|
-
- `registerTool(name, description, executor)` – Register a tool (only available after loading `toolCallingPlugin`).
|
|
353
|
+
---
|
|
72
354
|
|
|
73
|
-
|
|
355
|
+
## 🧪 Testing
|
|
74
356
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
- `addSystem(content, metadata)`
|
|
79
|
-
- `addTool(content, toolCallId, metadata)`
|
|
80
|
-
- `getAll()` – Get all messages.
|
|
81
|
-
- `undoToLastAssistant()` – Rollback to the last assistant message.
|
|
357
|
+
```bash
|
|
358
|
+
# 1. Create a .env file with your API key
|
|
359
|
+
echo "DEEPSEEK_API_KEY=sk-xxxxx" > .env
|
|
82
360
|
|
|
83
|
-
|
|
361
|
+
# 2. Run the test
|
|
362
|
+
npm test
|
|
363
|
+
```
|
|
84
364
|
|
|
85
|
-
|
|
86
|
-
- `off(event, handler)` – Unsubscribe.
|
|
87
|
-
- `emit(event, data)` – Emit an event.
|
|
365
|
+
Unit tests: `npm test` (`tests/core.test.js` + `tests/pipeline.test.js`, no API key needed). Integration test: `npm run test:integration` (`tests/integration.test.js`, requires `DEEPSEEK_API_KEY` in `.env`).
|
|
88
366
|
|
|
89
|
-
|
|
367
|
+
---
|
|
90
368
|
|
|
91
369
|
## 🛠️ Development
|
|
92
370
|
|
|
@@ -94,14 +372,20 @@ Events: `sending`, `message`, `stream-progress`, `error`, etc.
|
|
|
94
372
|
git clone https://github.com/your-username/my-ai-chat-framework.git
|
|
95
373
|
cd my-ai-chat-framework
|
|
96
374
|
npm install
|
|
97
|
-
|
|
98
|
-
|
|
375
|
+
|
|
376
|
+
# Dev mode (watching)
|
|
377
|
+
npm run dev
|
|
378
|
+
|
|
379
|
+
# Build the library (ES + UMD + CJS)
|
|
380
|
+
npm run build
|
|
381
|
+
|
|
382
|
+
# Run tests
|
|
383
|
+
npm test
|
|
99
384
|
```
|
|
100
385
|
|
|
386
|
+
---
|
|
387
|
+
|
|
101
388
|
## 📄 License
|
|
102
389
|
|
|
103
390
|
MIT
|
|
104
391
|
|
|
105
|
-
## 🤝 Contributing
|
|
106
|
-
|
|
107
|
-
Contributions are welcome! Please open an issue or submit a pull request.
|