agent-bridge-dsh 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/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # Temporary Holding Version
1
+ # DeepSeek Harness 插件
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 供 `mu-agent-bridge` 使用的 DSH 插件。安装:`npm install -g agent-bridge-dsh`。运行前需要在本机安装并配置 DeepSeek Harness。
@@ -0,0 +1,8 @@
1
+ {
2
+ "type": "object",
3
+ "properties": {
4
+ "aid": { "type": "string", "description": "复用的 EvolCore 已有 AID" },
5
+ "patchPath": { "type": "string", "description": "DeepSeek Harness patch 文件路径" }
6
+ },
7
+ "additionalProperties": false
8
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentError = void 0;
4
+ class AgentError extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = "AgentError";
8
+ }
9
+ }
10
+ exports.AgentError = AgentError;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DshAgent = void 0;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const agent_1 = require("./agent");
6
+ const dsh_paths_1 = require("./dsh-paths");
7
+ class DshAgent {
8
+ name = "dsh";
9
+ paths;
10
+ spawnProcess;
11
+ defaultTimeoutMs;
12
+ constructor(options) {
13
+ this.paths = options.paths ?? (0, dsh_paths_1.resolveDshPaths)(options.patchPath);
14
+ this.spawnProcess = options.spawn ?? node_child_process_1.spawn;
15
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? 300_000;
16
+ }
17
+ async send(request) {
18
+ if (!request.task.trim())
19
+ throw new agent_1.AgentError("DSH task must not be empty");
20
+ const args = ["--expose-internals", this.paths.cli, "--profile", "headless", "--patch", this.paths.patch, "--json"];
21
+ if (request.sessionId)
22
+ args.push("--session-id", request.sessionId);
23
+ args.push("-");
24
+ const child = this.spawnProcess(this.paths.executable, args, {
25
+ cwd: request.cwd,
26
+ env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" },
27
+ stdio: ["pipe", "pipe", "pipe"],
28
+ });
29
+ child.stdin.end(request.task, "utf8");
30
+ let output = "";
31
+ let errors = "";
32
+ let sessionId = request.sessionId;
33
+ let finalText = "";
34
+ child.stdout.setEncoding?.("utf8");
35
+ child.stderr.setEncoding?.("utf8");
36
+ child.stdout.on("data", (chunk) => {
37
+ output += chunk;
38
+ for (const line of output.split(/\r?\n/).slice(0, -1)) {
39
+ try {
40
+ const event = JSON.parse(line);
41
+ if (event.type === "session" && event.sessionId)
42
+ sessionId = event.sessionId;
43
+ if (event.type === "final" && event.text)
44
+ finalText = event.text.trim();
45
+ }
46
+ catch { /* diagnostics and partial lines are ignored */ }
47
+ }
48
+ output = output.split(/\r?\n/).at(-1) ?? "";
49
+ });
50
+ child.stderr.on("data", (chunk) => { errors += chunk; });
51
+ await new Promise((resolve, reject) => {
52
+ const timer = setTimeout(() => {
53
+ child.kill();
54
+ reject(new agent_1.AgentError(`DSH task timed out after ${request.timeoutMs ?? this.defaultTimeoutMs}ms`));
55
+ }, request.timeoutMs ?? this.defaultTimeoutMs);
56
+ child.once("error", (error) => { clearTimeout(timer); reject(new agent_1.AgentError("failed to start DSH", { cause: error })); });
57
+ child.once("close", (code) => {
58
+ clearTimeout(timer);
59
+ if (code !== 0 && !finalText)
60
+ reject(new agent_1.AgentError(errors.trim() || `DSH exited with code ${code}`));
61
+ else
62
+ resolve();
63
+ });
64
+ });
65
+ if (!finalText)
66
+ throw new agent_1.AgentError("DSH returned no final text");
67
+ return { text: finalText, sessionId };
68
+ }
69
+ }
70
+ exports.DshAgent = DshAgent;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveDshPaths = resolveDshPaths;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ function resolveDshPaths(patch) {
7
+ const override = process.env.DSH_INSTALL_DIR;
8
+ const candidates = [override, "D:\\Programs\\DeepSeek Harness", "D:\\DSH"].filter((value) => Boolean(value));
9
+ const root = candidates.find((candidate) => (0, node_fs_1.existsSync)((0, node_path_1.join)(candidate, "DeepSeek Harness.exe"))) ?? candidates[0];
10
+ return {
11
+ executable: (0, node_path_1.join)(root, "DeepSeek Harness.exe"),
12
+ cli: (0, node_path_1.join)(root, "resources", "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-desktop-host", "lib", "cli.js"),
13
+ patch,
14
+ };
15
+ }
@@ -0,0 +1,5 @@
1
+ # Select the account-backed model used by the DeepSeek Harness desktop login.
2
+ - id: agent-default-model
3
+ config:
4
+ provider: deepseek-account
5
+ model: deepseek-chat
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DshPlugin = exports.dshManifest = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const dsh_agent_1 = require("../../dsh-agent");
6
+ const openai_adapter_1 = require("../qwenwork/openai-adapter");
7
+ exports.dshManifest = {
8
+ id: "dsh",
9
+ version: "1.0.0",
10
+ displayName: "DeepSeek Harness",
11
+ entry: "dist/index.js",
12
+ apiVersion: "1",
13
+ process: "in-process",
14
+ capabilities: ["chat", "health"],
15
+ configSchema: "config.schema.json",
16
+ };
17
+ class DshPlugin {
18
+ manifest = exports.dshManifest;
19
+ agent;
20
+ status = "failed";
21
+ message = "插件尚未启动";
22
+ adapter;
23
+ unregister = [];
24
+ async activate(context) {
25
+ const configured = context.config.patchPath;
26
+ const patchPath = typeof configured === "string" && configured.trim()
27
+ ? configured
28
+ : (0, node_path_1.resolve)(__dirname, "..", "..", "headless-account.patch.yml");
29
+ this.agent = new dsh_agent_1.DshAgent({ patchPath });
30
+ this.adapter = new openai_adapter_1.OpenAIAdapter((prompt) => this.handle({ task: prompt, timeoutMs: 240_000 }).then((result) => result.text), { model: "dsh", prefix: "/dsh/v1", onExchange: (direction, text, peer, agentAid) => context.logger?.info?.("DSH 通信", { direction, text, peer, agentAid }) });
31
+ if (context.http) {
32
+ for (const [method, path] of [["GET", "/dsh/v1/models"], ["POST", "/dsh/v1/chat/completions"], ["OPTIONS", "/dsh/v1/chat/completions"]]) {
33
+ const dispose = context.http.register({ method, path, handler: (req, res) => this.adapter.handle(req, res) });
34
+ if (typeof dispose === "function")
35
+ this.unregister.push(dispose);
36
+ }
37
+ }
38
+ this.status = "ready";
39
+ this.message = "";
40
+ context.logger?.info?.("dsh plugin activated");
41
+ }
42
+ async deactivate() {
43
+ for (const dispose of this.unregister.splice(0))
44
+ dispose();
45
+ await this.adapter?.close();
46
+ this.adapter = undefined;
47
+ this.agent = undefined;
48
+ this.status = "failed";
49
+ this.message = "插件已停止";
50
+ }
51
+ async health() {
52
+ return { status: this.status, ...(this.message ? { message: this.message } : {}), details: { adapter: "DshAgent" } };
53
+ }
54
+ async handle(request) {
55
+ if (!this.agent || this.status !== "ready")
56
+ throw new Error("dsh 插件未就绪");
57
+ return this.agent.send(request);
58
+ }
59
+ }
60
+ exports.DshPlugin = DshPlugin;
61
+ exports.default = DshPlugin;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenAIAdapter = void 0;
4
+ exports.messageText = messageText;
5
+ const node_http_1 = require("node:http");
6
+ const node_crypto_1 = require("node:crypto");
7
+ function json(res, status, value) {
8
+ const body = JSON.stringify(value);
9
+ res.statusCode = status;
10
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
11
+ res.setHeader("Content-Length", Buffer.byteLength(body));
12
+ res.end(body);
13
+ }
14
+ function messageText(content) {
15
+ if (typeof content === "string")
16
+ return content;
17
+ if (Array.isArray(content)) {
18
+ return content.map((part) => {
19
+ if (typeof part === "string")
20
+ return part;
21
+ if (part && typeof part === "object" && "text" in part)
22
+ return String(part.text ?? "");
23
+ return "";
24
+ }).join("");
25
+ }
26
+ return content == null ? "" : String(content);
27
+ }
28
+ async function readBody(req) {
29
+ const chunks = [];
30
+ let size = 0;
31
+ for await (const chunk of req) {
32
+ const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
33
+ size += data.length;
34
+ if (size > 2 * 1024 * 1024)
35
+ throw new Error("请求体超过 2 MiB");
36
+ chunks.push(data);
37
+ }
38
+ return Buffer.concat(chunks).toString("utf8");
39
+ }
40
+ /** OpenAI-compatible HTTP adapter used by qwenwork and the platform router. */
41
+ class OpenAIAdapter {
42
+ complete;
43
+ model;
44
+ onLog;
45
+ onExchange;
46
+ prefix;
47
+ server;
48
+ constructor(complete, options = {}) {
49
+ this.complete = complete;
50
+ this.model = options.model ?? "qwenwork";
51
+ this.onLog = options.onLog;
52
+ this.onExchange = options.onExchange;
53
+ this.prefix = (options.prefix ?? "/v1").replace(/\/+$/, "") || "/v1";
54
+ }
55
+ models() {
56
+ return { object: "list", data: [{ id: this.model, object: "model", owned_by: "qwenwork" }] };
57
+ }
58
+ async handle(req, res) {
59
+ const requestPath = new URL(req.url ?? "/", "http://127.0.0.1").pathname;
60
+ res.setHeader("Access-Control-Allow-Origin", "*");
61
+ if (req.method === "OPTIONS") {
62
+ res.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
63
+ res.statusCode = 204;
64
+ res.end();
65
+ return;
66
+ }
67
+ if (req.method === "GET" && requestPath === `${this.prefix}/models`) {
68
+ json(res, 200, this.models());
69
+ return;
70
+ }
71
+ if (req.method !== "POST" || requestPath !== `${this.prefix}/chat/completions`) {
72
+ json(res, 404, { error: { message: "Not found", type: "invalid_request_error" } });
73
+ return;
74
+ }
75
+ const abort = new AbortController();
76
+ const peer = req.headers["x-aun-peer"];
77
+ const peerId = typeof peer === "string" && peer.length <= 256 ? peer : undefined;
78
+ const receivingAid = req.headers["x-evolcore-agent-aid"];
79
+ const agentAid = typeof receivingAid === "string" && receivingAid.length <= 256 ? receivingAid : undefined;
80
+ const onClose = () => { if (!res.writableEnded)
81
+ abort.abort(); };
82
+ res.once("close", onClose);
83
+ try {
84
+ const payload = JSON.parse(await readBody(req));
85
+ const lastUser = [...(payload.messages ?? [])].reverse().find((message) => message.role === "user");
86
+ const prompt = messageText(lastUser?.content);
87
+ if (!prompt.trim()) {
88
+ json(res, 400, { error: { message: "messages 中没有 user 内容", type: "invalid_request_error" } });
89
+ return;
90
+ }
91
+ this.onLog?.(`chat request model=${payload.model ?? this.model} stream=${Boolean(payload.stream)}`);
92
+ this.onExchange?.("request", prompt, peerId, agentAid);
93
+ const content = await this.complete(prompt, abort.signal);
94
+ this.onExchange?.("reply", content, peerId, agentAid);
95
+ if (abort.signal.aborted || res.destroyed)
96
+ return;
97
+ const now = Math.floor(Date.now() / 1000);
98
+ const id = `chatcmpl-${(0, node_crypto_1.randomUUID)()}`;
99
+ const model = payload.model ?? this.model;
100
+ if (payload.stream) {
101
+ res.statusCode = 200;
102
+ res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
103
+ res.setHeader("Cache-Control", "no-cache");
104
+ res.setHeader("Connection", "keep-alive");
105
+ res.write(`data: ${JSON.stringify({ id, object: "chat.completion.chunk", created: now, model, choices: [{ index: 0, delta: { role: "assistant", content }, finish_reason: null }] })}\n\n`);
106
+ res.write(`data: ${JSON.stringify({ id, object: "chat.completion.chunk", created: now, model, choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}\n\n`);
107
+ res.end("data: [DONE]\n\n");
108
+ return;
109
+ }
110
+ json(res, 200, {
111
+ id, object: "chat.completion", created: now, model,
112
+ choices: [{ index: 0, message: { role: "assistant", content }, finish_reason: "stop" }],
113
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
114
+ });
115
+ }
116
+ catch (error) {
117
+ this.onExchange?.("error", error instanceof Error ? error.message : String(error), peerId, agentAid);
118
+ if (abort.signal.aborted || res.destroyed)
119
+ return;
120
+ json(res, 500, { error: { message: error instanceof Error ? error.message : String(error), type: "server_error" } });
121
+ }
122
+ finally {
123
+ res.off("close", onClose);
124
+ }
125
+ }
126
+ createServer() {
127
+ return (0, node_http_1.createServer)((req, res) => void this.handle(req, res));
128
+ }
129
+ async listen(port = 17893, host = "127.0.0.1") {
130
+ if (this.server)
131
+ return;
132
+ this.server = this.createServer();
133
+ await new Promise((resolve, reject) => {
134
+ this.server.once("error", reject);
135
+ this.server.listen(port, host, () => resolve());
136
+ });
137
+ }
138
+ async close() {
139
+ const server = this.server;
140
+ this.server = undefined;
141
+ if (!server)
142
+ return;
143
+ await new Promise((resolve) => server.close(() => resolve()));
144
+ }
145
+ }
146
+ exports.OpenAIAdapter = OpenAIAdapter;
package/package.json CHANGED
@@ -1,6 +1,23 @@
1
1
  {
2
2
  "name": "agent-bridge-dsh",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "agent-bridge 的 DeepSeek Harness 插件。",
5
+ "repository": { "type": "git", "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git", "directory": "packages/dsh" },
6
+ "main": "dist/src-ts/plugins/dsh/index.js",
7
+ "files": [
8
+ "dist/",
9
+ "plugin.json",
10
+ "config.schema.json",
11
+ "README.md"
12
+ ],
13
+ "agentBridge": {
14
+ "plugin": "./plugin.json"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org"
19
+ },
20
+ "engines": {
21
+ "node": ">=22"
22
+ }
23
+ }
package/plugin.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "dsh",
3
+ "version": "1.0.0",
4
+ "displayName": "DeepSeek Harness",
5
+ "entry": "dist/src-ts/plugins/dsh/index.js",
6
+ "apiVersion": "1",
7
+ "process": "in-process",
8
+ "capabilities": ["chat", "health"],
9
+ "configSchema": "config.schema.json"
10
+ }