dsh-llm-workbuddy 0.1.7 → 0.1.9
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 +6 -3
- package/lib/index.js +59 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
1. **模型能力**:Web 界面的模型选择器(composer 模型菜单或 `/model` 命令)会多出
|
|
17
17
|
一个 **WorkBuddy** 分组,模型(DeepSeek-V4、GLM-5.x、Kimi-K2.x、MiniMax-M3、
|
|
18
18
|
Hy3、Hunyuan…)随账号可用列表实时同步,点一下即可切换;支持推理的模型还会
|
|
19
|
-
|
|
19
|
+
显示**推理等级**选择器(Low / Medium / High)。
|
|
20
20
|
2. **Web 登录状态小组件**:在 Web GUI 右下角常驻一个状态胶囊,**实时显示登录/
|
|
21
21
|
代理状态**,未登录时一键在新标签页打开 WorkBuddy 登录页,登录完成后自动变绿。
|
|
22
22
|
无需再回到终端手动跑登录脚本。
|
|
@@ -302,9 +302,12 @@ DSH 的 `dsh.client` 机制只要求 `package.json` 里:
|
|
|
302
302
|
|
|
303
303
|
## 限制
|
|
304
304
|
|
|
305
|
-
-
|
|
305
|
+
- 图片输入:声明支持图片的模型(`inputModalities: ["text", "image"]`,如 deepseek-v4-pro、
|
|
306
|
+
glm-5.2、kimi-k2.x、hy3 等)可附带图片,适配器会通过 DSH 的附件服务把图片编码为
|
|
307
|
+
`data:<mime>;base64,<bytes>` 以 OpenAI `image_url` 格式透传给代理。若附件服务不可用
|
|
308
|
+
(headless 等无附件场景),图片输入会以 `UNSUPPORTED_CONTENT` 稳定报错。
|
|
306
309
|
- 推理等级(reasoning effort):支持推理的模型(如 DeepSeek-V4、GLM、Kimi、MiniMax、
|
|
307
|
-
Hy3
|
|
310
|
+
Hy3 等)会显示推理等级下拉(Low / Medium / High),默认值取平台默认强度。`reasoning_effort`
|
|
308
311
|
会透传给代理;若某模型平台侧只接受平台默认、忽略该参数,则退化为平台默认强度,不影响出字。
|
|
309
312
|
- 代理未运行时,模型请求会以 `TRANSPORT` 错误快速失败(连接被拒绝);但状态
|
|
310
313
|
小组件本身不依赖代理——代理挂了它仍能显示「代理未运行」并允许触发登录。
|
package/lib/index.js
CHANGED
|
@@ -97,9 +97,9 @@ const DEFAULT_MODELS = [
|
|
|
97
97
|
|
|
98
98
|
/** Selectable reasoning efforts exposed to the harness UI, in display order. */
|
|
99
99
|
const REASONING_EFFORTS = [
|
|
100
|
-
{ id: "low", name: "
|
|
101
|
-
{ id: "medium", name: "
|
|
102
|
-
{ id: "high", name: "
|
|
100
|
+
{ id: "low", name: "Low" },
|
|
101
|
+
{ id: "medium", name: "Medium" },
|
|
102
|
+
{ id: "high", name: "High" },
|
|
103
103
|
];
|
|
104
104
|
|
|
105
105
|
/**
|
|
@@ -169,14 +169,29 @@ function serializeAssistant(message) {
|
|
|
169
169
|
/**
|
|
170
170
|
* Serialize the harness conversation into OpenAI chat-completions wire
|
|
171
171
|
* messages. `tool-result` blocks become standalone `{role: 'tool'}` messages;
|
|
172
|
-
* image
|
|
172
|
+
* image blocks are read through the durable attachment service and emitted as
|
|
173
|
+
* OpenAI `image_url` parts (`data:<mime>;base64,<bytes>`), which the
|
|
174
|
+
* workbuddy2api proxy passes through to the upstream platform. When the
|
|
175
|
+
* attachment service is unavailable, image input degrades to the stable
|
|
176
|
+
* `UNSUPPORTED_CONTENT` error.
|
|
173
177
|
*/
|
|
174
|
-
function serializeMessages(messages) {
|
|
178
|
+
async function serializeMessages(messages, attachments, signal) {
|
|
179
|
+
const refs = new Map();
|
|
180
|
+
for (const message of messages) collectImageRefs(message.content, refs);
|
|
181
|
+
const requestImages = new Map();
|
|
182
|
+
if (refs.size > 0) {
|
|
183
|
+
if (attachments === undefined) {
|
|
184
|
+
throw new LlmError("WorkBuddy image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
185
|
+
}
|
|
186
|
+
const policy = { maxPixels: 4_000_000, maxBytes: 20_000_000 };
|
|
187
|
+
const ordered = [...refs.values()];
|
|
188
|
+
const prepared = await Promise.all(ordered.map((ref) => attachments.readImageRequest(ref, policy, signal)));
|
|
189
|
+
for (let index = 0; index < ordered.length; index += 1) {
|
|
190
|
+
requestImages.set(ordered[index].attachmentId, prepared[index]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
175
193
|
const wire = [];
|
|
176
194
|
for (const message of messages) {
|
|
177
|
-
if (message.content.some((block) => block.type === "image")) {
|
|
178
|
-
throw new LlmError("The WorkBuddy adapter does not support image content yet.", "UNSUPPORTED_CONTENT");
|
|
179
|
-
}
|
|
180
195
|
if (message.role === "system") {
|
|
181
196
|
wire.push({ role: "system", content: flattenText(message.content) });
|
|
182
197
|
continue;
|
|
@@ -187,7 +202,26 @@ function serializeMessages(messages) {
|
|
|
187
202
|
}
|
|
188
203
|
const toolResults = message.content.filter((block) => block.type === "tool-result");
|
|
189
204
|
const text = flattenText(message.content);
|
|
190
|
-
|
|
205
|
+
const images = message.content.filter((block) => block.type === "image");
|
|
206
|
+
if (text.length > 0 || toolResults.length === 0) {
|
|
207
|
+
if (images.length > 0) {
|
|
208
|
+
const parts = [];
|
|
209
|
+
if (text.length > 0) parts.push({ type: "text", text });
|
|
210
|
+
for (const block of images) {
|
|
211
|
+
const version = requestImages.get(block.attachment.attachmentId);
|
|
212
|
+
if (version === undefined) {
|
|
213
|
+
throw new LlmError("WorkBuddy image input missing attachment bytes", "UNSUPPORTED_CONTENT");
|
|
214
|
+
}
|
|
215
|
+
parts.push({
|
|
216
|
+
type: "image_url",
|
|
217
|
+
image_url: { url: `data:${version.mediaType};base64,${Buffer.from(version.data).toString("base64")}` },
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
wire.push({ role: "user", content: parts });
|
|
221
|
+
} else {
|
|
222
|
+
wire.push({ role: "user", content: text });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
191
225
|
for (const result of toolResults) {
|
|
192
226
|
wire.push({ role: "tool", tool_call_id: result.toolCallId, content: flattenText(result.content) || "(no output)" });
|
|
193
227
|
}
|
|
@@ -195,11 +229,19 @@ function serializeMessages(messages) {
|
|
|
195
229
|
return wire;
|
|
196
230
|
}
|
|
197
231
|
|
|
232
|
+
/** Collect image attachment refs from a content block list (recursing into tool results). */
|
|
233
|
+
function collectImageRefs(blocks, refs) {
|
|
234
|
+
for (const block of blocks) {
|
|
235
|
+
if (block.type === "image") refs.set(block.attachment.attachmentId, block.attachment);
|
|
236
|
+
else if (block.type === "tool-result") collectImageRefs(block.content, refs);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
198
240
|
/** Build the full wire request. Always streaming with usage reporting on. */
|
|
199
|
-
function serializeRequest(options) {
|
|
241
|
+
async function serializeRequest(options, attachments, signal) {
|
|
200
242
|
const messages = [];
|
|
201
243
|
if (options.system !== undefined) messages.push({ role: "system", content: options.system });
|
|
202
|
-
messages.push(...serializeMessages(options.messages));
|
|
244
|
+
messages.push(...await serializeMessages(options.messages, attachments, signal));
|
|
203
245
|
const tools = options.tools?.map((tool) => ({
|
|
204
246
|
type: "function",
|
|
205
247
|
function: { name: tool.name, description: tool.description, parameters: tool.parameters },
|
|
@@ -588,7 +630,8 @@ export class WorkBuddyAdapter extends LlmAdapter {
|
|
|
588
630
|
}
|
|
589
631
|
|
|
590
632
|
async *request(options, signal, connection, onComment) {
|
|
591
|
-
const
|
|
633
|
+
const attachments = this.config.resolveAttachments?.();
|
|
634
|
+
const body = await serializeRequest(options, attachments, signal);
|
|
592
635
|
const headers = {
|
|
593
636
|
"content-type": "application/json",
|
|
594
637
|
"accept": "text/event-stream",
|
|
@@ -1053,7 +1096,10 @@ export function apply(ctx, config) {
|
|
|
1053
1096
|
}
|
|
1054
1097
|
};
|
|
1055
1098
|
options();
|
|
1056
|
-
const adapter = new WorkBuddyAdapter({
|
|
1099
|
+
const adapter = new WorkBuddyAdapter({
|
|
1100
|
+
options,
|
|
1101
|
+
resolveAttachments: () => ctx.get("attachments"),
|
|
1102
|
+
});
|
|
1057
1103
|
ctx.llm.registerConfigurableProviders([{
|
|
1058
1104
|
provider: PROVIDER,
|
|
1059
1105
|
displayName: "WorkBuddy",
|
package/package.json
CHANGED