dsh-llm-codebuddy 1.3.3 → 1.3.5
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.md +189 -132
- package/cli.js +59 -2
- package/client.js +152 -0
- package/codebuddy-auth.js +186 -0
- package/codebuddy-web.js +98 -0
- package/docs//345/217/215/345/220/221/344/273/243/347/220/206/350/260/203/347/224/250WorkBuddy-API/345/274/200/345/217/221/346/226/207/346/241/243.md +576 -0
- package/index.js +151 -22
- package/package.json +14 -4
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
# CodeBuddy 调用 WorkBuddy API 开发文档
|
|
2
|
+
|
|
3
|
+
> 适用对象:需要在其他 Agent、CLI、IDE 插件或本地代理中接入 CodeBuddy 模型的开发者
|
|
4
|
+
> 验证日期:2026-08-14
|
|
5
|
+
> 验证客户端:`@tencent-ai/codebuddy-code 2.136.0`
|
|
6
|
+
> 文档性质:基于已安装客户端、实际接口响应和本项目适配代码整理的第三方开发说明,不是腾讯官方 API 承诺。
|
|
7
|
+
|
|
8
|
+
## 1. 先说明名称关系
|
|
9
|
+
|
|
10
|
+
日常说的“调用 WorkBuddy API”实际包含两个不同角色:
|
|
11
|
+
|
|
12
|
+
| 名称 | 实际作用 |
|
|
13
|
+
|---|---|
|
|
14
|
+
| WorkBuddy | 提供或签发 API Key |
|
|
15
|
+
| CodeBuddy | 使用该 Key 的 Agent/CLI 产品 |
|
|
16
|
+
| `copilot.tencent.com` | 当前实际提供模型目录和推理接口的服务端 |
|
|
17
|
+
| 本项目 | 把上述服务适配为 DSH Provider |
|
|
18
|
+
|
|
19
|
+
因此,当前观察到的调用链是:
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart LR
|
|
23
|
+
A["WorkBuddy 提供 API Key"] --> B["CodeBuddy 或第三方 Agent"]
|
|
24
|
+
B --> C["GET /v3/config\n获取当前 Key 的模型权限"]
|
|
25
|
+
B --> D["POST /v2/chat/completions\n发起模型推理"]
|
|
26
|
+
C --> E["copilot.tencent.com"]
|
|
27
|
+
D --> E
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
不存在一个需要额外调用的“WorkBuddy 换取 CodeBuddy Token”步骤。开发者拿到有效 Key
|
|
31
|
+
后,直接把它用于模型目录和聊天请求。
|
|
32
|
+
|
|
33
|
+
## 2. 重要边界
|
|
34
|
+
|
|
35
|
+
1. 接口目前不是公开、稳定承诺的开发者 API,路径、Header 和字段可能随 CodeBuddy 更新。
|
|
36
|
+
2. 只能使用本人或组织授权的 API Key,不要绕过账号、额度、模型权限或企业策略。
|
|
37
|
+
3. 模型列表与 API Key 绑定,不同 Key 返回的模型 ID 可能不同。
|
|
38
|
+
4. 不要把模型目录写死在代码中;每次添加或替换 Key 后应重新获取 `/v3/config`。
|
|
39
|
+
5. API Key 只能放在环境变量、凭据服务或 Secret Manager 中,不能提交到 Git。
|
|
40
|
+
|
|
41
|
+
## 3. 接口总览
|
|
42
|
+
|
|
43
|
+
| 用途 | 方法 | 地址 | 协议 |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| 获取模型与产品配置 | `GET` | `https://copilot.tencent.com/v3/config` | JSON |
|
|
46
|
+
| 模型推理 | `POST` | `https://copilot.tencent.com/v2/chat/completions` | OpenAI Chat Completions 兼容,推荐 SSE 流式 |
|
|
47
|
+
|
|
48
|
+
当前不是 OpenAI Responses API,也不是 Anthropic Messages API。第三方 Agent 应优先实现
|
|
49
|
+
`openai-completions` / Chat Completions 适配。
|
|
50
|
+
|
|
51
|
+
## 4. API Key 管理
|
|
52
|
+
|
|
53
|
+
推荐环境变量:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
CODEBUDDY_API_KEY=<从 WorkBuddy 获取的 Key>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Windows PowerShell 设置用户环境变量:
|
|
60
|
+
|
|
61
|
+
```powershell
|
|
62
|
+
[Environment]::SetEnvironmentVariable(
|
|
63
|
+
"CODEBUDDY_API_KEY",
|
|
64
|
+
"<在本机填写,不要提交到仓库>",
|
|
65
|
+
"User"
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
新进程才能读取新设置。不要在日志中输出 Key;诊断两个 Key 是否相同时,可以比较
|
|
70
|
+
SHA-256 指纹,而不是打印明文。
|
|
71
|
+
|
|
72
|
+
## 5. 获取当前 Key 可用的模型
|
|
73
|
+
|
|
74
|
+
### 5.1 请求
|
|
75
|
+
|
|
76
|
+
```http
|
|
77
|
+
GET /v3/config HTTP/1.1
|
|
78
|
+
Host: copilot.tencent.com
|
|
79
|
+
Accept: application/json
|
|
80
|
+
X-API-Key: <API_KEY>
|
|
81
|
+
User-Agent: CLI/unknown CodeBuddy/2.136.0
|
|
82
|
+
X-Product: SaaS
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
其中:
|
|
86
|
+
|
|
87
|
+
- `X-API-Key`:模型目录接口的关键鉴权 Header;
|
|
88
|
+
- `User-Agent`:按当前 CodeBuddy CLI 行为填写,建议版本升级时同步验证;
|
|
89
|
+
- `X-Product: SaaS`:标识当前产品部署类型;
|
|
90
|
+
- Header 名大小写不敏感。
|
|
91
|
+
|
|
92
|
+
### 5.2 响应骨架
|
|
93
|
+
|
|
94
|
+
成功时 HTTP 通常为 `200`,同时还要检查业务字段 `code`:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"code": 0,
|
|
99
|
+
"msg": "OK",
|
|
100
|
+
"data": {
|
|
101
|
+
"agents": [
|
|
102
|
+
{
|
|
103
|
+
"name": "cli",
|
|
104
|
+
"models": ["hy3", "glm-5.3", "deepseek-v4-flash"]
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"models": [
|
|
108
|
+
{
|
|
109
|
+
"id": "deepseek-v4-flash",
|
|
110
|
+
"name": "Deepseek-V4-Flash",
|
|
111
|
+
"maxInputTokens": 1000000,
|
|
112
|
+
"maxOutputTokens": 50000,
|
|
113
|
+
"maxAllowedSize": 1000000,
|
|
114
|
+
"supportsImages": true,
|
|
115
|
+
"supportsReasoning": true,
|
|
116
|
+
"onlyReasoning": true,
|
|
117
|
+
"reasoning": {
|
|
118
|
+
"effort": "high",
|
|
119
|
+
"summary": "auto"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
示例只展示与适配有关的字段;实际响应还可能包含企业信息和产品功能开关。
|
|
128
|
+
|
|
129
|
+
### 5.3 正确的模型筛选算法
|
|
130
|
+
|
|
131
|
+
不能直接把 `data.models` 全部暴露给用户。正确流程是:
|
|
132
|
+
|
|
133
|
+
1. 在 `data.agents` 中找到 `name === "cli"` 的 Agent;
|
|
134
|
+
2. 读取该 Agent 的 `models`,这是当前 Key 对 CLI 开放的模型 ID;
|
|
135
|
+
3. 用这些 ID 与 `data.models[].id` 关联;
|
|
136
|
+
4. 丢弃没有容量信息或没有对应配置的异常条目;
|
|
137
|
+
5. 保留服务端顺序,避免客户端自行重排造成默认模型变化。
|
|
138
|
+
|
|
139
|
+
兼容旧响应时,可以同时检查:
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
const agents = Array.isArray(data.agents)
|
|
143
|
+
? data.agents
|
|
144
|
+
: data.agent?.agents;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 5.4 Node.js 获取模型示例
|
|
148
|
+
|
|
149
|
+
Node.js 22 以上可直接使用内置 `fetch`,不需要 SDK:
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
const apiKey = process.env.CODEBUDDY_API_KEY;
|
|
153
|
+
if (!apiKey) throw new Error("缺少 CODEBUDDY_API_KEY");
|
|
154
|
+
|
|
155
|
+
const response = await fetch("https://copilot.tencent.com/v3/config", {
|
|
156
|
+
headers: {
|
|
157
|
+
accept: "application/json",
|
|
158
|
+
"x-api-key": apiKey,
|
|
159
|
+
"user-agent": "CLI/unknown CodeBuddy/2.136.0",
|
|
160
|
+
"x-product": "SaaS",
|
|
161
|
+
},
|
|
162
|
+
signal: AbortSignal.timeout(20_000),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
throw new Error(`模型目录 HTTP ${response.status}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const body = await response.json();
|
|
170
|
+
if (body?.code !== 0) {
|
|
171
|
+
throw new Error(`模型目录业务错误:${body?.msg ?? body?.code}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const agents = Array.isArray(body.data?.agents)
|
|
175
|
+
? body.data.agents
|
|
176
|
+
: body.data?.agent?.agents;
|
|
177
|
+
const allowedIds = agents?.find((agent) => agent?.name === "cli")?.models ?? [];
|
|
178
|
+
const byId = new Map((body.data?.models ?? []).map((model) => [model.id, model]));
|
|
179
|
+
|
|
180
|
+
const models = allowedIds.flatMap((id) => {
|
|
181
|
+
const model = byId.get(id);
|
|
182
|
+
if (!model) return [];
|
|
183
|
+
const contextWindow = model.maxInputTokens ?? model.maxAllowedSize;
|
|
184
|
+
const maxTokens = model.maxOutputTokens;
|
|
185
|
+
if (!Number.isSafeInteger(contextWindow) || !Number.isSafeInteger(maxTokens)) return [];
|
|
186
|
+
return [{
|
|
187
|
+
id,
|
|
188
|
+
name: model.name ?? id,
|
|
189
|
+
contextWindow,
|
|
190
|
+
maxTokens,
|
|
191
|
+
supportsImages: model.supportsImages === true,
|
|
192
|
+
supportsReasoning: model.supportsReasoning === true,
|
|
193
|
+
onlyReasoning: model.onlyReasoning === true,
|
|
194
|
+
defaultReasoningEffort: model.reasoning?.effort,
|
|
195
|
+
thinkingLevelMap: model.thinkingLevelMap,
|
|
196
|
+
thinkingFormat: model.thinkingFormat,
|
|
197
|
+
}];
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
console.table(models);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 6. 为什么不同账号看到的模型不同
|
|
204
|
+
|
|
205
|
+
`agents[name=cli].models` 是 Key 级别的授权结果,不是全平台公共目录。
|
|
206
|
+
|
|
207
|
+
2026-08-14 实测,同一台电脑上的两把有效 Key 返回了不同结果:
|
|
208
|
+
|
|
209
|
+
| 现象 | Key A | Key B |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| GLM 新版本 | 包含 `glm-5.3` | 最高为 `glm-5.2` |
|
|
212
|
+
| MiniMax ID | `minimax-m3-pay` | `minimax-m3` |
|
|
213
|
+
| Kimi ID | `kimi-k3-2` | `kimi-k3-1` |
|
|
214
|
+
|
|
215
|
+
因此,出现“CodeBuddy 能看到 `glm-5.3`,另一个 Agent 看不到”时,先确认两个程序实际使用
|
|
216
|
+
的是不是同一把 Key。不要通过硬编码 `glm-5.3` 解决,否则请求阶段仍会被服务端拒绝。
|
|
217
|
+
|
|
218
|
+
## 7. 发起 Chat Completions 请求
|
|
219
|
+
|
|
220
|
+
### 7.1 请求地址
|
|
221
|
+
|
|
222
|
+
```text
|
|
223
|
+
POST https://copilot.tencent.com/v2/chat/completions
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 7.2 鉴权 Header
|
|
227
|
+
|
|
228
|
+
当前 CodeBuddy CLI 会为模型请求同时准备:
|
|
229
|
+
|
|
230
|
+
```http
|
|
231
|
+
Authorization: Bearer <API_KEY>
|
|
232
|
+
X-API-Key: <API_KEY>
|
|
233
|
+
Content-Type: application/json
|
|
234
|
+
Accept: text/event-stream
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
本项目基于 OpenAI SDK 的适配路径使用 `Authorization: Bearer` 即可完成请求;为了更贴近
|
|
238
|
+
CodeBuddy CLI 并兼容服务端策略变化,独立开发的新客户端建议同时发送 `Authorization`
|
|
239
|
+
和 `X-API-Key`。不要把 Key 放进 URL Query。
|
|
240
|
+
|
|
241
|
+
CodeBuddy CLI 还会添加请求 ID、会话 ID、IDE 名称、产品类型等内部 Header。第三方开发的
|
|
242
|
+
最小客户端不应伪造这些字段;只有在服务端明确要求时再增加。
|
|
243
|
+
|
|
244
|
+
### 7.3 最小请求体
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"model": "deepseek-v4-flash",
|
|
249
|
+
"messages": [
|
|
250
|
+
{ "role": "user", "content": "请只回复:连接成功" }
|
|
251
|
+
],
|
|
252
|
+
"stream": true,
|
|
253
|
+
"stream_options": { "include_usage": true },
|
|
254
|
+
"max_tokens": 1024
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
注意:
|
|
259
|
+
|
|
260
|
+
- 使用 `max_tokens`,不是 `max_completion_tokens`;
|
|
261
|
+
- `model` 必须来自当前 Key 的 CLI 模型列表;
|
|
262
|
+
- 推荐 `stream: true`,这是 CodeBuddy Agent 的主要工作模式;
|
|
263
|
+
- `max_tokens` 不应超过目录中的 `maxOutputTokens`;
|
|
264
|
+
- 输入和预期输出总量不能超过模型上下文限制。
|
|
265
|
+
|
|
266
|
+
## 8. 思考能力必须逐模型处理
|
|
267
|
+
|
|
268
|
+
不能为所有模型统一写死 `high`,也不能假设所有模型都支持 `xhigh` 或 `max`。至少读取:
|
|
269
|
+
|
|
270
|
+
| 字段 | 含义 | 处理方式 |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `supportsReasoning` | 模型是否支持推理 | `false` 时不显示思考档位,不发送推理参数 |
|
|
273
|
+
| `onlyReasoning` | 是否只能以推理模式工作 | `true` 时不提供 `off` |
|
|
274
|
+
| `reasoning.effort` | 该模型的默认思考档位 | 用户未选择时使用;不要拿一个模型的默认值套给其他模型 |
|
|
275
|
+
| `thinkingLevelMap` | UI 档位到线上参数的逐模型映射 | 存在时严格按映射提供选项和转换 |
|
|
276
|
+
| `thinkingFormat` | 推理参数协议 | 存在时按该格式转换;缺失时才使用当前端点的兼容默认 |
|
|
277
|
+
|
|
278
|
+
当前实测就存在不同默认值:部分模型为 `high`,部分模型为 `medium`。
|
|
279
|
+
|
|
280
|
+
### 8.1 选择规则
|
|
281
|
+
|
|
282
|
+
1. 用户显式选择档位:发送该模型声明支持的档位;
|
|
283
|
+
2. 用户选择“默认”或未选择:使用该模型自己的 `reasoning.effort`;
|
|
284
|
+
3. 服务端没有声明默认值:省略思考参数,让服务端决定;
|
|
285
|
+
4. `supportsReasoning === false`:删除所有推理参数;
|
|
286
|
+
5. `onlyReasoning === true`:不要提供 `off`;
|
|
287
|
+
6. `thinkingLevelMap` 存在:未出现在 Map 中的档位视为不支持;
|
|
288
|
+
7. 不要自动把不支持的档位静默替换成另一个档位,最好在请求前报错。
|
|
289
|
+
|
|
290
|
+
### 8.2 OpenAI 风格
|
|
291
|
+
|
|
292
|
+
当前适配使用的默认格式是:
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"reasoning_effort": "medium"
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
常见候选值为:
|
|
301
|
+
|
|
302
|
+
```text
|
|
303
|
+
minimal / low / medium / high / xhigh / max
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
候选值不等于所有模型均支持。以当前模型的能力声明为准。
|
|
307
|
+
|
|
308
|
+
### 8.3 其他思考格式
|
|
309
|
+
|
|
310
|
+
如果未来目录返回不同 `thinkingFormat`,适配器可能需要转换,例如:
|
|
311
|
+
|
|
312
|
+
```json
|
|
313
|
+
{ "reasoning": { "effort": "high" } }
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
或:
|
|
317
|
+
|
|
318
|
+
```json
|
|
319
|
+
{ "thinking": { "type": "enabled" }, "reasoning_effort": "high" }
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
不要同时无条件发送所有格式。应为每个模型只生成一种服务端声明的格式。
|
|
323
|
+
|
|
324
|
+
## 9. 工具调用
|
|
325
|
+
|
|
326
|
+
请求中的工具遵循 OpenAI Chat Completions 格式:
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
{
|
|
330
|
+
"tools": [
|
|
331
|
+
{
|
|
332
|
+
"type": "function",
|
|
333
|
+
"function": {
|
|
334
|
+
"name": "get_weather",
|
|
335
|
+
"description": "查询天气",
|
|
336
|
+
"parameters": {
|
|
337
|
+
"type": "object",
|
|
338
|
+
"properties": {
|
|
339
|
+
"city": { "type": "string" }
|
|
340
|
+
},
|
|
341
|
+
"required": ["city"],
|
|
342
|
+
"additionalProperties": false
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
],
|
|
347
|
+
"tool_choice": "auto"
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
流式响应中的工具参数会分段出现在:
|
|
352
|
+
|
|
353
|
+
```text
|
|
354
|
+
choices[0].delta.tool_calls[].function.arguments
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
客户端必须按 `tool_calls[].index` 累积字符串,结束后再解析 JSON。执行工具后,把结果作为
|
|
358
|
+
`role: "tool"`、带相同 `tool_call_id` 的消息加入下一轮请求。
|
|
359
|
+
|
|
360
|
+
## 10. 图片输入
|
|
361
|
+
|
|
362
|
+
只有 `supportsImages === true` 的模型才能接收图片。OpenAI 兼容格式示例:
|
|
363
|
+
|
|
364
|
+
```json
|
|
365
|
+
{
|
|
366
|
+
"role": "user",
|
|
367
|
+
"content": [
|
|
368
|
+
{ "type": "text", "text": "描述这张图片" },
|
|
369
|
+
{
|
|
370
|
+
"type": "image_url",
|
|
371
|
+
"image_url": {
|
|
372
|
+
"url": "data:image/png;base64,<BASE64>"
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
发送前应限制文件大小和 MIME 类型,不要仅根据扩展名判断图片。
|
|
380
|
+
|
|
381
|
+
## 11. SSE 流式响应解析
|
|
382
|
+
|
|
383
|
+
服务端返回 `text/event-stream`。每个事件通常形如:
|
|
384
|
+
|
|
385
|
+
```text
|
|
386
|
+
data: {"id":"...","choices":[{"delta":{"content":"你"}}]}
|
|
387
|
+
|
|
388
|
+
data: {"id":"...","choices":[{"delta":{"content":"好"},"finish_reason":"stop"}]}
|
|
389
|
+
|
|
390
|
+
data: [DONE]
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
需要处理的字段:
|
|
394
|
+
|
|
395
|
+
| 字段 | 用途 |
|
|
396
|
+
|---|---|
|
|
397
|
+
| `choices[0].delta.content` | 最终回答文本增量 |
|
|
398
|
+
| `choices[0].delta.reasoning_content` | 思考内容增量之一 |
|
|
399
|
+
| `choices[0].delta.reasoning` | 部分模型使用的思考内容字段 |
|
|
400
|
+
| `choices[0].delta.reasoning_text` | 另一种兼容思考字段 |
|
|
401
|
+
| `choices[0].delta.tool_calls` | 工具调用增量 |
|
|
402
|
+
| `choices[0].finish_reason` | `stop`、`length`、`tool_calls` 等结束原因 |
|
|
403
|
+
| `usage` | Token 用量,通常在开启 `include_usage` 后的尾部 Chunk 返回 |
|
|
404
|
+
|
|
405
|
+
同一个 Chunk 可能没有 `choices`,只有 `usage`,不能因此判定响应异常。
|
|
406
|
+
|
|
407
|
+
## 12. 完整 Node.js 流式示例
|
|
408
|
+
|
|
409
|
+
```javascript
|
|
410
|
+
const apiKey = process.env.CODEBUDDY_API_KEY;
|
|
411
|
+
if (!apiKey) throw new Error("缺少 CODEBUDDY_API_KEY");
|
|
412
|
+
|
|
413
|
+
const response = await fetch("https://copilot.tencent.com/v2/chat/completions", {
|
|
414
|
+
method: "POST",
|
|
415
|
+
headers: {
|
|
416
|
+
authorization: `Bearer ${apiKey}`,
|
|
417
|
+
"x-api-key": apiKey,
|
|
418
|
+
"content-type": "application/json",
|
|
419
|
+
accept: "text/event-stream",
|
|
420
|
+
},
|
|
421
|
+
body: JSON.stringify({
|
|
422
|
+
model: "deepseek-v4-flash",
|
|
423
|
+
messages: [{ role: "user", content: "请只回复:连接成功" }],
|
|
424
|
+
stream: true,
|
|
425
|
+
stream_options: { include_usage: true },
|
|
426
|
+
max_tokens: 1024,
|
|
427
|
+
// reasoning_effort: "high", // 只在当前模型明确支持且用户选择时添加
|
|
428
|
+
}),
|
|
429
|
+
signal: AbortSignal.timeout(300_000),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (!response.ok) {
|
|
433
|
+
const detail = await response.text();
|
|
434
|
+
throw new Error(`推理接口 HTTP ${response.status}: ${detail.slice(0, 500)}`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const decoder = new TextDecoder();
|
|
438
|
+
let buffer = "";
|
|
439
|
+
let answer = "";
|
|
440
|
+
let reasoning = "";
|
|
441
|
+
|
|
442
|
+
for await (const chunk of response.body) {
|
|
443
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
444
|
+
let boundary;
|
|
445
|
+
while ((boundary = buffer.indexOf("\n\n")) !== -1) {
|
|
446
|
+
const event = buffer.slice(0, boundary);
|
|
447
|
+
buffer = buffer.slice(boundary + 2);
|
|
448
|
+
|
|
449
|
+
for (const line of event.split(/\r?\n/)) {
|
|
450
|
+
if (!line.startsWith("data:")) continue;
|
|
451
|
+
const data = line.slice(5).trim();
|
|
452
|
+
if (!data || data === "[DONE]") continue;
|
|
453
|
+
|
|
454
|
+
const payload = JSON.parse(data);
|
|
455
|
+
const delta = payload.choices?.[0]?.delta;
|
|
456
|
+
if (typeof delta?.content === "string") {
|
|
457
|
+
answer += delta.content;
|
|
458
|
+
process.stdout.write(delta.content);
|
|
459
|
+
}
|
|
460
|
+
const thought = delta?.reasoning_content ?? delta?.reasoning ?? delta?.reasoning_text;
|
|
461
|
+
if (typeof thought === "string") reasoning += thought;
|
|
462
|
+
if (payload.usage) console.error("\nusage:", payload.usage);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
console.log("\n\nanswer:", answer);
|
|
468
|
+
console.log("reasoning length:", reasoning.length);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
生产代码还需要按 `tool_calls[].index` 累积工具参数,并处理单个 SSE 事件跨网络 Chunk 的情况。
|
|
472
|
+
|
|
473
|
+
## 13. 错误处理与重试
|
|
474
|
+
|
|
475
|
+
### 13.1 模型目录
|
|
476
|
+
|
|
477
|
+
同时检查 HTTP 状态和 JSON 业务状态:
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
481
|
+
if (body.code !== 0) throw new Error(body.msg ?? String(body.code));
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### 13.2 常见分类
|
|
485
|
+
|
|
486
|
+
| 情况 | 建议处理 |
|
|
487
|
+
|---|---|
|
|
488
|
+
| `401` / `403` | Key 无效、过期或无权限;停止重试并要求重新填写 |
|
|
489
|
+
| `408` / 网络断开 | 指数退避重试 |
|
|
490
|
+
| `429` | 遵守 `Retry-After`;提示额度或频率限制 |
|
|
491
|
+
| `5xx` | 短暂退避后有限重试 |
|
|
492
|
+
| 模型不在 CLI 列表 | 刷新目录,不要强制调用 |
|
|
493
|
+
| 流结束但没有结束原因 | 视为不完整响应,不要保存为成功回答 |
|
|
494
|
+
|
|
495
|
+
流式请求只应在“尚未收到任何有效增量”时自动重试。收到文本或工具调用后再次自动重试,
|
|
496
|
+
可能造成重复输出或重复执行工具。
|
|
497
|
+
|
|
498
|
+
### 13.3 推荐超时
|
|
499
|
+
|
|
500
|
+
- 模型目录:20 秒总超时;
|
|
501
|
+
- 建立推理连接:30–60 秒;
|
|
502
|
+
- 流空闲超时:300 秒;
|
|
503
|
+
- 用户取消:通过 `AbortController` 立即向上游传播。
|
|
504
|
+
|
|
505
|
+
## 14. 缓存策略
|
|
506
|
+
|
|
507
|
+
模型目录可以按 Key 指纹缓存,但不能跨 Key 共用:
|
|
508
|
+
|
|
509
|
+
```text
|
|
510
|
+
cache key = SHA-256(API Key) + 客户端产品类型
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
建议:
|
|
514
|
+
|
|
515
|
+
- 缓存 5–15 分钟;
|
|
516
|
+
- 用户点击“刷新模型”时绕过缓存;
|
|
517
|
+
- 更换 Key 后立即清除旧缓存;
|
|
518
|
+
- 缓存失败时可以使用最近一次成功目录,但 UI 必须标注“可能过期”;
|
|
519
|
+
- 不要把完整 Key 写进缓存键或日志。
|
|
520
|
+
|
|
521
|
+
## 15. 适配其他 Agent 的最小接口
|
|
522
|
+
|
|
523
|
+
一个可维护的 Provider 只需要四个职责:
|
|
524
|
+
|
|
525
|
+
```text
|
|
526
|
+
resolveCredential() -> 安全读取 Key
|
|
527
|
+
discoverModels() -> 获取并按 CLI 权限过滤模型
|
|
528
|
+
describeModel(id) -> 返回该模型容量、模态、思考档位和默认值
|
|
529
|
+
stream(request) -> 转换消息并解析 SSE
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
DSH、OpenCode 或其他 Agent 自己负责:
|
|
533
|
+
|
|
534
|
+
- Agent 循环;
|
|
535
|
+
- 上下文裁剪与压缩;
|
|
536
|
+
- 工具实际执行;
|
|
537
|
+
- 权限确认;
|
|
538
|
+
- 会话持久化;
|
|
539
|
+
- 重试策略和用户取消。
|
|
540
|
+
|
|
541
|
+
CodeBuddy 服务端负责模型推理,不会替第三方 Agent 自动执行本地工具。
|
|
542
|
+
|
|
543
|
+
## 16. 验收清单
|
|
544
|
+
|
|
545
|
+
开发完成后至少验证:
|
|
546
|
+
|
|
547
|
+
- [ ] Key 不出现在源码、Git、日志和异常堆栈中;
|
|
548
|
+
- [ ] 两把不同 Key 的模型目录不会串用;
|
|
549
|
+
- [ ] 只显示 `agents[name=cli].models` 授权的模型;
|
|
550
|
+
- [ ] 新模型无需发布新代码即可出现;
|
|
551
|
+
- [ ] 旧模型下线后不会继续从缓存永久显示;
|
|
552
|
+
- [ ] 上下文窗口和最大输出来自逐模型字段;
|
|
553
|
+
- [ ] 非图片模型会在请求前拒绝图片;
|
|
554
|
+
- [ ] 非推理模型不显示思考控件;
|
|
555
|
+
- [ ] `onlyReasoning` 模型不显示 `off`;
|
|
556
|
+
- [ ] 每个模型使用自己的默认思考档位;
|
|
557
|
+
- [ ] 用户显式档位会转换为该模型自己的线上值;
|
|
558
|
+
- [ ] 能累计文本、思考和工具调用 SSE 增量;
|
|
559
|
+
- [ ] 用户取消能中止网络请求;
|
|
560
|
+
- [ ] 429 和 5xx 有限重试,不会无限循环;
|
|
561
|
+
- [ ] 工具调用不会因自动重试而重复执行。
|
|
562
|
+
|
|
563
|
+
## 17. 版本漂移检查
|
|
564
|
+
|
|
565
|
+
升级 CodeBuddy CLI 后,应重新核对:
|
|
566
|
+
|
|
567
|
+
1. `product.json` 和 CLI 版本;
|
|
568
|
+
2. 配置接口是否仍为 `/v3/config`;
|
|
569
|
+
3. 推理接口是否仍为 `/v2/chat/completions`;
|
|
570
|
+
4. 鉴权是否仍接受 `Authorization` / `X-API-Key`;
|
|
571
|
+
5. 模型能力字段是否新增 `thinkingLevelMap`、`thinkingFormat` 等;
|
|
572
|
+
6. SSE 推理和工具字段是否变化;
|
|
573
|
+
7. 当前 Key 实际返回的 CLI 模型目录。
|
|
574
|
+
|
|
575
|
+
本项目的可执行实现位于 [`index.js`](../index.js),安装和使用说明见
|
|
576
|
+
[`README.md`](../README.md)。
|