@xisui-pa/chatserver 0.0.0-stage → 1.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/LICENSE +21 -0
- package/README.md +558 -2
- package/THIRD_PARTY_NOTICES.txt +179 -0
- package/dist/index.js +12295 -0
- package/dist/index.js.map +1 -0
- package/dist/models/chatserver-models.js +2740 -0
- package/dist/models/chatserver-models.js.map +1 -0
- package/package.json +67 -4
- package/schemas/config.schema.json +355 -0
- package/schemas/models.schema.json +103 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xisui-PA
|
|
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
CHANGED
|
@@ -1,3 +1,559 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ChatServer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A TypeScript bridge between chat apps and LLM models. It filters incoming messages,
|
|
4
|
+
generates short replies, and retains compact conversation memory. The built-in
|
|
5
|
+
WeChat adapter connects to agent-wechat using outbound REST requests; ChatServer
|
|
6
|
+
opens no listening ports.
|
|
7
|
+
|
|
8
|
+
## Quick start: WeChat
|
|
9
|
+
|
|
10
|
+
Requires Node.js **22.22.2+ (22.x) or 24.15.0+**, a Responses API compatible model
|
|
11
|
+
provider, and a running, logged-in [agent-wechat server](https://thisnick.github.io/agent-wechat/reference/api/)
|
|
12
|
+
with its bearer token. WeChat login and agent-wechat setup are managed separately.
|
|
13
|
+
|
|
14
|
+
1. Install and build from the repository root:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install
|
|
18
|
+
npm run build
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Add a model using guided setup:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm run model:add -- --config_dir ./chatserver-config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Choose or create a provider, enter its API base URL and key, then select or
|
|
28
|
+
enter a model ID. For a local server, a URL might be
|
|
29
|
+
`http://llm-host.local:1234/v1`. The command creates the config directory and
|
|
30
|
+
prints a reference such as `local/your-model-id`.
|
|
31
|
+
If your local server ignores API keys, enter a nonempty placeholder such as `local`.
|
|
32
|
+
|
|
33
|
+
3. Create `chatserver-config/config.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"chats": [
|
|
38
|
+
{
|
|
39
|
+
"messager": {
|
|
40
|
+
"name": "wechat",
|
|
41
|
+
"replier": "local/your-model-id",
|
|
42
|
+
"localTools": [],
|
|
43
|
+
"apiTools": [],
|
|
44
|
+
"options": {
|
|
45
|
+
"baseUrl": "http://127.0.0.1:6174",
|
|
46
|
+
"chatIds": ["wxid_example"]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"filters": []
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Replace `replier` with the printed reference and `wxid_example` with a direct
|
|
56
|
+
chat or group ID from agent-wechat's `/api/chats`. Set `baseUrl` to your
|
|
57
|
+
agent-wechat server root, without `/api`. `localTools: []` disables local
|
|
58
|
+
functions; omit it to enable the built-ins. `apiTools` defaults to `[]`.
|
|
59
|
+
|
|
60
|
+
4. Set the **agent-wechat bearer token** and start ChatServer.
|
|
61
|
+
|
|
62
|
+
macOS / Linux:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
export AGENT_WECHAT_TOKEN='your-agent-wechat-token'
|
|
66
|
+
npm start -- --config_dir ./chatserver-config
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Windows PowerShell:
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
$env:AGENT_WECHAT_TOKEN = 'your-agent-wechat-token'
|
|
73
|
+
npm start -- --config_dir ./chatserver-config
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
5. Send a **new** text message to the bot in the configured conversation.
|
|
77
|
+
Replies are processed sequentially with a random **1–3 minute** delay, including
|
|
78
|
+
before the first reply. With no filters, the reply model decides whether to
|
|
79
|
+
respond. Stop with Ctrl+C.
|
|
80
|
+
|
|
81
|
+
The WeChat bearer token and the model provider's API key are separate credentials.
|
|
82
|
+
For LAN model servers on Windows, use the full hostname such as `llm-host.local`
|
|
83
|
+
rather than a bare hostname.
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
Use the same config directory for the server and model commands. Select it with
|
|
88
|
+
`--config_dir <directory>` (`-c`) or `CHATSERVER_CONFIG_DIR`. An explicitly selected
|
|
89
|
+
directory must contain `config.json` before starting the server. Without an
|
|
90
|
+
explicit directory, the server creates a default `{ "chats": [] }` on first run.
|
|
91
|
+
|
|
92
|
+
| Platform | Default directory |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| macOS | `~/Library/Application Support/chatserver` |
|
|
95
|
+
| Linux | `$XDG_CONFIG_HOME/chatserver` or `~/.config/chatserver` |
|
|
96
|
+
| Windows | `%APPDATA%\chatserver` |
|
|
97
|
+
|
|
98
|
+
The directory contains `config.json`, `models.json`, optional ignore/token files,
|
|
99
|
+
optional `<messager_name>_context` files, and automatically managed `conversations/`
|
|
100
|
+
memory and image-cache files. An empty `chats` array
|
|
101
|
+
runs no adapters.
|
|
102
|
+
|
|
103
|
+
Set `messager.memoryTokenLimit` to change the token budget for each conversation
|
|
104
|
+
file belonging to that pipeline, for example `"memoryTokenLimit": 4000`. The
|
|
105
|
+
default is 1,000; valid limits are 100–16,000 tokens.
|
|
106
|
+
Notes and confirmed replies share this budget, including their compact JSON
|
|
107
|
+
metadata. Memory has a separate input allowance from the message snapshot.
|
|
108
|
+
Restart after changing the limit; lowering it removes the oldest entries on
|
|
109
|
+
the next turn for each conversation. Three-day expiry still applies. JSON
|
|
110
|
+
indentation and the file version field are not counted.
|
|
111
|
+
|
|
112
|
+
Each `chats` entry has a `messager` and optional `filters`:
|
|
113
|
+
|
|
114
|
+
| Field | Meaning |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `messager.name` | Adapter name; currently `wechat` |
|
|
117
|
+
| `messager.replier` | `<provider>/<model id>` from `models.json`, using the actual ID, not its CLI alias |
|
|
118
|
+
| `messager.memoryTokenLimit` | Tokens per conversation for notes and confirmed replies; defaults to 1,000 |
|
|
119
|
+
| `messager.persona` | Optional replier identity, tone, and conversational style |
|
|
120
|
+
| `messager.mainAccount` | Optional exact sender ID allowed to query `/status` in a direct chat |
|
|
121
|
+
| `messager.localTools` | Local tool names or `{ "type": "name" }` objects; omitted enables all five built-ins, `[]` disables local tools |
|
|
122
|
+
| `messager.apiTools` | Hosted tool type strings or definition objects; omitted or `[]` enables none |
|
|
123
|
+
| `messager.imageGeneration` | Optional boolean; omitted enables generation when `apiTools` includes `image_generation`; `true` adds that tool, `false` removes it |
|
|
124
|
+
| `messager.imageWidthLimit` | Maximum generated image width in pixels; defaults to `1024` |
|
|
125
|
+
| `messager.imageHeightLimit` | Maximum generated image height in pixels; defaults to `1024` |
|
|
126
|
+
| `messager.dailyImageGenLimit` | Daily image generation allowance shared across this messager's conversations; defaults to `10`; `0` disables it |
|
|
127
|
+
| `messager.options` | Adapter settings below |
|
|
128
|
+
| `filters` | Ordered filters; omitted or `[]` skips filtering |
|
|
129
|
+
| `stt` | Optional object with `type: "model"`, required `model`, and optional `audioConversionCmd`; omitted disables voice input |
|
|
130
|
+
| `tts` | Optional `{ "type": "model", "model": "provider/model-id", "options": { ... } }` for explicitly requested voice replies |
|
|
131
|
+
|
|
132
|
+
The former `messager.tools` field has been replaced by `messager.localTools`;
|
|
133
|
+
rename it in existing configurations. The old field is rejected with a migration
|
|
134
|
+
error so that a previous `tools: []` cannot silently enable the default tools.
|
|
135
|
+
|
|
136
|
+
Local and provider-hosted tools can be used together. For example, this enables
|
|
137
|
+
local arithmetic and time plus the provider's hosted web search:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"localTools": ["calculate", { "type": "current_time" }],
|
|
142
|
+
"apiTools": [
|
|
143
|
+
{ "type": "web_search", "search_context_size": "low" }
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Put these fields inside `messager`. Both arrays accept strings, objects, or a mix.
|
|
149
|
+
For example, `"apiTools": ["web_search"]` is equivalent to
|
|
150
|
+
`"apiTools": [{ "type": "web_search" }]`. Local objects contain only `type`,
|
|
151
|
+
which must name a registered local tool; duplicate names are deduplicated across
|
|
152
|
+
both forms. Hosted strings expand to `{ "type": "..." }`; hosted objects require
|
|
153
|
+
a nonempty `type` and preserve their provider-specific options. The resulting
|
|
154
|
+
objects are sent in the Responses API `tools` array alongside local function
|
|
155
|
+
declarations. Use the selected provider/model's
|
|
156
|
+
documented tool configuration; hosted tools are not discovered automatically.
|
|
157
|
+
The provider validates support and tool-specific options, and unsupported requests
|
|
158
|
+
fail rather than silently falling back to local tools. `function` and `custom`
|
|
159
|
+
definitions are rejected here: register application-executed functions as local
|
|
160
|
+
tools instead. Hosted tools must finish at the provider; text and generated image
|
|
161
|
+
results are supported. Client execution, approval exchanges, and other generated
|
|
162
|
+
artifacts are not implemented. Filters and formatting repairs receive no hosted
|
|
163
|
+
tools. Set both fields to `[]` and omit or disable `imageGeneration` to disable
|
|
164
|
+
all tool calling.
|
|
165
|
+
|
|
166
|
+
Each generation attempt allows up to eight local calls and eight hosted calls
|
|
167
|
+
across its model rounds, under the provider's existing timeout. Hosted requests
|
|
168
|
+
send `max_tool_calls` with the remaining hosted budget; providers must support
|
|
169
|
+
this Responses API parameter to enforce the cap during a response. All output
|
|
170
|
+
items, including hosted calls and reasoning, are replayed when local calls need
|
|
171
|
+
another round. Full-generation retries can repeat hosted calls and their charges;
|
|
172
|
+
completed image artifacts are preserved and reused across these retries.
|
|
173
|
+
|
|
174
|
+
Image generation uses the replier's provider-hosted
|
|
175
|
+
[`image_generation` Responses tool](https://developers.openai.com/api/docs/guides/tools-image-generation).
|
|
176
|
+
The selected provider/model must support this tool; there is no separate image
|
|
177
|
+
provider or fallback. Put these settings inside `messager`:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"imageGeneration": true,
|
|
182
|
+
"imageWidthLimit": 1024,
|
|
183
|
+
"imageHeightLimit": 1024,
|
|
184
|
+
"dailyImageGenLimit": 10
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Alternatively, include `"image_generation"` in `apiTools` and omit
|
|
189
|
+
`imageGeneration` to detect support from that selection. A definition object can
|
|
190
|
+
specify provider options such as `quality` or the hosted image `model`. Explicit
|
|
191
|
+
`imageGeneration: false` overrides that tool selection. The reply model generates
|
|
192
|
+
or edits images only for explicit requests. Model filters recognize these requests
|
|
193
|
+
when generation is enabled, and WeChat sends returned bytes as image messages,
|
|
194
|
+
with an optional caption. Image-only responses are supported.
|
|
195
|
+
|
|
196
|
+
`imageWidthLimit` and `imageHeightLimit` are independent integer pixel limits from
|
|
197
|
+
1 to 16384, each defaulting to 1024. ChatServer combines them into the provider's
|
|
198
|
+
`WIDTHxHEIGHT` size string; the provider must support the requested size. The tool
|
|
199
|
+
receives this size unless its configured concrete size fits within both limits.
|
|
200
|
+
`auto` and larger sizes are replaced by the combined limits. ChatServer requests PNG
|
|
201
|
+
and rejects returned images exceeding either dimension or the existing 5 MiB
|
|
202
|
+
image byte limit. Image bytes stay outside reply JSON and conversation memory;
|
|
203
|
+
confirmed memory records an image-delivery marker, any caption, and `imageIds`
|
|
204
|
+
linking the reply to its generated cache entries. These IDs count toward the
|
|
205
|
+
memory budget and allow `read_image` lookup after older labels leave `cachedImages`.
|
|
206
|
+
|
|
207
|
+
Generated images are saved in the conversation's existing image store as soon as
|
|
208
|
+
their provider output is validated, before later model rounds, formatting repair,
|
|
209
|
+
or delivery, including when `read_image` is disabled. If later model work fails,
|
|
210
|
+
the artifact remains cached; retries reuse the completed image without another
|
|
211
|
+
image generation call. They share the received-image
|
|
212
|
+
limits: three days from caching, at most 30 images, and 64 MiB of cache JSON per
|
|
213
|
+
conversation. Entries have their own generated-image IDs, assistant attribution,
|
|
214
|
+
`origin: "generated"`, and a `deliveryStatus` of `pending`, `sent`, or `failed`.
|
|
215
|
+
Only `sent` confirms delivery; failed or interrupted sends may remain unconfirmed.
|
|
216
|
+
Updating delivery status never extends retention. Cache write failures are logged
|
|
217
|
+
and do not block sending.
|
|
218
|
+
|
|
219
|
+
When `read_image` is enabled, generated image references appear in `cachedImages`
|
|
220
|
+
and their bytes can be inspected in later turns, including after a restart or
|
|
221
|
+
failed upload. Caching does not resend images, and no resend tool is provided.
|
|
222
|
+
|
|
223
|
+
Quota is kept only in memory for each configured messager pipeline, shared across
|
|
224
|
+
its conversations and generation retries. Usage resets on server restart and at
|
|
225
|
+
midnight in the server's local timezone. No quota files are read or written.
|
|
226
|
+
Before each image-capable provider request, ChatServer
|
|
227
|
+
reserves one slot and sets `max_tool_calls: 1` (also limiting other hosted calls
|
|
228
|
+
in that response). An unused slot is released when a response confirms no image
|
|
229
|
+
call occurred. Ambiguous provider failures retain their reservation; failed
|
|
230
|
+
delivery or rejected image bytes do not refund generation charges. Once an image
|
|
231
|
+
is generated, further local-tool rounds and model retries cannot generate another
|
|
232
|
+
for that reply. When quota is exhausted, image generation is removed from the
|
|
233
|
+
request and text replies remain available. WeChat image delivery uses the existing
|
|
234
|
+
[`/api/messages/send` image payload](https://github.com/thisnick/agent-wechat/blob/5472a168956ad5d09d3063342e691448980a5c31/packages/agent-server-rust/src/router/messages.rs#L144)
|
|
235
|
+
and is never automatically retried after an uncertain send.
|
|
236
|
+
|
|
237
|
+
Provider URL citations are preserved as complete source links in outgoing text,
|
|
238
|
+
including after a formatting repair. Text is shortened to reserve room for links;
|
|
239
|
+
if the citations alone cannot fit the reply budget, delivery fails. Required hosted
|
|
240
|
+
citations are included even when the sender did not ask for sources, as required
|
|
241
|
+
by [OpenAI's hosted web search](https://developers.openai.com/api/docs/guides/tools-web-search).
|
|
242
|
+
|
|
243
|
+
The `messager.persona` setting accepts a nonempty string with one or more sentences.
|
|
244
|
+
For example: `"You are a warm, lightly humorous friend. Give practical photography advice. Keep your tone relaxed."`
|
|
245
|
+
Use `\n` within the JSON string to separate paragraphs; sentence breaks and newlines
|
|
246
|
+
are preserved in the prompt.
|
|
247
|
+
Omitting it keeps the default helpful assistant. It affects only the replier;
|
|
248
|
+
reply-length, citation, tool, JSON, and memory rules still apply. Restart the server
|
|
249
|
+
after changing this setting.
|
|
250
|
+
|
|
251
|
+
For editor completion and validation, add `$schema` pointing to
|
|
252
|
+
[schemas/config.schema.json](schemas/config.schema.json) or
|
|
253
|
+
[schemas/models.schema.json](schemas/models.schema.json) in the corresponding
|
|
254
|
+
file. For example: `"$schema": "file:///absolute/path/to/ChatServer/schemas/config.schema.json"`.
|
|
255
|
+
Encode spaces in file URLs as `%20`.
|
|
256
|
+
|
|
257
|
+
Inspect configuration without starting adapters:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm start -- --config_dir ./chatserver-config --print-config
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Background context
|
|
264
|
+
|
|
265
|
+
Optionally create `<config_dir>/<messager_name>_context` yourself, for example
|
|
266
|
+
`chatserver-config/wechat_context`. Use plain UTF-8 text describing friends or
|
|
267
|
+
channels, with their IDs to identify which background applies:
|
|
268
|
+
|
|
269
|
+
```text
|
|
270
|
+
Friend wxid_alice: Alice is learning photography and prefers brief, practical tips.
|
|
271
|
+
Channel 123456@chatroom: A photography club discussing weekend outings.
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Model inputs include the WeChat display name when available, and replies prefer
|
|
275
|
+
that name when referring to a person. Stable wxids still identify speakers and
|
|
276
|
+
control `mainAccount`, ignore rules, and delivery. Names may change or be shared;
|
|
277
|
+
use wxids in background entries to keep them unambiguous.
|
|
278
|
+
|
|
279
|
+
The background is included in model filter and replier inputs. It is shared by
|
|
280
|
+
pipelines with the same messager name and re-read for each processed turn, so
|
|
281
|
+
edits apply without restarting. Up to 2,000 tokens are included separately from
|
|
282
|
+
the message snapshot budget; longer context is marked truncated. Missing or empty
|
|
283
|
+
files add no context, and ChatServer never creates them. Read failures log a warning
|
|
284
|
+
and continue without background. Context is not sent to the STT model or saved as
|
|
285
|
+
conversation memory.
|
|
286
|
+
|
|
287
|
+
### WeChat options
|
|
288
|
+
|
|
289
|
+
| Option | Default | Meaning |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| `baseUrl` | `http://127.0.0.1:6174` | Agent-wechat root URL, without `/api` |
|
|
292
|
+
| `tokenEnv` | `AGENT_WECHAT_TOKEN` | Environment variable containing the bearer token |
|
|
293
|
+
| `tokenFile` | unset | Token file used when the environment token is absent; relative to the config directory |
|
|
294
|
+
| `chatIds` | all chats | Exact conversation IDs to monitor; `[]` monitors none except `mainAccount`, if configured |
|
|
295
|
+
| `pollIntervalMs` | `30000` | Delay after each completed poll |
|
|
296
|
+
| `timeoutMs` | `30000` | Per-request API timeout |
|
|
297
|
+
| `bundleGapMs` | `20000` | Maximum gap between adjacent same-sender messages in a bundle; `0` disables bundling |
|
|
298
|
+
|
|
299
|
+
Tokens are read at startup. Group IDs typically end in `@chatroom`.
|
|
300
|
+
`mainAccount`, when set, is monitored even if omitted from `chatIds`. Its exact
|
|
301
|
+
`/status` command returns local queue, delivery, error, and polling information
|
|
302
|
+
without calling a model; it uses the normal reply delay. Other senders and group
|
|
303
|
+
status requests are ignored.
|
|
304
|
+
|
|
305
|
+
### Filters
|
|
306
|
+
|
|
307
|
+
For example, replace the quick start's empty `filters` with:
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
[
|
|
311
|
+
{ "type": "ignore" },
|
|
312
|
+
{
|
|
313
|
+
"type": "model",
|
|
314
|
+
"model": "local/your-model-id",
|
|
315
|
+
"options": { "instructions": "Allow questions; deny advertisements; otherwise abstain." }
|
|
316
|
+
}
|
|
317
|
+
]
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`ignore` (alias `ignore-sender`) reads `wechat_ignore` in the config directory:
|
|
321
|
+
one sender ID per line or comma-separated, with `#` comments. Changes reload
|
|
322
|
+
without restarting. Use `options.file` for a different file or `options.senderIds`
|
|
323
|
+
for an inline list. Ignored messages are removed before models see them.
|
|
324
|
+
|
|
325
|
+
Model filters return `allowed`, `denied`, or `optional`. The first decisive result
|
|
326
|
+
wins; if all abstain, the replier decides. Errors fail closed. Filter models have
|
|
327
|
+
thinking disabled (`reasoning.effort: none`); choose a provider that supports that
|
|
328
|
+
setting. When `read_image` is enabled for the pipeline, model filters can use it
|
|
329
|
+
to inspect cached images, but receive no other local or hosted tools.
|
|
330
|
+
|
|
331
|
+
## Models
|
|
332
|
+
|
|
333
|
+
Provider URLs, model IDs/aliases, and provider-specific `timeoutMs` live in
|
|
334
|
+
`models.json`. Each provider requires an explicit `baseUrl`; requests default to a
|
|
335
|
+
**3-minute** timeout. API keys are stored separately under the provider name:
|
|
336
|
+
macOS Keychain, Linux Secret Service (`secret-tool`), or Windows Credential Manager
|
|
337
|
+
(requires the `CredentialManager` PowerShell module).
|
|
338
|
+
|
|
339
|
+
Each entry in a provider's `models` array can set `supportsImageToolResults`.
|
|
340
|
+
It defaults to `false`; enable it only when that model endpoint accepts native
|
|
341
|
+
image content arrays in `function_call_output`:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"id": "gpt-6.1-sol",
|
|
346
|
+
"name": "sol",
|
|
347
|
+
"supportsImageToolResults": true
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
When enabled, the initial chat input contains image IDs and source labels without
|
|
352
|
+
image bytes. The model can call `read_image` when visual inspection is needed;
|
|
353
|
+
the image is returned directly inside that function's result. Keep `read_image`
|
|
354
|
+
enabled in `messager.localTools` for this mode. If image lookup is unavailable,
|
|
355
|
+
filters and repliers fall back to direct attachments. Pending optional images do
|
|
356
|
+
not defer a text-only decision; `read_image` can retry their source media if the
|
|
357
|
+
model asks to inspect them. Existing cached bytes are reused before fetching
|
|
358
|
+
source media again. With the default `false`, up to
|
|
359
|
+
four new images attach directly and older tool-retrieved images attach as a
|
|
360
|
+
separate image message on the next model round. Filter and replier models each
|
|
361
|
+
use their own setting, even when they share a chat snapshot. Restart the server
|
|
362
|
+
after changing model settings.
|
|
363
|
+
|
|
364
|
+
| Command | Purpose |
|
|
365
|
+
| --- | --- |
|
|
366
|
+
| `npm run model:add` | Guided provider/model setup; discovers advertised IDs or accepts manual input |
|
|
367
|
+
| `npm run model:update` | Guided provider/model editor; Enter keeps current values |
|
|
368
|
+
| `npm run model:list` | List configured providers and models; `--json` prints the configuration |
|
|
369
|
+
| `npm run model:test` | Send a test prompt to a configured model |
|
|
370
|
+
| `npm run model:test-stt` | Transcribe a local audio file with a configured STT model |
|
|
371
|
+
| `npm run model:test-tts` | Synthesize speech and save a WAV with the same padding as WeChat replies |
|
|
372
|
+
| `npm run model:remove` | Remove a model and its provider/key if no models remain |
|
|
373
|
+
|
|
374
|
+
Append `-- --config_dir ./chatserver-config` to select the quick start directory.
|
|
375
|
+
All commands support `--help`; add/update also support `--interactive` (`-i`).
|
|
376
|
+
Flags remain available for scripts:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
npm run model:add -- --config_dir ./chatserver-config --provider local --base-url http://llm-host.local:1234/v1 --model your-model-id
|
|
380
|
+
npm run model:test -- --config_dir ./chatserver-config --provider local --name your-model-id
|
|
381
|
+
npm run model:update -- --config_dir ./chatserver-config --provider local --timeout 300000
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Test speech models without sending any WeChat messages:
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
npm run model:test-stt -- --provider local --name your-stt-model --file ./voice.wav
|
|
388
|
+
npm run model:test-tts -- --provider local --name your-tts-model --voice your-provider-voice --text "Hello!" --output ./reply.wav
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
STT supports `--json` for a JSON transcript and `--audio-conversion-cmd` with
|
|
392
|
+
`{input}`/`{output}` for WAV-only engines, using the same conversion as chat input.
|
|
393
|
+
TTS accepts `--options` with a JSON object matching `tts.options`; `--voice` overrides
|
|
394
|
+
its voice setting. Output defaults to `tts-test.wav`; use `--force` to overwrite.
|
|
395
|
+
Both commands accept `--timeout`, `--config_dir`, and the same provider keys as
|
|
396
|
+
`model:test`. File paths resolve from the current directory.
|
|
397
|
+
|
|
398
|
+
`--name` selects a model's CLI alias (defaults to its ID). Update accepts `--model`
|
|
399
|
+
to change its API ID and `--new-name` to rename its alias. If the API ID changes,
|
|
400
|
+
update corresponding `config.json` references. Add requires overwrite confirmation
|
|
401
|
+
or `--force` for an existing alias. Commands without a terminal require flags.
|
|
402
|
+
|
|
403
|
+
Existing provider keys are reused. For missing keys, add reads `--api-key`, then
|
|
404
|
+
`OPENAI_API_KEY`, then a hidden prompt. Replace a saved key explicitly with
|
|
405
|
+
`--api-key`; `OPENAI_API_KEY` does not overwrite it. At runtime and in model tests,
|
|
406
|
+
`CHATSERVER_MODEL_API_KEY` overrides the saved key. Keys never go in `models.json`.
|
|
407
|
+
|
|
408
|
+
### Incoming voice
|
|
409
|
+
|
|
410
|
+
Add a speech-to-text model using `model:add`, then set `stt` alongside `messager`
|
|
411
|
+
and `filters` in the chat definition:
|
|
412
|
+
|
|
413
|
+
```json
|
|
414
|
+
{
|
|
415
|
+
"messager": { "name": "wechat", "replier": "local/your-model-id" },
|
|
416
|
+
"stt": {
|
|
417
|
+
"type": "model",
|
|
418
|
+
"model": "speech-provider/your-stt-model-id",
|
|
419
|
+
"audioConversionCmd": "ffmpeg -nostdin -y -i {input} -ar 16000 -ac 1 -c:a pcm_s16le {output}"
|
|
420
|
+
},
|
|
421
|
+
"filters": []
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The STT provider must implement the [OpenAI-compatible transcription endpoint](https://developers.openai.com/api/reference/resources/audio/subresources/transcriptions/methods/create)
|
|
426
|
+
(`POST <baseUrl>/audio/transcriptions`, multipart `file`/`model`, JSON `text`). It
|
|
427
|
+
uses that provider's saved key, timeout, and normal model retries; it does not need
|
|
428
|
+
to support the Responses API. `model:test` sends chat prompts, not audio.
|
|
429
|
+
|
|
430
|
+
`type` is currently `"model"`. An explicit `model` is required and must support
|
|
431
|
+
transcription; it does not default to `messager.replier`. Replace
|
|
432
|
+
old string settings such as `"stt": "speech/model"` with the object above.
|
|
433
|
+
|
|
434
|
+
`audioConversionCmd` is optional. Set it for WAV-only engines such as speech.cpp;
|
|
435
|
+
omit it to upload the original audio. Install the converter (FFmpeg in this example)
|
|
436
|
+
on the **ChatServer machine**, available on PATH or specified by an absolute path.
|
|
437
|
+
The command runs in a temporary directory with `{input}` and `{output}` replaced
|
|
438
|
+
by generated filenames; it must write WAV to `{output}`. Quote executable paths
|
|
439
|
+
containing spaces using your operating system's shell syntax (escape double quotes
|
|
440
|
+
in JSON). Conversion has a 30-second deadline, supports shutdown cancellation, and
|
|
441
|
+
removes temporary files afterwards. Conversion errors stop the turn before any
|
|
442
|
+
STT request; cached successful transcripts do not repeat conversion.
|
|
443
|
+
|
|
444
|
+
Voice messages are transcribed sequentially after the reply delay and sender
|
|
445
|
+
exclusions, before model filters and the replier. Transcripts retain the original
|
|
446
|
+
sender, timestamp, and message ID and are reused across retained snapshots. Raw
|
|
447
|
+
audio is not saved in conversation memory. Configure `tts` separately for voice replies.
|
|
448
|
+
|
|
449
|
+
Agent-wechat normally [converts received voice to MP3](https://github.com/thisnick/agent-wechat/blob/main/packages/agent-server-rust/src/tools/wechat_media.rs).
|
|
450
|
+
Audio is limited to 20 MiB per message. Pending media gets bounded retries;
|
|
451
|
+
unsupported audio (including raw SILK when upstream conversion fails), unavailable
|
|
452
|
+
media, and recordings with no recognized speech are skipped with a log. STT
|
|
453
|
+
request failures stop the turn rather than generating a reply from missing audio.
|
|
454
|
+
Without `stt`, voice messages are ignored without downloads or model requests.
|
|
455
|
+
|
|
456
|
+
### Voice replies (TTS)
|
|
457
|
+
|
|
458
|
+
Add `tts` to the chat entry, alongside `messager`, `filters`, and optional `stt`:
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
"tts": {
|
|
462
|
+
"type": "model",
|
|
463
|
+
"model": "local/your-tts-model-id",
|
|
464
|
+
"options": { "voice": "your-provider-voice", "speed": 1 }
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Register the TTS model in `models.json` using the model CLI. Its provider must
|
|
469
|
+
support an OpenAI-compatible `/audio/speech` endpoint returning uncompressed WAV.
|
|
470
|
+
`options` passes speech settings supported by that provider, such as `voice`,
|
|
471
|
+
`speed`, or `instructions`; it defaults to `{}`. ChatServer controls `model`,
|
|
472
|
+
`input`, `response_format` (`wav`), and `stream_format` (`audio`). Provider keys,
|
|
473
|
+
timeouts, and model-request retries work as for other models.
|
|
474
|
+
|
|
475
|
+
The replier selects voice only when the message being answered explicitly asks
|
|
476
|
+
for a voice reply. An incoming voice message alone does not select voice output.
|
|
477
|
+
The same short-reply limits apply to the spoken text. ChatServer adds independent
|
|
478
|
+
random **0.5–2 second** silences before and after the audio, without requiring
|
|
479
|
+
FFmpeg on the ChatServer machine.
|
|
480
|
+
|
|
481
|
+
Voice replies use the normal serial queue and 1–3 minute pacing. The adapter uploads
|
|
482
|
+
audio to agent-wechat's voice-job API and waits for confirmed completion, up to
|
|
483
|
+
three minutes; this requires an agent-wechat version with voice-job support.
|
|
484
|
+
Failure or uncertain delivery is logged without resending or sending an extra
|
|
485
|
+
text reply. Pending jobs receive best-effort cancellation on failure or shutdown.
|
|
486
|
+
Memory records the spoken text only after confirmed delivery; audio is not saved.
|
|
487
|
+
Omitting `tts` keeps replies in text. Restart after configuration changes.
|
|
488
|
+
|
|
489
|
+
## Runtime behavior
|
|
490
|
+
|
|
491
|
+
- **Queue:** One FIFO entry per conversation. Each turn uses messages since the last
|
|
492
|
+
successful reply or replier decision to stay silent, capped at the latest 30 source
|
|
493
|
+
messages. Filter denials retain messages for later turns. New arrivals update
|
|
494
|
+
pending context. Processing and sending are sequential, paced at 1–3 minutes.
|
|
495
|
+
- **History:** Messages from before startup, including those received while stopped,
|
|
496
|
+
are skipped. Self messages provide context without triggering replies; manual
|
|
497
|
+
replies arriving during generation invalidate the pending answer.
|
|
498
|
+
- **Media:** Text, images, and optional STT voice input are supported; video,
|
|
499
|
+
stickers, and files are skipped. The latest four new images per snapshot, each at most 5 MiB, are attached directly.
|
|
500
|
+
Models receiving them must support image input. Pending media gets bounded
|
|
501
|
+
retries; unavailable images are labeled rather than guessed.
|
|
502
|
+
With `read_image` enabled, images in retained snapshot messages are also downloaded
|
|
503
|
+
into `<conversation-hash>.images.json` beside the conversation memory file.
|
|
504
|
+
Older images are represented by source labels in `cachedImages`; the tool attaches
|
|
505
|
+
a requested image as native image input on the next model round. Models with
|
|
506
|
+
`supportsImageToolResults: true` receive image IDs only in the initial input and
|
|
507
|
+
receive requested images directly in tool results. Both repliers
|
|
508
|
+
and model filters can use it. Lookups are restricted to the current account and
|
|
509
|
+
conversation and work after restart. Images expire three days after the original
|
|
510
|
+
source timestamp; lookup never refreshes expiry. Cleanup runs at startup and
|
|
511
|
+
hourly, and expired entries cannot be retrieved between cleanups. Each conversation
|
|
512
|
+
keeps at most 30 images and 64 MiB of cache JSON, evicting the oldest first.
|
|
513
|
+
Recent cached-image labels have a 1,000-token prompt allowance; IDs retained in
|
|
514
|
+
memory can still retrieve images omitted from that list. Without `read_image`,
|
|
515
|
+
received images are attached directly without persistent image caching.
|
|
516
|
+
Generated images are always cached before delivery; `read_image` is required
|
|
517
|
+
for model inspection of these cached artifacts.
|
|
518
|
+
- **Replies:** Normally 1–2 short sentences, with a 150-token target and 200-token
|
|
519
|
+
hard cap. Citations are included when explicitly requested or required by hosted tools. Oversized text
|
|
520
|
+
is truncated locally. Malformed reply JSON or memory updates are logged in
|
|
521
|
+
full and receive one formatting-only repair request without tools; a second
|
|
522
|
+
invalid result stops delivery.
|
|
523
|
+
- **Memory:** Compact notes and confirmed replies persist per conversation for
|
|
524
|
+
three days, capped at 1,000 tokens by default (`messager.memoryTokenLimit`). This is summarized context, not a raw
|
|
525
|
+
message archive. Replier memory updates are saved even when it chooses not to reply;
|
|
526
|
+
filter denials skip the replier and its memory updates. Image bytes live in the
|
|
527
|
+
separate image cache and do not count toward the text memory budget.
|
|
528
|
+
- **Tools:** `web_search` (DuckDuckGo), `read_url`, `calculate`, `current_time`, and `read_image`
|
|
529
|
+
are enabled by default. Models need Responses API function calling to use them.
|
|
530
|
+
Page reading supports public HTTP(S) text/HTML/JSON, not PDFs or authenticated
|
|
531
|
+
pages. Set `messager.localTools` to select a subset or disable local tools.
|
|
532
|
+
`messager.apiTools` separately enables provider-hosted tools; its default is empty.
|
|
533
|
+
- **Failures:** Transient model errors retry the full generation up to three times,
|
|
534
|
+
ten seconds apart. Polling backs off on API errors. Failed sends are never
|
|
535
|
+
automatically retried because delivery may already have succeeded remotely.
|
|
536
|
+
|
|
537
|
+
Avoid overlapping pipelines for the same conversations, which can produce duplicate
|
|
538
|
+
replies. Reply pacing is per process and does not guarantee protection from account
|
|
539
|
+
bans.
|
|
540
|
+
|
|
541
|
+
## Development
|
|
542
|
+
|
|
543
|
+
Runtime limits and defaults are grouped and documented in [src/limit.ts](src/limit.ts).
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
npm run dev # Auto-reload
|
|
547
|
+
npm run build # Bundle server and model CLI into dist/
|
|
548
|
+
npm run typecheck
|
|
549
|
+
npm run lint
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
VS Code debug configurations and build tasks are in `.vscode/`. To use the built
|
|
553
|
+
executables directly, run `npm link`, then `chatserver` or `chatserver-models`.
|
|
554
|
+
|
|
555
|
+
Implementation entry points:
|
|
556
|
+
[server](src/server.ts), [config](src/config.ts),
|
|
557
|
+
[messagers](src/messagers/interface.ts), [filters](src/filters/interface.ts),
|
|
558
|
+
[repliers](src/repliers/interface.ts), and [tools](src/tools/interface.ts).
|
|
559
|
+
Register custom implementations in the corresponding registry before loading config.
|