@kanchingyg/dbwatch 0.0.0-stage → 0.1.1

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/dist/bridge.js ADDED
@@ -0,0 +1,740 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
3
+ import { createServer } from "node:http";
4
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ let allowWrite = false;
7
+ let dryRun = false;
8
+ /** Executes `setGuards` to set guards in `dbwatch`. */
9
+ export function setGuards(opts) {
10
+ allowWrite = Boolean(opts.allowWrite);
11
+ dryRun = Boolean(opts.dryRun);
12
+ }
13
+ /** Executes `canonicalizeJson` to canonicalize json in `dbwatch`. */
14
+ export function canonicalizeJson(value) {
15
+ if (value === null || value === undefined)
16
+ return "null";
17
+ if (typeof value !== "object")
18
+ return JSON.stringify(value);
19
+ if (Array.isArray(value)) {
20
+ return `[${value.map((v) => canonicalizeJson(v)).join(",")}]`;
21
+ }
22
+ const obj = value;
23
+ const keys = Object.keys(obj).sort();
24
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalizeJson(obj[k])}`).join(",")}}`;
25
+ }
26
+ /** Executes `loopBreakerHash` to loop breaker hash in `dbwatch`. */
27
+ export function loopBreakerHash(toolName, args) {
28
+ return createHash("sha256")
29
+ .update(`${toolName}:${canonicalizeJson(args)}`)
30
+ .digest("hex");
31
+ }
32
+ const loopState = { lastHash: "", repeatCount: 0 };
33
+ /** Executes `resetLoopBreaker` to reset loop breaker in `dbwatch`. */
34
+ export function resetLoopBreaker() {
35
+ loopState.lastHash = "";
36
+ loopState.repeatCount = 0;
37
+ }
38
+ /** Executes `checkLoopBreaker` to check loop breaker in `dbwatch`. */
39
+ export function checkLoopBreaker(toolName, args) {
40
+ const h = loopBreakerHash(toolName, args);
41
+ if (h === loopState.lastHash) {
42
+ loopState.repeatCount++;
43
+ }
44
+ else {
45
+ loopState.lastHash = h;
46
+ loopState.repeatCount = 1;
47
+ }
48
+ if (loopState.repeatCount >= 3) {
49
+ return "LOOP_BREAKER: Identical tool call repeated 2 times consecutively. Stop calling tools and return your final synthesis.";
50
+ }
51
+ return null;
52
+ }
53
+ /** Executes `guardMutatingTool` to guard mutating tool in `dbwatch`. */
54
+ export function guardMutatingTool(tool) {
55
+ if (!tool.mutates)
56
+ return { blocked: false };
57
+ if (!allowWrite) {
58
+ return {
59
+ blocked: true,
60
+ reason: "Read-only mode: pass --allow-write to enable mutating tools.",
61
+ };
62
+ }
63
+ return { blocked: false };
64
+ }
65
+ const WIN_DRIVE_RE = /^([A-Za-z]):\\(.*)$/;
66
+ const WSL_RE = /^\/mnt\/([a-z])\/(.*)$/i;
67
+ /** Executes `windowsToWsl` to windows to wsl in `dbwatch`. */
68
+ export function windowsToWsl(p) {
69
+ const m = p.match(WIN_DRIVE_RE);
70
+ if (!m)
71
+ return p;
72
+ const rest = m[2].replace(/\\/g, "/");
73
+ return `/mnt/${m[1].toLowerCase()}/${rest}`;
74
+ }
75
+ /** Executes `wslToWindows` to wsl to windows in `dbwatch`. */
76
+ export function wslToWindows(p) {
77
+ const m = p.match(WSL_RE);
78
+ if (!m)
79
+ return p;
80
+ const rest = m[2].replace(/\//g, "\\");
81
+ return `${m[1].toUpperCase()}:\\${rest}`;
82
+ }
83
+ /** Executes `normalizePathsDeep` to normalize paths deep in `dbwatch`. */
84
+ export function normalizePathsDeep(value, toWsl) {
85
+ if (typeof value === "string") {
86
+ if (toWsl && WIN_DRIVE_RE.test(value))
87
+ return windowsToWsl(value);
88
+ if (!toWsl && WSL_RE.test(value))
89
+ return wslToWindows(value);
90
+ if (value.startsWith("file:///")) {
91
+ const raw = value.slice(8).replace(/\//g, "\\");
92
+ return (toWsl ? windowsToWsl(raw) : raw);
93
+ }
94
+ return value;
95
+ }
96
+ if (Array.isArray(value)) {
97
+ return value.map((v) => normalizePathsDeep(v, toWsl));
98
+ }
99
+ if (value && typeof value === "object") {
100
+ const out = {};
101
+ for (const [k, v] of Object.entries(value)) {
102
+ out[k] = normalizePathsDeep(v, toWsl);
103
+ }
104
+ return out;
105
+ }
106
+ return value;
107
+ }
108
+ let jsonRpcWrite = null;
109
+ let stdoutHijacked = false;
110
+ /** Executes `hijackStdoutForMcp` to hijack stdout for mcp in `dbwatch`. */
111
+ export function hijackStdoutForMcp() {
112
+ if (stdoutHijacked)
113
+ return;
114
+ stdoutHijacked = true;
115
+ const native = process.stdout.write.bind(process.stdout);
116
+ jsonRpcWrite = (line) => {
117
+ native(line);
118
+ };
119
+ process.stdout.write = ((chunk, ...args) => {
120
+ const str = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
121
+ if (jsonRpcWrite && str.trim().startsWith("{") && str.includes('"jsonrpc"')) {
122
+ return native(chunk, ...args);
123
+ }
124
+ process.stderr.write(str);
125
+ return true;
126
+ });
127
+ }
128
+ /** Constant `jsonRpcFrameLog` defining json rpc frame log. */
129
+ export const jsonRpcFrameLog = [];
130
+ /** Executes `writeJsonRpcFrame` to write json rpc frame in `dbwatch`. */
131
+ export function writeJsonRpcFrame(obj) {
132
+ const line = JSON.stringify(obj) + "\n";
133
+ jsonRpcFrameLog.push(line);
134
+ if (jsonRpcWrite)
135
+ jsonRpcWrite(line);
136
+ else
137
+ process.stdout.write(line);
138
+ }
139
+ /** Executes `startMcpServer` to start mcp server in `dbwatch`. */
140
+ export function startMcpServer(opts = {}) {
141
+ hijackStdoutForMcp();
142
+ const repoRoot = opts.repoRoot ?? process.cwd();
143
+ const tools = opts.tools ?? [];
144
+ const handlers = opts.handlers ?? {};
145
+ const toWsl = opts.pathToWsl ?? process.env.DBWATCH_PATH_WSL === "1";
146
+ const stdin = process.stdin;
147
+ stdin.setEncoding("utf8");
148
+ let buffer = "";
149
+ stdin.on("data", (chunk) => {
150
+ buffer += chunk;
151
+ let idx;
152
+ while ((idx = buffer.indexOf("\n")) >= 0) {
153
+ const line = buffer.slice(0, idx).trim();
154
+ buffer = buffer.slice(idx + 1);
155
+ if (!line)
156
+ continue;
157
+ let msg;
158
+ try {
159
+ msg = JSON.parse(line);
160
+ }
161
+ catch {
162
+ continue;
163
+ }
164
+ dispatchMcpMessage(msg, { repoRoot, tools, handlers, pathToWsl: toWsl, nCtx: opts.nCtx });
165
+ }
166
+ });
167
+ }
168
+ /** Executes `dispatchMcpMessage` to dispatch mcp message in `dbwatch`. */
169
+ export function dispatchMcpMessage(msg, opts = {}) {
170
+ const repoRoot = opts.repoRoot ?? process.cwd();
171
+ const tools = opts.tools ?? [];
172
+ const handlers = opts.handlers ?? {};
173
+ const toWsl = opts.pathToWsl ?? process.env.DBWATCH_PATH_WSL === "1";
174
+ const nCtx = opts.nCtx ?? DEFAULT_N_CTX;
175
+ handleMessage(msg, tools, handlers, toWsl, nCtx);
176
+ }
177
+ function handleMessage(msg, tools, handlers, toWsl, nCtx) {
178
+ const { id, method, params } = msg;
179
+ if (method === "initialize") {
180
+ writeJsonRpcFrame({
181
+ jsonrpc: "2.0",
182
+ id,
183
+ result: {
184
+ protocolVersion: "2024-11-05",
185
+ capabilities: { tools: {} },
186
+ serverInfo: { name: "dbwatch", version: "0.1.0" },
187
+ },
188
+ });
189
+ return;
190
+ }
191
+ if (method === "notifications/initialized")
192
+ return;
193
+ if (method === "tools/list") {
194
+ writeJsonRpcFrame({
195
+ jsonrpc: "2.0",
196
+ id,
197
+ result: {
198
+ tools: tools.map((t) => ({
199
+ name: t.name,
200
+ description: t.description,
201
+ inputSchema: t.inputSchema,
202
+ annotations: t.annotations,
203
+ })),
204
+ },
205
+ });
206
+ return;
207
+ }
208
+ if (method === "tools/call") {
209
+ const name = params?.name;
210
+ const args = normalizePathsDeep(params?.arguments ?? {}, toWsl);
211
+ const tool = tools.find((t) => t.name === name);
212
+ const loopErr = checkLoopBreaker(name, args);
213
+ if (loopErr) {
214
+ writeJsonRpcFrame({
215
+ jsonrpc: "2.0",
216
+ id,
217
+ result: { content: [{ type: "text", text: loopErr }], isError: true },
218
+ });
219
+ return;
220
+ }
221
+ if (tool) {
222
+ if (dryRun && tool.mutates) {
223
+ writeJsonRpcFrame({
224
+ jsonrpc: "2.0",
225
+ id,
226
+ result: {
227
+ content: [
228
+ {
229
+ type: "text",
230
+ text: `[dry-run] Would call ${name} with ${canonicalizeJson(args)}`,
231
+ },
232
+ ],
233
+ },
234
+ });
235
+ return;
236
+ }
237
+ const guard = guardMutatingTool(tool);
238
+ if (guard.blocked) {
239
+ writeJsonRpcFrame({
240
+ jsonrpc: "2.0",
241
+ id,
242
+ result: {
243
+ content: [{ type: "text", text: guard.reason ?? "blocked" }],
244
+ isError: true,
245
+ },
246
+ });
247
+ return;
248
+ }
249
+ }
250
+ const handler = handlers[name];
251
+ void (async () => {
252
+ try {
253
+ let result;
254
+ if (handler) {
255
+ result = await handler(args);
256
+ }
257
+ else {
258
+ result = { ok: true, tool: name, args };
259
+ }
260
+ const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
261
+ writeJsonRpcFrame({
262
+ jsonrpc: "2.0",
263
+ id,
264
+ result: {
265
+ content: [{ type: "text", text: finalizeMcpToolText(text, { nCtx, toWsl }) }],
266
+ },
267
+ });
268
+ }
269
+ catch (e) {
270
+ writeJsonRpcFrame({
271
+ jsonrpc: "2.0",
272
+ id,
273
+ result: {
274
+ content: [{ type: "text", text: finalizeMcpToolText(String(e), { nCtx, toWsl }) }],
275
+ isError: true,
276
+ },
277
+ });
278
+ }
279
+ })();
280
+ return;
281
+ }
282
+ if (id !== undefined) {
283
+ writeJsonRpcFrame({
284
+ jsonrpc: "2.0",
285
+ id,
286
+ error: { code: -32601, message: `Method not found: ${method}` },
287
+ });
288
+ }
289
+ }
290
+ /** Constant `EMBED_LOCK` defining embed lock. */
291
+ export const EMBED_LOCK = ".dbwatch-embed-lock.json";
292
+ /** Constant `KNOWN_DIMENSIONS` defining known dimensions. */
293
+ export const KNOWN_DIMENSIONS = [384, 768, 1024, 1536, 2560, 3072, 4096];
294
+ /** Executes `readEmbedLock` to read embed lock in `dbwatch`. */
295
+ export function readEmbedLock(repoRoot) {
296
+ const p = join(repoRoot, EMBED_LOCK);
297
+ if (!existsSync(p))
298
+ return null;
299
+ return JSON.parse(readFileSync(p, "utf8"));
300
+ }
301
+ /** Executes `writeEmbedLock` to write embed lock in `dbwatch`. */
302
+ export function writeEmbedLock(repoRoot, lock) {
303
+ writeFileSync(join(repoRoot, EMBED_LOCK), JSON.stringify(lock, null, 2));
304
+ }
305
+ /** Executes `assertDimensionFingerprint` to assert dimension fingerprint in `dbwatch`. */
306
+ export function assertDimensionFingerprint(lock, dim) {
307
+ if (!lock)
308
+ return;
309
+ if (lock.dimension !== dim) {
310
+ throw new Error(`Dimension fingerprint mismatch: lock expects ${lock.dimension}, got ${dim}`);
311
+ }
312
+ }
313
+ /** Executes `l2Normalize` to l2 normalize in `dbwatch`. */
314
+ export function l2Normalize(vec) {
315
+ let sum = 0;
316
+ for (const v of vec)
317
+ sum += v * v;
318
+ const norm = Math.sqrt(sum) || 1;
319
+ return vec.map((v) => v / norm);
320
+ }
321
+ /** Executes `mrlSlice` to mrl slice in `dbwatch`. */
322
+ export function mrlSlice(vec, dimensions) {
323
+ const sliced = vec.slice(0, dimensions);
324
+ return l2Normalize(sliced);
325
+ }
326
+ /** Executes `floatArrayToBase64` to float array to base64 in `dbwatch`. */
327
+ export function floatArrayToBase64(vec) {
328
+ const buf = Buffer.alloc(vec.length * 4);
329
+ for (let i = 0; i < vec.length; i++)
330
+ buf.writeFloatLE(vec[i], i * 4);
331
+ return buf.toString("base64");
332
+ }
333
+ /** Executes `base64ToFloatArray` to base64 to float array in `dbwatch`. */
334
+ export function base64ToFloatArray(b64) {
335
+ const buf = Buffer.from(b64, "base64");
336
+ const out = [];
337
+ for (let i = 0; i < buf.length; i += 4) {
338
+ out.push(buf.readFloatLE(i));
339
+ }
340
+ return out;
341
+ }
342
+ /** Executes `chunkArray` to chunk array in `dbwatch`. */
343
+ export function chunkArray(arr, size) {
344
+ const out = [];
345
+ for (let i = 0; i < arr.length; i += size)
346
+ out.push(arr.slice(i, i + size));
347
+ return out;
348
+ }
349
+ /** Executes `normalizeEmbedBatch` to normalize embed batch in `dbwatch`. */
350
+ export async function normalizeEmbedBatch(inputs, options) {
351
+ const dimensions = options.dimensions;
352
+ const indexed = inputs.map((text, i) => ({ text, i }));
353
+ const nonEmpty = indexed.filter((x) => x.text.trim().length > 0);
354
+ const batches = chunkArray(nonEmpty, options.upstream.maxBatch);
355
+ const vectors = new Map();
356
+ for (const batch of batches) {
357
+ const texts = batch.map((b) => b.text);
358
+ const got = await options.upstream.embed(texts, options.model, dimensions);
359
+ for (let j = 0; j < batch.length; j++) {
360
+ let vec = got[j] ?? [];
361
+ if (dimensions && vec.length > dimensions)
362
+ vec = mrlSlice(vec, dimensions);
363
+ else if (dimensions && vec.length === dimensions)
364
+ vec = l2Normalize(vec);
365
+ if (dimensions)
366
+ assertDimensionFingerprint(options.lock ?? null, vec.length);
367
+ vectors.set(batch[j].i, vec);
368
+ }
369
+ }
370
+ const dim = dimensions ?? (vectors.values().next().value?.length ?? 0);
371
+ const embeddings = [];
372
+ const indices = [];
373
+ for (let i = 0; i < inputs.length; i++) {
374
+ indices.push(i);
375
+ embeddings.push(vectors.get(i) ?? new Array(dim).fill(0));
376
+ }
377
+ return { embeddings, indices };
378
+ }
379
+ /** Executes `createEmbedRequestHandler` to create embed request handler in `dbwatch`. */
380
+ export function createEmbedRequestHandler(opts) {
381
+ return async (req, res) => {
382
+ if (req.method !== "POST" || !req.url?.startsWith("/v1/embeddings")) {
383
+ res.writeHead(404);
384
+ res.end();
385
+ return;
386
+ }
387
+ const body = await readBody(req);
388
+ let parsed;
389
+ try {
390
+ parsed = JSON.parse(body);
391
+ }
392
+ catch {
393
+ res.writeHead(400);
394
+ res.end("Invalid JSON");
395
+ return;
396
+ }
397
+ const input = Array.isArray(parsed.input)
398
+ ? parsed.input
399
+ : parsed.input
400
+ ? [parsed.input]
401
+ : [];
402
+ const lock = readEmbedLock(opts.repoRoot);
403
+ try {
404
+ const { embeddings } = await normalizeEmbedBatch(input, {
405
+ dimensions: parsed.dimensions,
406
+ upstream: opts.upstream,
407
+ model: parsed.model ?? opts.defaultModel,
408
+ lock,
409
+ });
410
+ if (parsed.dimensions && !lock) {
411
+ writeEmbedLock(opts.repoRoot, {
412
+ provider: opts.upstream.name,
413
+ model: parsed.model ?? opts.defaultModel,
414
+ dimension: parsed.dimensions,
415
+ });
416
+ }
417
+ const data = embeddings.map((embedding, index) => {
418
+ if (parsed.encoding_format === "base64") {
419
+ return { object: "embedding", index, embedding: floatArrayToBase64(embedding) };
420
+ }
421
+ return { object: "embedding", index, embedding };
422
+ });
423
+ res.writeHead(200, { "Content-Type": "application/json" });
424
+ res.end(JSON.stringify({ object: "list", data, model: parsed.model, usage: {} }));
425
+ }
426
+ catch (e) {
427
+ res.writeHead(400);
428
+ res.end(String(e));
429
+ }
430
+ };
431
+ }
432
+ function readBody(req) {
433
+ return new Promise((resolve, reject) => {
434
+ const chunks = [];
435
+ req.on("data", (c) => chunks.push(c));
436
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()));
437
+ req.on("error", reject);
438
+ });
439
+ }
440
+ /** Executes `startEmbedServer` to start embed server in `dbwatch`. */
441
+ export function startEmbedServer(opts) {
442
+ const server = createServer(createEmbedRequestHandler(opts));
443
+ server.listen(opts.port, "127.0.0.1");
444
+ return server;
445
+ }
446
+ /** Executes `createDashScopeUpstream` to create dash scope upstream in `dbwatch`. */
447
+ export function createDashScopeUpstream(fetchImpl = fetch, opts) {
448
+ return {
449
+ name: "dashscope",
450
+ maxBatch: 10,
451
+ embed: async (input, model, dimensions) => {
452
+ const key = opts?.apiKey ?? process.env.DASHSCOPE_API_KEY;
453
+ if (!key) {
454
+ throw new Error("DASHSCOPE_API_KEY is required for DashScope embed upstream");
455
+ }
456
+ const res = await fetchImpl("https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings", {
457
+ method: "POST",
458
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
459
+ body: JSON.stringify({ model, input, dimensions }),
460
+ });
461
+ const j = (await res.json());
462
+ return (j.data ?? []).map((d) => d.embedding);
463
+ },
464
+ };
465
+ }
466
+ /** Constant `DEFAULT_N_CTX` defining default n ctx. */
467
+ export const DEFAULT_N_CTX = 4096;
468
+ /** Constant `TRUNCATION_MARKER` defining truncation marker. */
469
+ export const TRUNCATION_MARKER = "[...TRUNCATED BY DBWATCH CONTEXT GUARD...]";
470
+ /** Executes `probeNCtx` to probe nctx in `dbwatch`. */
471
+ export async function probeNCtx(baseUrl, fetchImpl = fetch) {
472
+ const base = baseUrl.replace(/\/$/, "");
473
+ try {
474
+ const props = await fetchImpl(`${base}/props`);
475
+ if (props.ok) {
476
+ const j = (await props.json());
477
+ if (typeof j.n_ctx === "number" && j.n_ctx > 0)
478
+ return { nCtx: j.n_ctx, source: "llama.cpp" };
479
+ }
480
+ }
481
+ catch {
482
+ /* continue */
483
+ }
484
+ try {
485
+ const models = await fetchImpl(`${base}/v1/models`);
486
+ if (models.ok) {
487
+ const j = (await models.json());
488
+ const n = j.data?.[0]?.max_model_len;
489
+ if (n && n > 0)
490
+ return { nCtx: n, source: "vllm" };
491
+ }
492
+ }
493
+ catch {
494
+ /* continue */
495
+ }
496
+ return { nCtx: DEFAULT_N_CTX, source: "fallback" };
497
+ }
498
+ /** Executes `headTailTruncate` to head tail truncate in `dbwatch`. */
499
+ export function headTailTruncate(text, maxChars) {
500
+ if (text.length <= maxChars)
501
+ return text;
502
+ const headLen = Math.floor(maxChars * 0.6);
503
+ const tailLen = Math.floor(maxChars * 0.4) - TRUNCATION_MARKER.length;
504
+ if (tailLen < 0)
505
+ return text.slice(0, maxChars);
506
+ return text.slice(0, headLen) + TRUNCATION_MARKER + text.slice(-tailLen);
507
+ }
508
+ /** Executes `truncateToolOutput` to truncate tool output in `dbwatch`. */
509
+ export function truncateToolOutput(text, nCtx) {
510
+ const budget = Math.floor(nCtx * 4 * 0.3);
511
+ return headTailTruncate(text, budget);
512
+ }
513
+ /** Executes `buildRecencyAnchor` to build recency anchor in `dbwatch`. */
514
+ export function buildRecencyAnchor(sigs) {
515
+ const lines = sigs.map((s) => `- ${s.name}: ${s.summary}`);
516
+ return [
517
+ "[AVAILABLE TOOLS � ANCHORED FOR RECENCY]",
518
+ ...lines,
519
+ 'To call a tool, output ONLY: <tool_call>{"name": "TOOL_NAME", "arguments": {...}}</tool_call>',
520
+ ].join("\n");
521
+ }
522
+ /** Executes `anchorPromptTail` to anchor prompt tail in `dbwatch`. */
523
+ export function anchorPromptTail(prompt, anchor) {
524
+ const marker = "\nASSISTANT:";
525
+ const idx = prompt.lastIndexOf(marker);
526
+ if (idx === -1)
527
+ return `${prompt}\n\n${anchor}\n${marker}`;
528
+ return `${prompt.slice(0, idx)}\n\n${anchor}\n${prompt.slice(idx)}`;
529
+ }
530
+ /** Stateful helper class `StreamToolJsonAbort` (stream tool json abort). */
531
+ export class StreamToolJsonAbort {
532
+ depth = 0;
533
+ started = false;
534
+ acc = "";
535
+ /** Member `feed` (feed). */
536
+ feed(chunk) {
537
+ this.acc += chunk;
538
+ if (!this.started) {
539
+ if (/<tool_call>/i.test(this.acc) ||
540
+ /\u273FFUNCTION\u273F/i.test(this.acc) ||
541
+ /tool_call_argument_begin/i.test(this.acc) ||
542
+ /"name"\s*:\s*"/.test(this.acc)) {
543
+ this.started = true;
544
+ }
545
+ }
546
+ this.depth = countBraceFrom(this.acc);
547
+ return this.started && this.depth === 0 && /\{[\s\S]*\}/.test(this.acc);
548
+ }
549
+ }
550
+ function countBraceFrom(text) {
551
+ let depth = 0;
552
+ let inString = false;
553
+ let escape = false;
554
+ for (let i = 0; i < text.length; i++) {
555
+ const c = text[i];
556
+ if (escape) {
557
+ escape = false;
558
+ continue;
559
+ }
560
+ if (inString) {
561
+ if (c === "\\")
562
+ escape = true;
563
+ else if (c === '"')
564
+ inString = false;
565
+ continue;
566
+ }
567
+ if (c === '"') {
568
+ inString = true;
569
+ continue;
570
+ }
571
+ if (c === "{")
572
+ depth++;
573
+ else if (c === "}")
574
+ depth--;
575
+ }
576
+ return depth;
577
+ }
578
+ /** Executes `stripRedactedThinking` to strip redacted thinking in `dbwatch`. */
579
+ export function stripRedactedThinking(text) {
580
+ return text.replace(/<think>[\s\S]*?(?:<\/redacted_thinking>|$)/gi, "");
581
+ }
582
+ function repairJson(raw) {
583
+ let s = raw.trim();
584
+ if (!s)
585
+ return "{}";
586
+ s = s.replace(/,\s*([}\]])/g, "$1");
587
+ s = s.replace(/'/g, '"');
588
+ s = s.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)/g, '$1"$2"$3');
589
+ const open = (s.match(/\{/g) ?? []).length;
590
+ const close = (s.match(/\}/g) ?? []).length;
591
+ if (open > close)
592
+ s += "}".repeat(open - close);
593
+ return s;
594
+ }
595
+ function tryParseObject(raw) {
596
+ for (const a of [raw, repairJson(raw)]) {
597
+ try {
598
+ const v = JSON.parse(a);
599
+ if (v && typeof v === "object" && !Array.isArray(v))
600
+ return v;
601
+ }
602
+ catch {
603
+ /* next */
604
+ }
605
+ }
606
+ return null;
607
+ }
608
+ /** Executes `parseDirtyToolCall` to parse dirty tool call in `dbwatch`. */
609
+ export function parseDirtyToolCall(content) {
610
+ const text = stripRedactedThinking(content);
611
+ const hermes = text.match(/<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/i);
612
+ if (hermes) {
613
+ const obj = tryParseObject(hermes[1]);
614
+ if (obj?.name && typeof obj.name === "string") {
615
+ return {
616
+ name: obj.name,
617
+ arguments: obj.arguments ?? {},
618
+ };
619
+ }
620
+ }
621
+ const kimi = text.match(/functions\.([^:<|]+):\d+<\|(?:redacted_)?tool_call_argument_begin\|>([\s\S]*?)<\|(?:redacted_)?tool_call_end[^|]*\|>/i);
622
+ if (kimi) {
623
+ const obj = tryParseObject(kimi[2]);
624
+ return { name: kimi[1].trim(), arguments: obj ?? {} };
625
+ }
626
+ const glm = text.match(/<\|(?:redacted_)?tool_call_begin[^|]*\|>([^<|]+)<\|tool_call_sep\|>([\s\S]*?)<\|(?:redacted_)?tool_call_end[^|]*\|>/i);
627
+ if (glm) {
628
+ const obj = tryParseObject(glm[2]);
629
+ return { name: glm[1].trim(), arguments: obj ?? {} };
630
+ }
631
+ const qwenFn = text.match(/\u273FFUNCTION\u273F:\s*([^\n]+)\s*\u273FARGS\u273F:\s*([\s\S]*?)(?:\u273FRESULT\u273F|$)/i);
632
+ if (qwenFn) {
633
+ const obj = tryParseObject(qwenFn[2]);
634
+ return { name: qwenFn[1].trim(), arguments: obj ?? {} };
635
+ }
636
+ const iq = text.match(/\{\s*['"]?cmd['"]?\s*:\s*['"]([^'"]+)['"]/);
637
+ if (iq)
638
+ return { name: "run_shell", arguments: { cmd: iq[1] } };
639
+ const broken = tryParseObject(text);
640
+ if (broken?.cmd)
641
+ return { name: "run_shell", arguments: broken };
642
+ return null;
643
+ }
644
+ /** Executes `detectProvider` to detect provider in `dbwatch`. */
645
+ export function detectProvider(baseUrl, model) {
646
+ const u = baseUrl.toLowerCase();
647
+ if (u.includes("deepseek"))
648
+ return "deepseek";
649
+ if (u.includes("dashscope"))
650
+ return "dashscope";
651
+ if (u.includes("moonshot"))
652
+ return "moonshot";
653
+ if (u.includes("bigmodel") || u.includes("zhipu"))
654
+ return "zhipu";
655
+ if (u.includes("minimax"))
656
+ return "minimax";
657
+ if (u.includes("siliconflow"))
658
+ return "siliconflow";
659
+ if (u.includes("volces"))
660
+ return "doubao";
661
+ if (u.includes("11434") || u.includes("ollama"))
662
+ return "ollama";
663
+ if (u.includes("openai"))
664
+ return "openai";
665
+ return "generic";
666
+ }
667
+ /** Executes `sanitizeRequestBody` to sanitize request body in `dbwatch`. */
668
+ export function sanitizeRequestBody(body, model) {
669
+ const out = { ...body };
670
+ if (/reasoner|qwq|glm-z1|thinking/i.test(model)) {
671
+ for (const key of ["temperature", "top_p", "presence_penalty", "frequency_penalty"]) {
672
+ delete out[key];
673
+ }
674
+ }
675
+ return out;
676
+ }
677
+ /** Executes `normalizeToolChoice` to normalize tool choice in `dbwatch`. */
678
+ export function normalizeToolChoice(toolChoice, provider) {
679
+ if (toolChoice === "required" || toolChoice === "any") {
680
+ switch (provider) {
681
+ case "dashscope":
682
+ case "moonshot":
683
+ case "zhipu":
684
+ case "minimax":
685
+ case "siliconflow":
686
+ case "doubao":
687
+ return "auto";
688
+ default:
689
+ return toolChoice;
690
+ }
691
+ }
692
+ return toolChoice;
693
+ }
694
+ /** Executes `createReasoningState` to create reasoning state in `dbwatch`. */
695
+ export function createReasoningState() {
696
+ return { inToolLoop: false, lastUserTurnIndex: -1 };
697
+ }
698
+ /** Executes `applyReasoningContentPolicy` to apply reasoning content policy in `dbwatch`. */
699
+ export function applyReasoningContentPolicy(messages, state) {
700
+ let lastUser = -1;
701
+ for (let i = 0; i < messages.length; i++) {
702
+ if (messages[i].role === "user")
703
+ lastUser = i;
704
+ }
705
+ const newUserTurn = lastUser > state.lastUserTurnIndex;
706
+ if (newUserTurn) {
707
+ state = { ...state, lastUserTurnIndex: lastUser, inToolLoop: false };
708
+ }
709
+ const hasPendingTools = messages.some((m) => m.role === "assistant" && m.tool_calls && m.tool_calls.length > 0);
710
+ if (hasPendingTools)
711
+ state = { ...state, inToolLoop: true };
712
+ const cleaned = messages.map((m, idx) => {
713
+ const copy = { ...m };
714
+ if (copy.content && typeof copy.content === "string") {
715
+ copy.content = stripRedactedThinking(copy.content);
716
+ }
717
+ if (newUserTurn && copy.role === "assistant" && idx < lastUser) {
718
+ delete copy.reasoning_content;
719
+ }
720
+ if (!state.inToolLoop && copy.role === "assistant") {
721
+ delete copy.reasoning_content;
722
+ }
723
+ return copy;
724
+ });
725
+ return { messages: cleaned, state };
726
+ }
727
+ /** Executes `redactSecrets` to redact secrets in `dbwatch`. */
728
+ export function redactSecrets(text) {
729
+ return text
730
+ .replace(/(api[_-]?key|token|password|secret)\s*[=:]\s*[^\s]+/gi, "$1=[REDACTED]")
731
+ .replace(/Bearer\s+[A-Za-z0-9._-]+/gi, "Bearer [REDACTED]")
732
+ .replace(/sk-[A-Za-z0-9]{20,}/g, "[REDACTED]");
733
+ }
734
+ /** Executes `finalizeMcpToolText` to finalize mcp tool text in `dbwatch`. */
735
+ export function finalizeMcpToolText(text, opts) {
736
+ const nCtx = opts?.nCtx ?? DEFAULT_N_CTX;
737
+ const toWsl = opts?.toWsl ?? false;
738
+ return normalizePathsDeep(redactSecrets(truncateToolOutput(String(text), nCtx)), !toWsl);
739
+ }
740
+ //# sourceMappingURL=bridge.js.map