dsh-input-assistant 0.1.3 → 0.1.4
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/CHANGELOG.md +83 -0
- package/LICENSE +21 -21
- package/README.en.md +100 -0
- package/README.md +215 -184
- package/cordis.patch.yml +3 -3
- package/docs/CHANGELOG-0.1.2.md +102 -102
- package/docs/evaluation.md +111 -111
- package/docs/images/dark.png +0 -0
- package/docs/images/light.png +0 -0
- package/docs/images/optimize.png +0 -0
- package/docs/references.md +39 -39
- package/docs/smoke-comparison.md +37 -37
- package/docs/validation.md +180 -100
- package/lib/browser/components.d.ts +1 -1
- package/lib/browser/controller.d.ts +47 -4
- package/lib/browser/dom.d.ts +1 -1
- package/lib/browser/index.d.ts +1 -1
- package/lib/browser/public.d.ts +2 -2
- package/lib/browser/runtime.d.ts +4 -2
- package/lib/browser/settings.d.ts +1 -1
- package/lib/browser/styles.d.ts +1 -1
- package/lib/client.js +1164 -222
- package/lib/client.meta.json +40 -40
- package/lib/core/config.d.ts +1 -1
- package/lib/core/context.d.ts +1 -1
- package/lib/core/generation.d.ts +2 -2
- package/lib/core/index.d.ts +9 -8
- package/lib/core/models.d.ts +15 -0
- package/lib/core/protection.d.ts +3 -4
- package/lib/core/scheduler.d.ts +1 -1
- package/lib/core/snapshot.d.ts +1 -1
- package/lib/core/types.d.ts +11 -0
- package/lib/core.js +280 -53
- package/lib/core.js.map +2 -2
- package/lib/host/rpc.d.ts +2 -0
- package/lib/host/runtime.d.ts +5 -3
- package/lib/host/storage.d.ts +11 -3
- package/lib/host/transcript.d.ts +1 -1
- package/lib/index.d.ts +5 -3
- package/lib/index.js +451 -156
- package/lib/index.js.map +4 -4
- package/package.json +10 -3
package/lib/index.js
CHANGED
|
@@ -9,6 +9,7 @@ var DEFAULT_CONFIG = Object.freeze({
|
|
|
9
9
|
optimizationStrength: "standard",
|
|
10
10
|
nextEnabled: true,
|
|
11
11
|
completionEnabled: true,
|
|
12
|
+
predictionDetail: "standard",
|
|
12
13
|
displayMode: "ghost",
|
|
13
14
|
debounceMs: 600,
|
|
14
15
|
minIntervalMs: 2e3,
|
|
@@ -35,8 +36,8 @@ var numeric = {
|
|
|
35
36
|
minIntervalMs: [500, 3e4],
|
|
36
37
|
maxHistoryChars: [1e3, 64e3],
|
|
37
38
|
maxDraftChars: [100, 32e3],
|
|
38
|
-
maxNextChars: [20,
|
|
39
|
-
maxCompletionChars: [10,
|
|
39
|
+
maxNextChars: [20, 1e3],
|
|
40
|
+
maxCompletionChars: [10, 500],
|
|
40
41
|
optimizeTimeoutMs: [1e3, 12e4],
|
|
41
42
|
suggestionTimeoutMs: [1e3, 6e4]
|
|
42
43
|
};
|
|
@@ -48,6 +49,7 @@ function validate(config) {
|
|
|
48
49
|
}
|
|
49
50
|
if (!["ghost", "panel"].includes(config.displayMode)) throw new AssistantError("CONFIG", "\u663E\u793A\u65B9\u5F0F\u5FC5\u987B\u662F ghost \u6216 panel");
|
|
50
51
|
if (!["light", "standard", "deep"].includes(config.optimizationStrength)) throw new AssistantError("CONFIG", "\u4F18\u5316\u5F3A\u5EA6\u5FC5\u987B\u662F light\u3001standard \u6216 deep");
|
|
52
|
+
if (!["concise", "standard", "detailed"].includes(config.predictionDetail)) throw new AssistantError("CONFIG", "\u9884\u6D4B\u8BE6\u7565\u5EA6\u5FC5\u987B\u662F concise\u3001standard \u6216 detailed");
|
|
51
53
|
for (const [key, [min, max]] of Object.entries(numeric)) {
|
|
52
54
|
const value = config[key];
|
|
53
55
|
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
|
|
@@ -69,6 +71,7 @@ function resolveStoredConfig(input) {
|
|
|
69
71
|
const record = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
70
72
|
const config = { ...DEFAULT_CONFIG };
|
|
71
73
|
for (const key of Object.keys(DEFAULT_CONFIG)) {
|
|
74
|
+
if (key === "provider" || key === "model") continue;
|
|
72
75
|
if (!(key in record)) continue;
|
|
73
76
|
const value = record[key];
|
|
74
77
|
const candidate = { ...DEFAULT_CONFIG, [key]: value };
|
|
@@ -79,9 +82,11 @@ function resolveStoredConfig(input) {
|
|
|
79
82
|
}
|
|
80
83
|
config[key] = typeof value === "string" ? value.trim() : value;
|
|
81
84
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
config.
|
|
85
|
+
try {
|
|
86
|
+
const route = validate({ ...DEFAULT_CONFIG, provider: record.provider ?? "", model: record.model ?? "" });
|
|
87
|
+
config.provider = route.provider;
|
|
88
|
+
config.model = route.model;
|
|
89
|
+
} catch {
|
|
85
90
|
}
|
|
86
91
|
return config;
|
|
87
92
|
}
|
|
@@ -119,7 +124,7 @@ function buildContext(history, budget = 16e3) {
|
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
// src/core/protection.ts
|
|
122
|
-
var FILE_EXTENSIONS = [
|
|
127
|
+
var FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
123
128
|
"json",
|
|
124
129
|
"jsonc",
|
|
125
130
|
"json5",
|
|
@@ -209,8 +214,8 @@ var FILE_EXTENSIONS = [
|
|
|
209
214
|
"stylelintrc",
|
|
210
215
|
"babelrc",
|
|
211
216
|
"browserslistrc"
|
|
212
|
-
]
|
|
213
|
-
var NAMED_FILES = [
|
|
217
|
+
]);
|
|
218
|
+
var NAMED_FILES = /* @__PURE__ */ new Set([
|
|
214
219
|
"LICENSE",
|
|
215
220
|
"LICENCE",
|
|
216
221
|
"README",
|
|
@@ -227,28 +232,193 @@ var NAMED_FILES = [
|
|
|
227
232
|
"Vagrantfile",
|
|
228
233
|
"Jenkinsfile",
|
|
229
234
|
"Brewfile"
|
|
230
|
-
]
|
|
231
|
-
var
|
|
232
|
-
|
|
235
|
+
]);
|
|
236
|
+
var DOTFILES = [
|
|
237
|
+
".env",
|
|
238
|
+
".npmrc",
|
|
239
|
+
".nvmrc",
|
|
240
|
+
".editorconfig",
|
|
241
|
+
".gitignore",
|
|
242
|
+
".gitattributes",
|
|
243
|
+
".gitmodules",
|
|
244
|
+
".prettierrc",
|
|
245
|
+
".eslintrc",
|
|
246
|
+
".stylelintrc",
|
|
247
|
+
".babelrc",
|
|
248
|
+
".browserslistrc"
|
|
249
|
+
];
|
|
250
|
+
var candidatePattern = /[\p{L}\p{N}\p{M}_.@+~\\/-]+/uy;
|
|
251
|
+
var cjk = "[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]";
|
|
252
|
+
var cjkCharacter = new RegExp(cjk, "u");
|
|
253
|
+
var candidateEnding = new RegExp(`(?:\\.[A-Za-z][A-Za-z0-9]{0,11}|${[...NAMED_FILES].join("|")})(?=${cjk}|$)`, "gu");
|
|
254
|
+
function knownFile(value) {
|
|
255
|
+
const dot = value.lastIndexOf(".");
|
|
256
|
+
return NAMED_FILES.has(value) || DOTFILES.some((name2) => value === name2 || value.startsWith(name2 + ".")) || dot > 0 && FILE_EXTENSIONS.has(value.slice(dot + 1).toLowerCase());
|
|
257
|
+
}
|
|
258
|
+
function pathOrFile(value) {
|
|
259
|
+
const slash = Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\"));
|
|
260
|
+
if (slash < 0) return knownFile(value);
|
|
261
|
+
if (/^(?:\.{1,2}[\\/]|[\\/])/.test(value)) return value.length > slash + 1 || slash > 0;
|
|
262
|
+
const tail = value.slice(slash + 1), dot = tail.lastIndexOf(".");
|
|
263
|
+
if (dot >= 0 && /^[A-Za-z][A-Za-z0-9]{0,11}$/.test(tail.slice(dot + 1))) return true;
|
|
264
|
+
let separators = 0;
|
|
265
|
+
for (const char of value) if (char === "/" || char === "\\") separators++;
|
|
266
|
+
return separators >= 2;
|
|
267
|
+
}
|
|
268
|
+
function literalStart(value) {
|
|
269
|
+
const slash = value.search(/[\\/]/);
|
|
270
|
+
const first = slash < 0 ? value : value.slice(0, slash);
|
|
271
|
+
if (slash >= 0) {
|
|
272
|
+
const relative = first.match(/(?:\.{1,2}|~)$/);
|
|
273
|
+
if (relative) return relative.index;
|
|
274
|
+
}
|
|
275
|
+
const dot = first.indexOf(".");
|
|
276
|
+
const stem = dot < 0 ? first : first.slice(0, dot);
|
|
277
|
+
let start = 0, previous = "", offset = 0;
|
|
278
|
+
for (const char of stem) {
|
|
279
|
+
if (cjkCharacter.test(previous) && /[A-Za-z0-9_@+~-]/.test(char)) start = offset;
|
|
280
|
+
previous = char;
|
|
281
|
+
offset += char.length;
|
|
282
|
+
}
|
|
283
|
+
if (!start && dot > 0 && DOTFILES.some((name2) => first.slice(dot) === name2 || first.slice(dot).startsWith(name2 + ".")) && Array.from(stem).every((char) => cjkCharacter.test(char))) return dot;
|
|
284
|
+
return start;
|
|
285
|
+
}
|
|
286
|
+
function candidateRanges(value, base) {
|
|
233
287
|
const ranges = [];
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
]
|
|
251
|
-
|
|
288
|
+
let cursor = 0;
|
|
289
|
+
candidateEnding.lastIndex = 0;
|
|
290
|
+
for (const ending of value.matchAll(candidateEnding)) {
|
|
291
|
+
const end = ending.index + ending[0].length;
|
|
292
|
+
let next = end;
|
|
293
|
+
while (next < value.length) {
|
|
294
|
+
const char = String.fromCodePoint(value.codePointAt(next));
|
|
295
|
+
if (!cjkCharacter.test(char)) break;
|
|
296
|
+
next += char.length;
|
|
297
|
+
}
|
|
298
|
+
if (next > end && /[\\/]/.test(value[next] ?? "")) continue;
|
|
299
|
+
const portion = value.slice(cursor, end);
|
|
300
|
+
const start = cursor + literalStart(portion);
|
|
301
|
+
if (pathOrFile(value.slice(start, end))) ranges.push({ start: base + start, end: base + end });
|
|
302
|
+
cursor = end;
|
|
303
|
+
}
|
|
304
|
+
if (cursor < value.length && /[.\\/]/.test(value.slice(cursor))) {
|
|
305
|
+
const portion = value.slice(cursor);
|
|
306
|
+
const start = cursor + literalStart(portion);
|
|
307
|
+
let end = value.length;
|
|
308
|
+
const tailStart = Math.max(start, Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\")) + 1);
|
|
309
|
+
const tail = value.slice(tailStart), boundary = tail.search(cjkCharacter);
|
|
310
|
+
if (tailStart >= start && boundary > 0 && /^[A-Za-z0-9_@+~.-]/.test(tail)) end = tailStart + boundary;
|
|
311
|
+
if (pathOrFile(value.slice(start, end))) ranges.push({ start: base + start, end: base + end });
|
|
312
|
+
}
|
|
313
|
+
return ranges;
|
|
314
|
+
}
|
|
315
|
+
function urlEnd(text, start) {
|
|
316
|
+
let end = start, parentheses = 0, brackets = 0;
|
|
317
|
+
while (end < text.length) {
|
|
318
|
+
const char = text[end];
|
|
319
|
+
if (/[\s<>"'`,。;;)]/.test(char)) break;
|
|
320
|
+
if (char === "(") parentheses++;
|
|
321
|
+
else if (char === ")") {
|
|
322
|
+
if (!parentheses) break;
|
|
323
|
+
parentheses--;
|
|
324
|
+
} else if (char === "[") brackets++;
|
|
325
|
+
else if (char === "]") {
|
|
326
|
+
if (!brackets) break;
|
|
327
|
+
brackets--;
|
|
328
|
+
}
|
|
329
|
+
end++;
|
|
330
|
+
}
|
|
331
|
+
while (end > start && /[.,!]/.test(text[end - 1])) end--;
|
|
332
|
+
return end;
|
|
333
|
+
}
|
|
334
|
+
function windowsPathEnd(text, start) {
|
|
335
|
+
const quote = text[start - 1];
|
|
336
|
+
if (quote === '"' || quote === "'") {
|
|
337
|
+
const closing = text.indexOf(quote, start);
|
|
338
|
+
if (closing >= 0 && !/[\r\n]/.test(text.slice(start, closing))) return closing;
|
|
339
|
+
}
|
|
340
|
+
let end = start + 3;
|
|
341
|
+
while (end < text.length && !/[\r\n<>"'`,。;;))\]!?!?::]/.test(text[end])) end++;
|
|
342
|
+
while (end > start && /[ \t]/.test(text[end - 1])) end--;
|
|
343
|
+
const value = text.slice(start, end);
|
|
344
|
+
const tailStart = Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\")) + 1;
|
|
345
|
+
const tail = value.slice(tailStart);
|
|
346
|
+
const dotfile = DOTFILES.some((name2) => tail.startsWith(name2) && /[. \t]/.test(tail[name2.length] ?? ""));
|
|
347
|
+
let dot = -1;
|
|
348
|
+
for (let i = 0; i < tail.length; i++) {
|
|
349
|
+
if (tail[i] === ".") dot = i;
|
|
350
|
+
if (/[ \t]/.test(tail[i]) && (dotfile || dot >= 0 && i - dot <= 13 && FILE_EXTENSIONS.has(tail.slice(dot + 1, i).toLowerCase()) || i <= 20 && NAMED_FILES.has(tail.slice(0, i)))) return start + tailStart + i;
|
|
351
|
+
}
|
|
352
|
+
return end;
|
|
353
|
+
}
|
|
354
|
+
function automaticRanges(text) {
|
|
355
|
+
const ranges = [];
|
|
356
|
+
let cursor = 0;
|
|
357
|
+
while (cursor < text.length) {
|
|
358
|
+
const start = cursor;
|
|
359
|
+
const fence = text.startsWith("```", cursor) ? "```" : text.startsWith("~~~", cursor) ? "~~~" : null;
|
|
360
|
+
if (fence) {
|
|
361
|
+
const closing = text.indexOf(fence, cursor + 3);
|
|
362
|
+
cursor = closing < 0 ? text.length : closing + 3;
|
|
363
|
+
ranges.push({ start, end: cursor });
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (text[cursor] === "`") {
|
|
367
|
+
let end = cursor + 1;
|
|
368
|
+
while (end < text.length && text[end] !== "`" && text[end] !== "\n") end++;
|
|
369
|
+
if (text[end] === "`" && end > cursor + 1) {
|
|
370
|
+
cursor = end + 1;
|
|
371
|
+
ranges.push({ start, end: cursor });
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (text[cursor] === '"' || text[cursor] === "'") {
|
|
376
|
+
const closing = text.indexOf(text[cursor], cursor + 1);
|
|
377
|
+
if (closing >= 0) {
|
|
378
|
+
const literal = text.slice(cursor + 1, closing);
|
|
379
|
+
if (!/[\r\n]/.test(literal) && (pathOrFile(literal) || /^[A-Za-z]:[\\/]/.test(literal))) {
|
|
380
|
+
ranges.push({ start: cursor + 1, end: closing });
|
|
381
|
+
cursor = closing + 1;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const scheme = text.slice(cursor, cursor + 8).toLowerCase();
|
|
387
|
+
if (scheme.startsWith("https://") || scheme.startsWith("http://")) {
|
|
388
|
+
cursor = urlEnd(text, cursor);
|
|
389
|
+
ranges.push({ start, end: cursor });
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (/[A-Za-z]/.test(text[cursor]) && text[cursor + 1] === ":" && /[\\/]/.test(text[cursor + 2] ?? "")) {
|
|
393
|
+
cursor = windowsPathEnd(text, cursor);
|
|
394
|
+
ranges.push({ start, end: cursor });
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
candidatePattern.lastIndex = cursor;
|
|
398
|
+
const match = candidatePattern.exec(text);
|
|
399
|
+
if (match) {
|
|
400
|
+
cursor = candidatePattern.lastIndex;
|
|
401
|
+
const fenceOffset = match[0].indexOf("~~~");
|
|
402
|
+
if (fenceOffset >= 0) cursor = start + fenceOffset;
|
|
403
|
+
else if (text[cursor] === ":") {
|
|
404
|
+
if (text.startsWith("//", cursor + 1)) {
|
|
405
|
+
const ending = text.slice(Math.max(start, cursor - 5), cursor).toLowerCase();
|
|
406
|
+
if (ending.endsWith("https")) cursor -= 5;
|
|
407
|
+
else if (ending.endsWith("http")) cursor -= 4;
|
|
408
|
+
else if (/[A-Za-z]/.test(text[cursor - 1] ?? "")) cursor--;
|
|
409
|
+
} else if (/[\\/]/.test(text[cursor + 1] ?? "") && /[A-Za-z]/.test(text[cursor - 1] ?? "")) cursor--;
|
|
410
|
+
}
|
|
411
|
+
if (cursor === start) continue;
|
|
412
|
+
let end = cursor;
|
|
413
|
+
if (!/[\\/]\.{1,2}$/.test(text.slice(start, cursor))) while (end > start && text[end - 1] === ".") end--;
|
|
414
|
+
const value = text.slice(start, end);
|
|
415
|
+
if (value) ranges.push(...candidateRanges(value, start));
|
|
416
|
+
} else cursor++;
|
|
417
|
+
}
|
|
418
|
+
return ranges;
|
|
419
|
+
}
|
|
420
|
+
function protectText(text, locked = []) {
|
|
421
|
+
const ranges = automaticRanges(text);
|
|
252
422
|
for (const literal of locked) {
|
|
253
423
|
if (typeof literal !== "string" || !literal.length || !text.includes(literal)) throw new AssistantError("LOCK_SCOPE", "\u9501\u5B9A\u7247\u6BB5\u5DF2\u53D8\u5316\u6216\u4E0D\u5728\u672C\u6B21\u4F18\u5316\u8303\u56F4\u5185");
|
|
254
424
|
let start = text.indexOf(literal);
|
|
@@ -264,8 +434,14 @@ function protectText(text, locked = []) {
|
|
|
264
434
|
if (prev && r.start < prev.end) prev.end = Math.max(prev.end, r.end);
|
|
265
435
|
else merged.push({ ...r });
|
|
266
436
|
}
|
|
267
|
-
let
|
|
268
|
-
while (
|
|
437
|
+
let longestPrefix = -1, occurrence = text.indexOf("DSH_KEEP_");
|
|
438
|
+
while (occurrence >= 0) {
|
|
439
|
+
let start = occurrence;
|
|
440
|
+
while (start > 0 && text[start - 1] === "_") start--;
|
|
441
|
+
longestPrefix = Math.max(longestPrefix, occurrence - start);
|
|
442
|
+
occurrence = text.indexOf("DSH_KEEP_", occurrence + 9);
|
|
443
|
+
}
|
|
444
|
+
const prefix = "_".repeat(longestPrefix + 1) + "DSH_KEEP_";
|
|
269
445
|
const fragments = [];
|
|
270
446
|
let cursor = 0, masked = "";
|
|
271
447
|
for (const r of merged) {
|
|
@@ -278,19 +454,20 @@ function protectText(text, locked = []) {
|
|
|
278
454
|
return { original: text, masked: masked + text.slice(cursor), fragments };
|
|
279
455
|
}
|
|
280
456
|
function restoreProtected(output, protectedText) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
457
|
+
const fragments = new Map(protectedText.fragments.map((fragment) => [fragment.token, fragment.original]));
|
|
458
|
+
const existing = new Set(protectedText.original.match(/⟪_*DSH_KEEP_\d+⟫/gu) ?? []);
|
|
459
|
+
const seen = /* @__PURE__ */ new Set();
|
|
460
|
+
const restored = output.replace(/⟪_*DSH_KEEP_\d+⟫/gu, (token) => {
|
|
461
|
+
const original = fragments.get(token);
|
|
462
|
+
if (original === void 0) {
|
|
463
|
+
if (!existing.has(token)) throw new AssistantError("PROTECTION", "\u751F\u6210\u7ED3\u679C\u5305\u542B\u65E0\u6548\u7684\u4FDD\u62A4\u6807\u8BB0\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
464
|
+
return token;
|
|
465
|
+
}
|
|
466
|
+
if (seen.has(token)) throw new AssistantError("PROTECTION", "\u751F\u6210\u7ED3\u679C\u672A\u5B8C\u6574\u4FDD\u7559\u9501\u5B9A\u5185\u5BB9\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
467
|
+
seen.add(token);
|
|
468
|
+
return original;
|
|
469
|
+
});
|
|
470
|
+
if (seen.size !== fragments.size) throw new AssistantError("PROTECTION", "\u751F\u6210\u7ED3\u679C\u672A\u5B8C\u6574\u4FDD\u7559\u9501\u5B9A\u5185\u5BB9\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
294
471
|
return restored;
|
|
295
472
|
}
|
|
296
473
|
|
|
@@ -311,6 +488,13 @@ function prepareGeneration(request, history, config) {
|
|
|
311
488
|
if (charCount(target) > config.maxDraftChars) throw new AssistantError("TOO_LONG", `\u672C\u6B21\u6587\u672C\u8D85\u8FC7 ${config.maxDraftChars} \u5B57\u7B26\uFF0C\u8BF7\u9009\u4E2D\u8F83\u77ED\u7247\u6BB5\u4F18\u5316`);
|
|
312
489
|
if (request.operation === "optimize" && !target.trim()) throw new AssistantError("EMPTY", "\u8BF7\u5148\u8F93\u5165\u6216\u9009\u4E2D\u9700\u8981\u4F18\u5316\u7684\u6587\u5B57");
|
|
313
490
|
if (request.operation === "complete" && !request.draft.trim()) throw new AssistantError("EMPTY", "\u6CA1\u6709\u53EF\u7EED\u5199\u7684\u8349\u7A3F");
|
|
491
|
+
const detail = config.predictionDetail;
|
|
492
|
+
const suggestionCap = request.operation === "next" ? config.maxNextChars : config.maxCompletionChars;
|
|
493
|
+
const detailGuidance = {
|
|
494
|
+
concise: "Detail level: CONCISE. Prefer one brief sentence or phrase. Do not add scope merely to fill the character budget.",
|
|
495
|
+
standard: "Detail level: STANDARD. Use a natural, complete expression with only the detail needed for the current intent.",
|
|
496
|
+
detailed: "Detail level: DETAILED. Explain only already-supported intent more fully; short paragraphs or a structured list are allowed when useful. There is no minimum length. Never add tasks, constraints, permissions, deliverables, or commitments merely to make the message longer."
|
|
497
|
+
}[detail];
|
|
314
498
|
let system = common, protection = null;
|
|
315
499
|
if (request.operation === "optimize") {
|
|
316
500
|
protection = protectText(target, request.lockedFragments);
|
|
@@ -335,18 +519,22 @@ function prepareGeneration(request, history, config) {
|
|
|
335
519
|
"Consider answering the latest question, a natural follow-up question, correcting a misunderstanding, or proceeding with an explicitly requested unfinished step.",
|
|
336
520
|
"A question or correction is allowed when supported. Do not automatically propose commits, pushing, testing, or new features just because code was produced.",
|
|
337
521
|
`If no single continuation is well-supported, or the task is naturally finished, output ${NO_SUGGESTION}. Do not guess an unresolved choice.`,
|
|
338
|
-
`Return one
|
|
522
|
+
`Return one message of at most ${suggestionCap} Unicode characters. This is a hard maximum, not a target or minimum.`,
|
|
523
|
+
detailGuidance,
|
|
524
|
+
detail === "detailed" ? "Internal line breaks are allowed for supported structure; do not add blank padding lines." : "Return a single line with no line breaks.",
|
|
339
525
|
"Examples: user requested hit rate AND invalidation, only hit rate was explained -> \u518D\u89E3\u91CA\u4E00\u4E0B\u7F13\u5B58\u5931\u6548\u3002",
|
|
340
526
|
"User said explanation only, assistant proposes file edits -> \u5148\u4E0D\u8981\u6539\u6587\u4EF6\uFF0C\u8BF7\u53EA\u89E3\u91CA\u539F\u56E0\u3002",
|
|
341
527
|
`Assistant asks Redis or local cache, no preference in history -> ${NO_SUGGESTION}.`
|
|
342
528
|
].join("\n");
|
|
343
529
|
} else {
|
|
344
530
|
system += "\n" + [
|
|
345
|
-
"Complete the user's current draft by returning ONLY a
|
|
531
|
+
"Complete the user's current draft by returning ONLY a suffix to append exactly at its end.",
|
|
346
532
|
"The draft is the strongest constraint. Do not repeat its prefix, rewrite existing text, introduce a new topic, or answer the question.",
|
|
347
533
|
"Preserve necessary leading whitespace in the suffix. A complete message does not need an arbitrary extra requirement.",
|
|
348
534
|
`If the intended continuation is uncertain or the draft is already complete, output ${NO_SUGGESTION}.`,
|
|
349
|
-
`Return at most ${
|
|
535
|
+
`Return at most ${suggestionCap} Unicode characters, with no explanation. This is a hard maximum, not a target or minimum.`,
|
|
536
|
+
detailGuidance,
|
|
537
|
+
detail === "detailed" ? "Line breaks are allowed only when they naturally continue the existing draft structure. Preserve a necessary leading line break, but do not start an unrelated list or task." : "Return a single line with no line breaks.",
|
|
350
538
|
"Examples: draft \u8BF7\u89E3\u91CA\u8FD9\u6BB5\u4EE3\u7801\u7684\u65F6\u95F4 -> \u590D\u6742\u5EA6\u3002",
|
|
351
539
|
`Draft Please explain the cause without editing files. -> ${NO_SUGGESTION}.`
|
|
352
540
|
].join("\n");
|
|
@@ -365,11 +553,46 @@ function prepareGeneration(request, history, config) {
|
|
|
365
553
|
config,
|
|
366
554
|
protection,
|
|
367
555
|
timeoutMs: request.operation === "optimize" ? config.optimizeTimeoutMs : config.suggestionTimeoutMs,
|
|
368
|
-
|
|
556
|
+
// A Unicode character may require several tokens, especially for CJK and
|
|
557
|
+
// emoji. Keep the transport budget large enough for the explicit text cap.
|
|
558
|
+
maxOutputTokens: request.operation === "optimize" ? 8192 : Math.max(request.operation === "next" ? 128 : 96, suggestionCap * 4 + 32)
|
|
369
559
|
};
|
|
370
560
|
}
|
|
561
|
+
function hasUnpairedSurrogate(text) {
|
|
562
|
+
for (let i = 0; i < text.length; i++) {
|
|
563
|
+
const code = text.charCodeAt(i);
|
|
564
|
+
if (code >= 55296 && code <= 56319) {
|
|
565
|
+
const next = text.charCodeAt(++i);
|
|
566
|
+
if (!(next >= 56320 && next <= 57343)) return true;
|
|
567
|
+
} else if (code >= 56320 && code <= 57343) return true;
|
|
568
|
+
}
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
function cleanOptimizationOutput(raw, prepared) {
|
|
572
|
+
let cleaned = raw.trim();
|
|
573
|
+
const original = prepared.protection.original.trim();
|
|
574
|
+
const masked = prepared.protection.masked.trim();
|
|
575
|
+
if (cleaned === masked) return cleaned;
|
|
576
|
+
const fence = cleaned.match(/^```(?:markdown|prompt|text)?\r?\n([\s\S]*?)\r?\n```$/i);
|
|
577
|
+
if (fence) cleaned = fence[1].trim();
|
|
578
|
+
const preamble = /^(?:(?:好的|好的,|已为您|根据您的要求|针对您的需求|我为您)?(?:优化|改写|整理|修改|调整)后的?(?:提示词|prompt|内容|表达|文本)?(?:如下|为)?[::\s\n]+|Here(?:'s| is) (?:the |an )?(?:optimized|improved|rewritten) (?:prompt|version)[::\s\n]+)/i;
|
|
579
|
+
const prefix = cleaned.match(preamble)?.[0];
|
|
580
|
+
if (prefix && !original.toLowerCase().startsWith(prefix.trim().toLowerCase())) cleaned = cleaned.slice(prefix.length).trim();
|
|
581
|
+
for (const [start, end] of [['"', '"'], ["\u201C", "\u201D"], ["`", "`"]]) {
|
|
582
|
+
if (!cleaned.startsWith(start) || !cleaned.endsWith(end) || cleaned.length < 2) continue;
|
|
583
|
+
const inner = cleaned.slice(start.length, -end.length).trim();
|
|
584
|
+
const originalWrapped = masked.startsWith(start) && masked.endsWith(end);
|
|
585
|
+
if (!originalWrapped || inner.startsWith(start) && inner.endsWith(end)) cleaned = inner;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
return cleaned;
|
|
589
|
+
}
|
|
371
590
|
function finishGeneration(raw, prepared) {
|
|
372
591
|
if (typeof raw !== "string") throw new AssistantError("OUTPUT", "\u6A21\u578B\u672A\u8FD4\u56DE\u6709\u6548\u6587\u5B57");
|
|
592
|
+
if (hasUnpairedSurrogate(raw)) {
|
|
593
|
+
if (prepared.operation === "optimize") throw new AssistantError("OUTPUT", "\u6A21\u578B\u8FD4\u56DE\u4E86\u4E0D\u5B8C\u6574\u5B57\u7B26\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
373
596
|
const trimmed = raw.trim();
|
|
374
597
|
if (!trimmed || trimmed === NO_SUGGESTION) {
|
|
375
598
|
if (prepared.operation === "optimize") throw new AssistantError("EMPTY_OUTPUT", "\u6A21\u578B\u672A\u8FD4\u56DE\u4F18\u5316\u6587\u5B57\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
@@ -377,16 +600,20 @@ function finishGeneration(raw, prepared) {
|
|
|
377
600
|
}
|
|
378
601
|
if (prepared.operation === "optimize") {
|
|
379
602
|
if (raw.includes(NO_SUGGESTION)) throw new AssistantError("OUTPUT", "\u6A21\u578B\u8F93\u51FA\u4E0D\u5B8C\u6574\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
380
|
-
const
|
|
603
|
+
const cleaned = cleanOptimizationOutput(trimmed, prepared);
|
|
604
|
+
if (!cleaned.trim()) throw new AssistantError("EMPTY_OUTPUT", "\u6A21\u578B\u672A\u8FD4\u56DE\u4F18\u5316\u6587\u5B57\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
605
|
+
const output = restoreProtected(cleaned, prepared.protection);
|
|
606
|
+
if (hasUnpairedSurrogate(output)) throw new AssistantError("OUTPUT", "\u4F18\u5316\u7ED3\u679C\u5305\u542B\u4E0D\u5B8C\u6574\u5B57\u7B26\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
381
607
|
if (charCount(output) > prepared.config.maxDraftChars * 2) throw new AssistantError("OUTPUT", "\u4F18\u5316\u7ED3\u679C\u8FC7\u957F\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
382
608
|
return output;
|
|
383
609
|
}
|
|
384
610
|
const cap = prepared.operation === "next" ? prepared.config.maxNextChars : prepared.config.maxCompletionChars;
|
|
385
|
-
|
|
611
|
+
const allowNewlines = prepared.config.predictionDetail === "detailed";
|
|
612
|
+
if (charCount(raw) > cap || !allowNewlines && /[\r\n]/.test(raw) || /^(?:```|\{\s*"|<think>|(?:建议|预测|续写|Suggestion|Prediction)\s*[::])/i.test(trimmed) || trimmed.includes(NO_SUGGESTION)) return null;
|
|
386
613
|
if (prepared.operation === "next") return trimmed;
|
|
387
|
-
|
|
388
|
-
if (
|
|
389
|
-
return raw.replace(
|
|
614
|
+
const draft = prepared.request.draft.trim();
|
|
615
|
+
if (charCount(draft) >= 4 && trimmed.startsWith(draft) || prepared.request.draft.endsWith(raw)) return null;
|
|
616
|
+
return raw.replace(/\s+$/u, "");
|
|
390
617
|
}
|
|
391
618
|
|
|
392
619
|
// src/core/scheduler.ts
|
|
@@ -441,19 +668,101 @@ var SessionScheduler = class {
|
|
|
441
668
|
};
|
|
442
669
|
|
|
443
670
|
// src/host/runtime.ts
|
|
444
|
-
import { randomUUID } from "node:crypto";
|
|
445
|
-
import { z as
|
|
671
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
672
|
+
import { z as z3 } from "zod";
|
|
446
673
|
import { Session } from "@deepseek-ai/dsh-session";
|
|
447
674
|
import { BlockAssembler } from "@deepseek-ai/dsh-llm";
|
|
448
675
|
|
|
449
|
-
// src/host/
|
|
676
|
+
// src/host/storage.ts
|
|
450
677
|
import { z } from "zod";
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
678
|
+
import { randomUUID } from "node:crypto";
|
|
679
|
+
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
680
|
+
var draftSchema = z.object({ id: z.string().min(1).max(300), text: z.string().min(1).max(64e3).refine((text) => charCount(text) <= 32e3), createdAt: z.number().int().nonnegative() });
|
|
681
|
+
var sessionDraftsSchema = z.object({ version: z.literal(1), drafts: z.array(draftSchema).max(3) });
|
|
682
|
+
function createDomainSpec(initial) {
|
|
683
|
+
return defineDomain({
|
|
684
|
+
name: "input_assistant",
|
|
685
|
+
version: 1,
|
|
686
|
+
layout: "per-record",
|
|
687
|
+
global: { schema: z.object({ version: z.literal(1), settings: z.record(z.string(), z.unknown()) }), initial: { version: 1, settings: { ...initial } } },
|
|
688
|
+
tables: { drafts: domainTable(sessionDraftsSchema) }
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
var AssistantStorage = class {
|
|
692
|
+
constructor(domain) {
|
|
693
|
+
this.domain = domain;
|
|
694
|
+
}
|
|
695
|
+
chain = Promise.resolve();
|
|
696
|
+
transaction(work) {
|
|
697
|
+
const task = this.chain.then(async () => work(await this.domain()));
|
|
698
|
+
this.chain = task.then(() => void 0, () => void 0);
|
|
699
|
+
return task;
|
|
700
|
+
}
|
|
701
|
+
async settings() {
|
|
702
|
+
const domain = await this.domain();
|
|
703
|
+
return resolveStoredConfig(domain.global.get().settings);
|
|
704
|
+
}
|
|
705
|
+
updateSettings(patch) {
|
|
706
|
+
return this.transaction(async (domain) => {
|
|
707
|
+
const settings = resolveConfig({ ...resolveStoredConfig(domain.global.get().settings), ...patch });
|
|
708
|
+
await domain.global.set({ version: 1, settings });
|
|
709
|
+
return settings;
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
async list(sessionId) {
|
|
713
|
+
return (await this.domain()).table("drafts").get(sessionId)?.drafts.map((draft) => ({ ...draft })) ?? [];
|
|
714
|
+
}
|
|
715
|
+
save(sessionId, text, replaceId) {
|
|
716
|
+
if (text.trim().length === 0 || text.length > 64e3 || charCount(text) > 32e3) throw new AssistantError("DRAFT", "\u6682\u5B58\u5185\u5BB9\u4E3A\u7A7A\u6216\u8D85\u8FC7 32,000 \u5B57\u7B26");
|
|
717
|
+
return this.transaction(async (domain) => {
|
|
718
|
+
const drafts = [...domain.table("drafts").get(sessionId)?.drafts ?? []];
|
|
719
|
+
const index = replaceId === void 0 ? -1 : drafts.findIndex((draft) => draft.id === replaceId);
|
|
720
|
+
if (replaceId !== void 0 && index < 0) throw new AssistantError("DRAFT_NOT_FOUND", "\u8981\u66FF\u6362\u7684\u6682\u5B58\u8349\u7A3F\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5237\u65B0\u5217\u8868");
|
|
721
|
+
if (drafts.length >= 3 && index < 0) throw new AssistantError("DRAFT_LIMIT", "\u5F53\u524D\u4F1A\u8BDD\u5DF2\u6682\u5B58\u4E09\u6761\uFF0C\u8BF7\u9009\u62E9\u66FF\u6362\u6216\u5220\u9664\u4E00\u6761");
|
|
722
|
+
const next = { id: replaceId ?? randomUUID(), text, createdAt: Date.now() };
|
|
723
|
+
if (index < 0) drafts.push(next);
|
|
724
|
+
else drafts[index] = next;
|
|
725
|
+
await domain.table("drafts").put(sessionId, { version: 1, drafts });
|
|
726
|
+
return drafts.map((draft) => ({ ...draft }));
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
delete(sessionId, id) {
|
|
730
|
+
return this.transaction(async (domain) => {
|
|
731
|
+
const current = domain.table("drafts").get(sessionId)?.drafts ?? [];
|
|
732
|
+
const drafts = id === void 0 ? [] : current.filter((draft) => draft.id !== id);
|
|
733
|
+
const removed = id === void 0 ? current : current.filter((draft) => draft.id === id);
|
|
734
|
+
if (drafts.length === 0) await domain.table("drafts").delete(sessionId);
|
|
735
|
+
else await domain.table("drafts").put(sessionId, { version: 1, drafts });
|
|
736
|
+
return { drafts: drafts.map((draft) => ({ ...draft })), removed: removed.map((draft) => ({ ...draft })) };
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
/** Undo deletion without overwriting drafts created or changed elsewhere. */
|
|
740
|
+
restore(sessionId, deleted) {
|
|
741
|
+
const snapshots = z.array(draftSchema).max(3).parse(deleted);
|
|
742
|
+
return this.transaction(async (domain) => {
|
|
743
|
+
const drafts = [...domain.table("drafts").get(sessionId)?.drafts ?? []];
|
|
744
|
+
const ids = new Set(drafts.map((draft) => draft.id));
|
|
745
|
+
for (const snapshot of snapshots) {
|
|
746
|
+
if (!ids.has(snapshot.id)) {
|
|
747
|
+
drafts.push(snapshot);
|
|
748
|
+
ids.add(snapshot.id);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (drafts.length > 3) throw new AssistantError("DRAFT_LIMIT", "\u5F53\u524D\u4F1A\u8BDD\u6682\u5B58\u7A7A\u95F4\u4E0D\u8DB3\uFF0C\u8BF7\u5220\u9664\u4E00\u6761\u540E\u518D\u64A4\u9500");
|
|
752
|
+
if (drafts.length) await domain.table("drafts").put(sessionId, { version: 1, drafts });
|
|
753
|
+
return drafts.map((draft) => ({ ...draft }));
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
// src/host/transcript.ts
|
|
759
|
+
import { z as z2 } from "zod";
|
|
760
|
+
var transcriptSchema = z2.object({
|
|
761
|
+
entries: z2.array(z2.object({ role: z2.enum(["user", "assistant"]), text: z2.string(), turnId: z2.string().optional() })),
|
|
762
|
+
currentTurn: z2.number().int().nonnegative(),
|
|
763
|
+
sourceTurnId: z2.string().nullable(),
|
|
764
|
+
running: z2.boolean(),
|
|
765
|
+
pendingAssistant: z2.string().nullable()
|
|
457
766
|
});
|
|
458
767
|
function emptyTranscript() {
|
|
459
768
|
return { entries: [], currentTurn: 0, sourceTurnId: null, running: false, pendingAssistant: null };
|
|
@@ -492,36 +801,38 @@ function foldTranscript(state, event) {
|
|
|
492
801
|
}
|
|
493
802
|
|
|
494
803
|
// src/host/runtime.ts
|
|
495
|
-
var stringId =
|
|
496
|
-
var sessionRequest =
|
|
497
|
-
var
|
|
804
|
+
var stringId = z3.string().min(1).max(300);
|
|
805
|
+
var sessionRequest = z3.object({ sessionId: stringId });
|
|
806
|
+
var inputText = z3.string().max(64e3).refine((value) => charCount(value) <= 32e3);
|
|
807
|
+
var generationRequest = z3.object({
|
|
498
808
|
requestId: stringId,
|
|
499
809
|
sessionId: stringId,
|
|
500
|
-
operation:
|
|
501
|
-
sourceTurnId:
|
|
502
|
-
draft:
|
|
503
|
-
draftRev:
|
|
504
|
-
span:
|
|
810
|
+
operation: z3.enum(["optimize", "next", "complete"]),
|
|
811
|
+
sourceTurnId: z3.string().nullable(),
|
|
812
|
+
draft: z3.string().max(128e3),
|
|
813
|
+
draftRev: z3.number().int().nonnegative(),
|
|
814
|
+
span: z3.object({ start: z3.number().int().nonnegative(), end: z3.number().int().nonnegative(), draftRev: z3.number().int().nonnegative() }),
|
|
505
815
|
connectionGeneration: stringId,
|
|
506
|
-
routeKey:
|
|
507
|
-
targetText:
|
|
508
|
-
lockedFragments:
|
|
509
|
-
provider:
|
|
510
|
-
model:
|
|
816
|
+
routeKey: z3.string().max(1e3),
|
|
817
|
+
targetText: inputText.optional(),
|
|
818
|
+
lockedFragments: z3.array(inputText.refine((value) => value.length > 0)).max(100).optional(),
|
|
819
|
+
provider: z3.string().max(200).optional(),
|
|
820
|
+
model: z3.string().max(200).optional(),
|
|
821
|
+
manual: z3.boolean().optional()
|
|
511
822
|
});
|
|
512
823
|
var AssistantHostRuntime = class {
|
|
513
824
|
constructor(ctx, storage) {
|
|
514
825
|
this.ctx = ctx;
|
|
515
826
|
this.storage = storage;
|
|
516
827
|
}
|
|
517
|
-
mount =
|
|
828
|
+
mount = randomUUID2();
|
|
518
829
|
instances = /* @__PURE__ */ new WeakMap();
|
|
519
830
|
scheduler = new SessionScheduler();
|
|
520
831
|
disposed = false;
|
|
521
832
|
state(session) {
|
|
522
833
|
let state = this.instances.get(session);
|
|
523
834
|
if (!state) {
|
|
524
|
-
state = { generation: `${this.mount}:${
|
|
835
|
+
state = { generation: `${this.mount}:${randomUUID2()}`, nextTurn: null, nextRoutes: /* @__PURE__ */ new Set(), lastCompletionAt: -Infinity };
|
|
525
836
|
this.instances.set(session, state);
|
|
526
837
|
}
|
|
527
838
|
return state;
|
|
@@ -558,9 +869,29 @@ var AssistantHostRuntime = class {
|
|
|
558
869
|
...route ? { route } : {}
|
|
559
870
|
};
|
|
560
871
|
}
|
|
561
|
-
|
|
872
|
+
async models(signal) {
|
|
873
|
+
signal.throwIfAborted();
|
|
874
|
+
if (this.disposed) throw new AssistantError("DISPOSED", "\u8F93\u5165\u52A9\u624B\u5DF2\u505C\u7528");
|
|
875
|
+
const directory = this.ctx.get("sessionController");
|
|
876
|
+
if (!directory) throw new AssistantError("MODEL_CATALOG", "\u5F53\u524D\u5BBF\u4E3B\u672A\u63D0\u4F9B\u6A21\u578B\u76EE\u5F55\uFF0C\u8BF7\u4F7F\u7528\u624B\u52A8\u586B\u5199");
|
|
877
|
+
const catalog = await directory.modelCatalog();
|
|
878
|
+
signal.throwIfAborted();
|
|
879
|
+
if (this.disposed) throw new AssistantError("DISPOSED", "\u8F93\u5165\u52A9\u624B\u5DF2\u505C\u7528");
|
|
880
|
+
return {
|
|
881
|
+
groups: catalog.groups.map((group) => ({
|
|
882
|
+
provider: group.id,
|
|
883
|
+
name: group.name,
|
|
884
|
+
models: group.models.filter((model) => model.id.length > 0 && model.id.length <= 200).map((model) => ({ id: model.id, name: model.name }))
|
|
885
|
+
})).filter((group) => group.provider.length > 0 && group.provider.length <= 200 && group.models.length > 0),
|
|
886
|
+
// Provider diagnostics can include private details; advertise only the failed provider identity.
|
|
887
|
+
failures: catalog.failures.map((failure) => ({ provider: failure.id, name: failure.name }))
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
result(request, text, retryAfterMs) {
|
|
562
891
|
return {
|
|
563
892
|
text,
|
|
893
|
+
outcome: retryAfterMs === void 0 ? text === null ? "abstained" : "suggestion" : "throttled",
|
|
894
|
+
...retryAfterMs === void 0 ? {} : { retryAfterMs },
|
|
564
895
|
requestId: request.requestId,
|
|
565
896
|
sessionId: request.sessionId,
|
|
566
897
|
sourceTurnId: request.sourceTurnId,
|
|
@@ -594,20 +925,28 @@ var AssistantHostRuntime = class {
|
|
|
594
925
|
const state = this.state(session);
|
|
595
926
|
const transcript = this.transcript(session);
|
|
596
927
|
if (request.operation === "next") {
|
|
597
|
-
if (!settings.nextEnabled || transcript.running || request.sourceTurnId === null || request.draft.length > 0
|
|
598
|
-
state.lastNextTurn = request.sourceTurnId;
|
|
928
|
+
if (!request.manual && !settings.nextEnabled || transcript.running || request.sourceTurnId === null || request.draft.length > 0) return this.result(request, null);
|
|
599
929
|
} else if (request.operation === "complete") {
|
|
600
|
-
if (!settings.completionEnabled || transcript.running) return this.result(request, null);
|
|
601
|
-
const now = Date.now();
|
|
602
|
-
if (now - state.lastCompletionAt < settings.minIntervalMs) return this.result(request, null);
|
|
603
|
-
state.lastCompletionAt = now;
|
|
930
|
+
if (!request.manual && !settings.completionEnabled || transcript.running) return this.result(request, null);
|
|
604
931
|
}
|
|
605
932
|
const fallback = this.sessionRoute(session);
|
|
606
933
|
const provider = settings.provider || request.provider || fallback?.provider;
|
|
607
934
|
const model = settings.model || request.model || fallback?.model;
|
|
608
935
|
if (!provider || !model) throw new AssistantError("MODEL", "\u8BF7\u5148\u5728 DSH \u8F93\u5165\u6846\u9009\u62E9\u53EF\u7528\u6A21\u578B\uFF0C\u6216\u5728\u8F93\u5165\u52A9\u624B\u8BBE\u7F6E\u8F85\u52A9\u6A21\u578B");
|
|
936
|
+
const actualRoute = JSON.stringify([provider, model]);
|
|
937
|
+
if (request.operation === "next") {
|
|
938
|
+
if (state.nextTurn !== request.sourceTurnId) {
|
|
939
|
+
state.nextTurn = request.sourceTurnId;
|
|
940
|
+
state.nextRoutes.clear();
|
|
941
|
+
}
|
|
942
|
+
if (!request.manual && state.nextRoutes.has(actualRoute)) return this.result(request, null);
|
|
943
|
+
} else if (request.operation === "complete") {
|
|
944
|
+
const retryAfterMs = settings.minIntervalMs - (Date.now() - state.lastCompletionAt);
|
|
945
|
+
if (retryAfterMs > 0) return this.result(request, null, retryAfterMs);
|
|
946
|
+
}
|
|
609
947
|
const prepared = prepareGeneration(request, transcript.entries, settings);
|
|
610
948
|
const text = await this.scheduler.run(request.sessionId, request.requestId, request.operation, prepared.timeoutMs, async (active) => {
|
|
949
|
+
if (request.operation === "complete") state.lastCompletionAt = Date.now();
|
|
611
950
|
const call = await this.ctx.llm.prepareCall({ provider, model, maxTokens: prepared.maxOutputTokens }, active, (controls, info) => {
|
|
612
951
|
const effort = info.reasoning?.efforts[0]?.id;
|
|
613
952
|
return effort === void 0 ? { ...controls } : { ...controls, reasoningEffort: effort };
|
|
@@ -636,6 +975,10 @@ var AssistantHostRuntime = class {
|
|
|
636
975
|
return finishGeneration(raw, prepared);
|
|
637
976
|
}, signal);
|
|
638
977
|
this.current(request, session);
|
|
978
|
+
if (request.operation === "next") {
|
|
979
|
+
if (state.nextRoutes.size >= 512) state.nextRoutes.clear();
|
|
980
|
+
state.nextRoutes.add(actualRoute);
|
|
981
|
+
}
|
|
639
982
|
return this.result(request, text);
|
|
640
983
|
}
|
|
641
984
|
cancel(id, requestId) {
|
|
@@ -653,10 +996,12 @@ var AssistantHostRuntime = class {
|
|
|
653
996
|
switch (payload.method) {
|
|
654
997
|
case "generate":
|
|
655
998
|
return this.generate(payload.data, signal);
|
|
999
|
+
case "models/list":
|
|
1000
|
+
return this.models(signal);
|
|
656
1001
|
case "settings/get":
|
|
657
1002
|
return this.storage.settings();
|
|
658
1003
|
case "settings/set": {
|
|
659
|
-
const patch =
|
|
1004
|
+
const patch = z3.record(z3.string(), z3.unknown()).parse(payload.data);
|
|
660
1005
|
const result = await this.storage.updateSettings(patch);
|
|
661
1006
|
this.scheduler.cancelAll();
|
|
662
1007
|
return result;
|
|
@@ -664,7 +1009,7 @@ var AssistantHostRuntime = class {
|
|
|
664
1009
|
case "status":
|
|
665
1010
|
return this.status(sessionRequest.parse(payload.data).sessionId, signal);
|
|
666
1011
|
case "cancel": {
|
|
667
|
-
const request =
|
|
1012
|
+
const request = z3.object({ sessionId: stringId, requestId: stringId.optional() }).parse(payload.data);
|
|
668
1013
|
await this.session(request.sessionId, signal);
|
|
669
1014
|
return { cancelled: this.cancel(request.sessionId, request.requestId) };
|
|
670
1015
|
}
|
|
@@ -674,7 +1019,7 @@ var AssistantHostRuntime = class {
|
|
|
674
1019
|
return this.storage.list(request.sessionId);
|
|
675
1020
|
}
|
|
676
1021
|
case "drafts/save": {
|
|
677
|
-
const request =
|
|
1022
|
+
const request = z3.object({ sessionId: stringId, text: inputText, replaceId: stringId.optional() }).parse(payload.data);
|
|
678
1023
|
await this.session(request.sessionId, signal);
|
|
679
1024
|
if (!(await this.storage.settings()).stashEnabled) {
|
|
680
1025
|
throw new AssistantError("DISABLED", "\u8349\u7A3F\u6682\u5B58\u5DF2\u5173\u95ED\uFF0C\u8BF7\u5728\u8F93\u5165\u52A9\u624B\u8BBE\u7F6E\u4E2D\u542F\u7528\uFF1B\u5DF2\u6709\u8349\u7A3F\u4ECD\u53EF\u67E5\u770B\u6216\u5220\u9664");
|
|
@@ -682,7 +1027,7 @@ var AssistantHostRuntime = class {
|
|
|
682
1027
|
return this.storage.save(request.sessionId, request.text, request.replaceId);
|
|
683
1028
|
}
|
|
684
1029
|
case "drafts/delete": {
|
|
685
|
-
const request =
|
|
1030
|
+
const request = z3.object({ sessionId: stringId, id: stringId }).parse(payload.data);
|
|
686
1031
|
await this.session(request.sessionId, signal);
|
|
687
1032
|
return this.storage.delete(request.sessionId, request.id);
|
|
688
1033
|
}
|
|
@@ -691,79 +1036,20 @@ var AssistantHostRuntime = class {
|
|
|
691
1036
|
await this.session(request.sessionId, signal);
|
|
692
1037
|
return this.storage.delete(request.sessionId);
|
|
693
1038
|
}
|
|
1039
|
+
case "drafts/restore": {
|
|
1040
|
+
const request = z3.object({ sessionId: stringId, drafts: z3.array(draftSchema).max(3) }).parse(payload.data);
|
|
1041
|
+
await this.session(request.sessionId, signal);
|
|
1042
|
+
return this.storage.restore(request.sessionId, request.drafts);
|
|
1043
|
+
}
|
|
694
1044
|
}
|
|
695
1045
|
}
|
|
696
1046
|
};
|
|
697
1047
|
|
|
698
|
-
// src/host/storage.ts
|
|
699
|
-
import { z as z3 } from "zod";
|
|
700
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
701
|
-
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
702
|
-
var draftSchema = z3.object({ id: z3.string().min(1), text: z3.string().min(1).max(32e3), createdAt: z3.number().int().nonnegative() });
|
|
703
|
-
var sessionDraftsSchema = z3.object({ version: z3.literal(1), drafts: z3.array(draftSchema).max(3) });
|
|
704
|
-
function createDomainSpec(initial) {
|
|
705
|
-
return defineDomain({
|
|
706
|
-
name: "input_assistant",
|
|
707
|
-
version: 1,
|
|
708
|
-
layout: "per-record",
|
|
709
|
-
global: { schema: z3.object({ version: z3.literal(1), settings: z3.record(z3.string(), z3.unknown()) }), initial: { version: 1, settings: { ...initial } } },
|
|
710
|
-
tables: { drafts: domainTable(sessionDraftsSchema) }
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
var AssistantStorage = class {
|
|
714
|
-
constructor(domain) {
|
|
715
|
-
this.domain = domain;
|
|
716
|
-
}
|
|
717
|
-
chain = Promise.resolve();
|
|
718
|
-
transaction(work) {
|
|
719
|
-
const task = this.chain.then(async () => work(await this.domain()));
|
|
720
|
-
this.chain = task.then(() => void 0, () => void 0);
|
|
721
|
-
return task;
|
|
722
|
-
}
|
|
723
|
-
async settings() {
|
|
724
|
-
const domain = await this.domain();
|
|
725
|
-
return resolveStoredConfig(domain.global.get().settings);
|
|
726
|
-
}
|
|
727
|
-
updateSettings(patch) {
|
|
728
|
-
return this.transaction(async (domain) => {
|
|
729
|
-
const settings = resolveConfig({ ...resolveStoredConfig(domain.global.get().settings), ...patch });
|
|
730
|
-
await domain.global.set({ version: 1, settings });
|
|
731
|
-
return settings;
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
async list(sessionId) {
|
|
735
|
-
return (await this.domain()).table("drafts").get(sessionId)?.drafts.map((draft) => ({ ...draft })) ?? [];
|
|
736
|
-
}
|
|
737
|
-
save(sessionId, text, replaceId) {
|
|
738
|
-
if (text.trim().length === 0 || text.length > 32e3) throw new AssistantError("DRAFT", "\u6682\u5B58\u5185\u5BB9\u4E3A\u7A7A\u6216\u8D85\u8FC7 32,000 \u5B57\u7B26");
|
|
739
|
-
return this.transaction(async (domain) => {
|
|
740
|
-
const drafts = [...domain.table("drafts").get(sessionId)?.drafts ?? []];
|
|
741
|
-
const index = replaceId === void 0 ? -1 : drafts.findIndex((draft) => draft.id === replaceId);
|
|
742
|
-
if (replaceId !== void 0 && index < 0) throw new AssistantError("DRAFT_NOT_FOUND", "\u8981\u66FF\u6362\u7684\u6682\u5B58\u8349\u7A3F\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5237\u65B0\u5217\u8868");
|
|
743
|
-
if (drafts.length >= 3 && index < 0) throw new AssistantError("DRAFT_LIMIT", "\u5F53\u524D\u4F1A\u8BDD\u5DF2\u6682\u5B58\u4E09\u6761\uFF0C\u8BF7\u9009\u62E9\u66FF\u6362\u6216\u5220\u9664\u4E00\u6761");
|
|
744
|
-
const next = { id: replaceId ?? randomUUID2(), text, createdAt: Date.now() };
|
|
745
|
-
if (index < 0) drafts.push(next);
|
|
746
|
-
else drafts[index] = next;
|
|
747
|
-
await domain.table("drafts").put(sessionId, { version: 1, drafts });
|
|
748
|
-
return drafts.map((draft) => ({ ...draft }));
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
delete(sessionId, id) {
|
|
752
|
-
return this.transaction(async (domain) => {
|
|
753
|
-
const current = domain.table("drafts").get(sessionId)?.drafts ?? [];
|
|
754
|
-
const drafts = id === void 0 ? [] : current.filter((draft) => draft.id !== id);
|
|
755
|
-
if (drafts.length === 0) await domain.table("drafts").delete(sessionId);
|
|
756
|
-
else await domain.table("drafts").put(sessionId, { version: 1, drafts });
|
|
757
|
-
return drafts.map((draft) => ({ ...draft }));
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
};
|
|
761
|
-
|
|
762
1048
|
// src/host/rpc.ts
|
|
763
1049
|
import { z as z4 } from "zod";
|
|
764
1050
|
import { createTypertRemoteInfo } from "@deepseek-ai/dsh-typert-protocol/remote-info";
|
|
765
1051
|
var rpcPayloadSchema = z4.strictObject({
|
|
766
|
-
method: z4.enum(["status", "generate", "cancel", "drafts/list", "drafts/save", "drafts/delete", "drafts/clear", "settings/get", "settings/set"]),
|
|
1052
|
+
method: z4.enum(["status", "generate", "cancel", "models/list", "drafts/list", "drafts/save", "drafts/delete", "drafts/clear", "drafts/restore", "settings/get", "settings/set"]),
|
|
767
1053
|
data: z4.json()
|
|
768
1054
|
});
|
|
769
1055
|
var inputCodec = {
|
|
@@ -802,13 +1088,14 @@ var Config = schema.object({
|
|
|
802
1088
|
optimizationStrength: schema.union(["light", "standard", "deep"]).default("standard"),
|
|
803
1089
|
nextEnabled: schema.boolean().default(true),
|
|
804
1090
|
completionEnabled: schema.boolean().default(true),
|
|
1091
|
+
predictionDetail: schema.union(["concise", "standard", "detailed"]).default("standard"),
|
|
805
1092
|
displayMode: schema.union(["ghost", "panel"]).default("ghost"),
|
|
806
1093
|
debounceMs: schema.natural().min(200).max(5e3).default(600),
|
|
807
1094
|
minIntervalMs: schema.natural().min(500).max(3e4).default(2e3),
|
|
808
1095
|
maxHistoryChars: schema.natural().min(1e3).max(64e3).default(16e3),
|
|
809
1096
|
maxDraftChars: schema.natural().min(100).max(32e3).default(8e3),
|
|
810
|
-
maxNextChars: schema.natural().min(20).max(
|
|
811
|
-
maxCompletionChars: schema.natural().min(10).max(
|
|
1097
|
+
maxNextChars: schema.natural().min(20).max(1e3).default(120),
|
|
1098
|
+
maxCompletionChars: schema.natural().min(10).max(500).default(80),
|
|
812
1099
|
optimizeTimeoutMs: schema.natural().min(1e3).max(12e4).default(6e4),
|
|
813
1100
|
suggestionTimeoutMs: schema.natural().min(1e3).max(6e4).default(15e3),
|
|
814
1101
|
provider: schema.string().default(""),
|
|
@@ -834,7 +1121,15 @@ var InputAssistantService = class extends TypertRemoteService {
|
|
|
834
1121
|
function apply(ctx, config = {}) {
|
|
835
1122
|
const initial = resolveConfig({ ...DEFAULT_CONFIG, ...config });
|
|
836
1123
|
let opened;
|
|
837
|
-
const domain = () =>
|
|
1124
|
+
const domain = () => {
|
|
1125
|
+
if (opened) return opened;
|
|
1126
|
+
const attempt = Promise.resolve().then(() => ctx.storageDomain.open(createDomainSpec(initial)));
|
|
1127
|
+
opened = attempt;
|
|
1128
|
+
void attempt.catch(() => {
|
|
1129
|
+
if (opened === attempt) opened = void 0;
|
|
1130
|
+
});
|
|
1131
|
+
return attempt;
|
|
1132
|
+
};
|
|
838
1133
|
const runtime = new AssistantHostRuntime(ctx, new AssistantStorage(domain));
|
|
839
1134
|
ctx.sessionProjections.register({
|
|
840
1135
|
key: "inputAssistantTranscript",
|