dsh-deeptutor 0.1.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 +101 -0
- package/README.zh.md +85 -0
- package/cordis.patch.yml +17 -0
- package/lib/cli-exec.d.ts +28 -0
- package/lib/cli-exec.js +188 -0
- package/lib/config.d.ts +20 -0
- package/lib/config.js +58 -0
- package/lib/html-render.d.ts +14 -0
- package/lib/html-render.js +41 -0
- package/lib/http-api.d.ts +10 -0
- package/lib/http-api.js +77 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +384 -0
- package/lib/turn.d.ts +24 -0
- package/lib/turn.js +159 -0
- package/package.json +53 -0
- package/scripts/md-to-html.js +353 -0
- package/src/cli-exec.ts +251 -0
- package/src/config.ts +70 -0
- package/src/html-render.ts +53 -0
- package/src/http-api.ts +83 -0
- package/src/index.ts +449 -0
- package/src/turn.ts +174 -0
package/src/turn.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepTutor bridge: one learning "turn" — running a capability over WebSocket (HTTP mode)
|
|
3
|
+
* or the CLI (fallback), and shared JSONL/WS event parsing + result formatting.
|
|
4
|
+
*/
|
|
5
|
+
import { API_BASE, truncate } from "./config.ts";
|
|
6
|
+
import { runCli } from "./cli-exec.ts";
|
|
7
|
+
|
|
8
|
+
export interface TurnResult {
|
|
9
|
+
answer: string;
|
|
10
|
+
errors: string[];
|
|
11
|
+
toolCalls: string[];
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Fields both wsRunTurn and cliRunTurn read off the deeptutor_run tool params. */
|
|
17
|
+
export interface RunParams {
|
|
18
|
+
prompt: string;
|
|
19
|
+
capability: string;
|
|
20
|
+
kbs?: string[];
|
|
21
|
+
tools?: string[];
|
|
22
|
+
session_id?: string;
|
|
23
|
+
language?: string;
|
|
24
|
+
config?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Fold one event (WS or CLI JSONL) into the running turn state. Exported for unit tests. */
|
|
28
|
+
export function collectEvent(state: TurnResult, e: any) {
|
|
29
|
+
if (e.type === "content" && typeof e.content === "string") state.answer += e.content;
|
|
30
|
+
else if (e.type === "result" && typeof e.metadata?.response === "string")
|
|
31
|
+
state.answer = e.metadata.response;
|
|
32
|
+
else if (e.type === "error")
|
|
33
|
+
state.errors.push(typeof e.content === "string" ? e.content : JSON.stringify(e.metadata ?? ""));
|
|
34
|
+
else if (e.type === "tool_call")
|
|
35
|
+
state.toolCalls.push(
|
|
36
|
+
typeof e.content === "string" ? e.content : String(e.metadata?.name ?? ""),
|
|
37
|
+
);
|
|
38
|
+
else if (e.type === "session_meta" && e.metadata?.title) state.title = e.metadata.title;
|
|
39
|
+
if (!state.sessionId && typeof e.session_id === "string") state.sessionId = e.session_id;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Execute one turn over WebSocket (streaming). */
|
|
43
|
+
export function wsRunTurn(
|
|
44
|
+
params: RunParams,
|
|
45
|
+
timeoutMs: number,
|
|
46
|
+
signal?: AbortSignal,
|
|
47
|
+
): Promise<TurnResult> {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
if (signal?.aborted) return reject(new Error("Aborted"));
|
|
50
|
+
const WSImpl: any = (globalThis as any).WebSocket;
|
|
51
|
+
if (!WSImpl) return reject(new Error("WebSocket is not available in this environment"));
|
|
52
|
+
const ws = new WSImpl(`${API_BASE.replace(/^http/, "ws")}/api/v1/ws`);
|
|
53
|
+
let settled = false;
|
|
54
|
+
const state: TurnResult = { answer: "", errors: [], toolCalls: [] };
|
|
55
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
56
|
+
const finish = (fn: () => void) => {
|
|
57
|
+
if (settled) return;
|
|
58
|
+
settled = true;
|
|
59
|
+
if (timer) clearTimeout(timer);
|
|
60
|
+
signal?.removeEventListener("abort", onAbort);
|
|
61
|
+
fn();
|
|
62
|
+
};
|
|
63
|
+
timer = setTimeout(() => {
|
|
64
|
+
finish(() => {
|
|
65
|
+
try {
|
|
66
|
+
ws.close();
|
|
67
|
+
} catch {}
|
|
68
|
+
reject(new Error(`WebSocket timeout (${Math.round(timeoutMs / 1000)}s)`));
|
|
69
|
+
});
|
|
70
|
+
}, timeoutMs);
|
|
71
|
+
const onAbort = () => {
|
|
72
|
+
finish(() => {
|
|
73
|
+
try {
|
|
74
|
+
ws.close();
|
|
75
|
+
} catch {}
|
|
76
|
+
reject(new Error("Aborted"));
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
80
|
+
ws.onopen = () => {
|
|
81
|
+
ws.send(
|
|
82
|
+
JSON.stringify({
|
|
83
|
+
type: "start_turn",
|
|
84
|
+
content: params.prompt,
|
|
85
|
+
capability: params.capability,
|
|
86
|
+
session_id: params.session_id ?? null,
|
|
87
|
+
tools: params.tools ?? [],
|
|
88
|
+
knowledge_bases: params.kbs ?? [],
|
|
89
|
+
language: params.language ?? "en",
|
|
90
|
+
config: params.config ?? {},
|
|
91
|
+
notebook_references: [],
|
|
92
|
+
history_references: [],
|
|
93
|
+
attachments: [],
|
|
94
|
+
skills: [],
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
ws.onmessage = (ev: any) => {
|
|
99
|
+
let e: any;
|
|
100
|
+
try {
|
|
101
|
+
e = JSON.parse(ev.data);
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
collectEvent(state, e);
|
|
106
|
+
if (e.type === "done") {
|
|
107
|
+
finish(() => {
|
|
108
|
+
try {
|
|
109
|
+
ws.close();
|
|
110
|
+
} catch {}
|
|
111
|
+
resolve(state);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
ws.onerror = () => {
|
|
116
|
+
finish(() => {
|
|
117
|
+
try {
|
|
118
|
+
ws.close();
|
|
119
|
+
} catch {}
|
|
120
|
+
reject(new Error("WebSocket connection error (server not running or tunnel down)"));
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
ws.onclose = () => {
|
|
124
|
+
finish(() => {
|
|
125
|
+
reject(new Error("WebSocket closed unexpectedly"));
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Execute one turn via the CLI (local or SSH). */
|
|
132
|
+
export async function cliRunTurn(
|
|
133
|
+
params: RunParams,
|
|
134
|
+
timeoutMs: number,
|
|
135
|
+
signal?: AbortSignal,
|
|
136
|
+
): Promise<TurnResult> {
|
|
137
|
+
const argv = ["run", params.capability, params.prompt, "--format", "json"];
|
|
138
|
+
for (const kb of params.kbs ?? []) argv.push("--kb", kb);
|
|
139
|
+
for (const t of params.tools ?? []) argv.push("--tool", t);
|
|
140
|
+
if (params.session_id) argv.push("--session", params.session_id);
|
|
141
|
+
if (params.language) argv.push("--language", params.language);
|
|
142
|
+
for (const [k, v] of Object.entries(params.config ?? {}))
|
|
143
|
+
argv.push("--config", `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`);
|
|
144
|
+
const { stdout, stderr, code, killed } = await runCli(argv, { signal, timeoutMs });
|
|
145
|
+
if (killed) throw new Error(`CLI execution timed out (${Math.round(timeoutMs / 1000)}s)`);
|
|
146
|
+
if (code !== 0) throw new Error(`CLI exit=${code}: ${stderr || "(no stderr)"}`);
|
|
147
|
+
const state: TurnResult = { answer: "", errors: [], toolCalls: [] };
|
|
148
|
+
for (const line of stdout.split("\n")) {
|
|
149
|
+
if (!line.trim()) continue;
|
|
150
|
+
let e: any;
|
|
151
|
+
try {
|
|
152
|
+
e = JSON.parse(line);
|
|
153
|
+
} catch {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
collectEvent(state, e);
|
|
157
|
+
}
|
|
158
|
+
return state;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const fmtTurnResult = (capability: string, r: TurnResult, maxChars: number): string => {
|
|
162
|
+
const parts: string[] = [];
|
|
163
|
+
parts.push(`[deeptutor ${capability} done]`);
|
|
164
|
+
if (r.sessionId)
|
|
165
|
+
parts.push(
|
|
166
|
+
`- session_id: ${r.sessionId} (pass session_id in later turns to continue this context)`,
|
|
167
|
+
);
|
|
168
|
+
if (r.title) parts.push(`- session title: ${r.title}`);
|
|
169
|
+
if (r.toolCalls.length) parts.push(`- tools used: ${[...new Set(r.toolCalls)].join(", ")}`);
|
|
170
|
+
if (r.errors.length)
|
|
171
|
+
parts.push(`- note (some steps failed): ${r.errors.slice(0, 3).join(" | ")}`);
|
|
172
|
+
parts.push(`\n===== Answer =====\n${truncate(r.answer || "(no answer)", maxChars)}`);
|
|
173
|
+
return parts.join("\n");
|
|
174
|
+
};
|