@lynn123411/dsh-chat-translate 1.3.2 → 1.7.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 +14 -9
- package/docs//351/205/215/347/275/256.md +11 -1
- package/lib/client.js +1527 -489
- package/lib/client.js.map +4 -4
- package/lib/index.js +565 -173
- package/lib/index.js.map +4 -4
- package/lib/types/client/index.d.ts +7 -5
- package/lib/types/client/locales.d.ts +51 -4
- package/lib/types/client/reading-settings-page.d.ts +1 -1
- package/lib/types/client/settings/store.d.ts +17 -4
- package/lib/types/client/settings/ui.d.ts +5 -3
- package/lib/types/client/translate/api.d.ts +13 -0
- package/lib/types/client/translate/client-cache.d.ts +6 -1
- package/lib/types/client/translate/hold.d.ts +135 -0
- package/lib/types/client/translate/lazy.d.ts +8 -0
- package/lib/types/client/translate/mount.d.ts +22 -0
- package/lib/types/client/translate/observer.d.ts +57 -0
- package/lib/types/client/translate/think-button.d.ts +19 -0
- package/lib/types/client/translate/think.d.ts +55 -0
- package/lib/types/client/translate/viewport-observer.d.ts +14 -2
- package/lib/types/index.d.ts +11 -18
- package/lib/types/server/adapters/base.d.ts +1 -1
- package/lib/types/server/adapters/openai.d.ts +2 -2
- package/lib/types/server/cache.d.ts +5 -1
- package/lib/types/server/config.d.ts +35 -7
- package/lib/types/server/dispatcher.d.ts +30 -3
- package/lib/types/server/pipeline/mask-tokens.d.ts +54 -48
- package/lib/types/server/pipeline/masking.d.ts +35 -12
- package/lib/types/server/pipeline/think.d.ts +49 -0
- package/lib/types/server/router.d.ts +24 -3
- package/lib/types/server/types.d.ts +18 -1
- package/package.json +23 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { ITranslationAdapter, PluginConfig } from '../types.ts';
|
|
1
|
+
export type { ITranslationAdapter, PluginConfig, TranslateAdapterOptions, } from '../types.ts';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ITranslationAdapter, PluginConfig } from './base.ts';
|
|
1
|
+
import type { ITranslationAdapter, PluginConfig, TranslateAdapterOptions } from './base.ts';
|
|
2
2
|
import type { KeyReader } from '../credentials.ts';
|
|
3
3
|
export declare class OpenAiCompatibleAdapter implements ITranslationAdapter {
|
|
4
4
|
readonly id = "openai";
|
|
@@ -6,5 +6,5 @@ export declare class OpenAiCompatibleAdapter implements ITranslationAdapter {
|
|
|
6
6
|
private credentials;
|
|
7
7
|
constructor(credentials: KeyReader);
|
|
8
8
|
isAvailable(config: PluginConfig): boolean;
|
|
9
|
-
translate(text: string, signal: AbortSignal, config: PluginConfig): Promise<string>;
|
|
9
|
+
translate(text: string, signal: AbortSignal, config: PluginConfig, options?: TranslateAdapterOptions): Promise<string>;
|
|
10
10
|
}
|
|
@@ -6,8 +6,12 @@ export declare class LruDiskCache {
|
|
|
6
6
|
private dirty;
|
|
7
7
|
/** Legacy root-level cache file (<=1.1); moved under the plugin subdir. */
|
|
8
8
|
private legacyPath;
|
|
9
|
-
|
|
9
|
+
/** Only the original title cache carries a pre-1.2 root-level predecessor. */
|
|
10
|
+
private hasLegacyPredecessor;
|
|
11
|
+
constructor(maxEntries?: number, fileName?: string);
|
|
10
12
|
init(): Promise<void>;
|
|
13
|
+
/** Load one cache document into memory, dropping poisoned or malformed entries. */
|
|
14
|
+
private loadFile;
|
|
11
15
|
get(key: string): string | undefined;
|
|
12
16
|
set(key: string, value: string): void;
|
|
13
17
|
private scheduleSave;
|
|
@@ -5,6 +5,15 @@ export declare const MAX_CONCURRENCY = 100;
|
|
|
5
5
|
/** Bounds for the AI channel request timeout. */
|
|
6
6
|
export declare const AI_TIMEOUT_MIN = 500;
|
|
7
7
|
export declare const AI_TIMEOUT_MAX = 120000;
|
|
8
|
+
/**
|
|
9
|
+
* Bounds for the think-chain request timeout. A full reasoning block can take
|
|
10
|
+
* minutes on a local model, so this budget is far larger than the short-text
|
|
11
|
+
* AI timeout and is bounded separately.
|
|
12
|
+
*/
|
|
13
|
+
export declare const THINK_TIMEOUT_MIN = 500;
|
|
14
|
+
export declare const THINK_TIMEOUT_MAX = 900000;
|
|
15
|
+
/** Entries kept in the think-chain cache pool, separate from the title pool. */
|
|
16
|
+
export declare const THINK_CACHE_ENTRIES = 300;
|
|
8
17
|
/**
|
|
9
18
|
* The settings namespace this plugin owns. The user-editable layer lives in
|
|
10
19
|
* the DSH-managed document (~/.dsh/settings.yaml) under this key; the
|
|
@@ -25,6 +34,31 @@ export interface SettingsScopeLike {
|
|
|
25
34
|
/** Merge a patch into the user layer and persist through the provider. */
|
|
26
35
|
update(patch: Partial<PluginConfig>): Promise<unknown>;
|
|
27
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* One path-addressed edit to a settings namespace's user section, matching
|
|
39
|
+
* DSH's `SettingsPathOp` wire shape.
|
|
40
|
+
*/
|
|
41
|
+
export type SettingsPathOpLike = {
|
|
42
|
+
op: 'set';
|
|
43
|
+
path: string[];
|
|
44
|
+
value: unknown;
|
|
45
|
+
} | {
|
|
46
|
+
op: 'unset';
|
|
47
|
+
path: string[];
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Provider-level write face the legacy migration needs: the settings service
|
|
51
|
+
* itself (not the per-namespace owner scope), whose `mutate` applies path ops
|
|
52
|
+
* under one revision fence.
|
|
53
|
+
*/
|
|
54
|
+
export interface SettingsMigrationTarget {
|
|
55
|
+
describe(): Array<{
|
|
56
|
+
ns: string;
|
|
57
|
+
user?: unknown;
|
|
58
|
+
revision?: number;
|
|
59
|
+
}>;
|
|
60
|
+
mutate(ns: string, ops: readonly SettingsPathOpLike[], expectedRevision?: number): Promise<unknown>;
|
|
61
|
+
}
|
|
28
62
|
/**
|
|
29
63
|
* Config facade over the DSH `ctx.settings` service. No file I/O lives here
|
|
30
64
|
* anymore: persistence, atomic writes, external-edit hot reload and the
|
|
@@ -64,10 +98,4 @@ export declare function sanitizePatch(input: Record<string, unknown>): Partial<P
|
|
|
64
98
|
* or not a migration happened.
|
|
65
99
|
* @returns whether any legacy values were migrated.
|
|
66
100
|
*/
|
|
67
|
-
export declare function migrateLegacyConfigFile(settings:
|
|
68
|
-
describe(): Array<{
|
|
69
|
-
ns: string;
|
|
70
|
-
user?: unknown;
|
|
71
|
-
}>;
|
|
72
|
-
update(ns: string, patch: Record<string, unknown>): Promise<unknown>;
|
|
73
|
-
}, legacyPath: string): Promise<boolean>;
|
|
101
|
+
export declare function migrateLegacyConfigFile(settings: SettingsMigrationTarget, legacyPath: string): Promise<boolean>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { TranslateItemResult } from './types.ts';
|
|
1
|
+
import type { ThinkBlockResult, TranslateItemResult } from './types.ts';
|
|
2
2
|
import type { ConfigManager } from './config.ts';
|
|
3
|
-
import
|
|
3
|
+
import { LruDiskCache } from './cache.ts';
|
|
4
4
|
import type { KeyReader } from './credentials.ts';
|
|
5
5
|
export declare class TranslationDispatcher {
|
|
6
6
|
private configManager;
|
|
7
7
|
private cache;
|
|
8
|
+
/** 思考正文译文的独立缓存池,与工具标题的池互不挤占。 */
|
|
9
|
+
private thinkCache;
|
|
8
10
|
private credentials;
|
|
9
11
|
private masking;
|
|
10
12
|
private adapters;
|
|
@@ -12,7 +14,9 @@ export declare class TranslationDispatcher {
|
|
|
12
14
|
private inFlightMap;
|
|
13
15
|
private activeCount;
|
|
14
16
|
private queue;
|
|
15
|
-
|
|
17
|
+
/** 思考链请求的串行队列尾,保证同时最多一个在途请求。 */
|
|
18
|
+
private thinkTail;
|
|
19
|
+
constructor(configManager: ConfigManager, cache: LruDiskCache, credentials?: KeyReader, thinkCache?: LruDiskCache);
|
|
16
20
|
private registerAdapter;
|
|
17
21
|
/**
|
|
18
22
|
* Decide which channels are active for the current config, in priority order.
|
|
@@ -27,6 +31,29 @@ export declare class TranslationDispatcher {
|
|
|
27
31
|
private computeChannels;
|
|
28
32
|
translateBatch(texts: string[], forceRefresh?: boolean): Promise<TranslateItemResult[]>;
|
|
29
33
|
translateOne(rawText: string, forceRefresh?: boolean): Promise<TranslateItemResult>;
|
|
34
|
+
/**
|
|
35
|
+
* 翻译思考正文的块。整条链路与工具标题翻译分离:只走 AI 通道、独立串行
|
|
36
|
+
* 队列、独立超时、独立缓存池,Bing 从不参与。
|
|
37
|
+
*
|
|
38
|
+
* 每个块先按输入上限切成片段,再各自掩码、相邻片段打包成一个请求;整批
|
|
39
|
+
* 失败时退回逐片段单发,仍失败的片段保留原文。块内任一片段失败即整块不译,
|
|
40
|
+
* 避免半中半英的段落。
|
|
41
|
+
*/
|
|
42
|
+
translateThinkBlocks(blocks: string[]): Promise<ThinkBlockResult[]>;
|
|
43
|
+
/**
|
|
44
|
+
* 一整批一次请求;失败则该批逐片段单发重试一次,仍失败的片段直接放弃
|
|
45
|
+
* (保留原文),不在界面上留提示。
|
|
46
|
+
*/
|
|
47
|
+
private translateThinkBatch;
|
|
48
|
+
/** 发出一次思考链请求并把结果还原成每个片段的最终译文。 */
|
|
49
|
+
private requestThinkBatch;
|
|
50
|
+
/** 串行执行器:同时最多一个在途请求,与工具标题的并发池完全独立。 */
|
|
51
|
+
private runThinkSerial;
|
|
52
|
+
/**
|
|
53
|
+
* 只读判断某通道是否还在熔断冷却期。思考链翻译只借用这个判断避开已经
|
|
54
|
+
* 出问题的服务,不改动熔断状态本身(那是工具标题通道的记账)。
|
|
55
|
+
*/
|
|
56
|
+
private isChannelCoolingDown;
|
|
30
57
|
testChannel(channelId: string): Promise<{
|
|
31
58
|
ok: boolean;
|
|
32
59
|
latencyMs: number;
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mask placeholder tokens — single source of truth for the wire format, the
|
|
3
|
-
*
|
|
3
|
+
* matcher and the leftover detector.
|
|
4
4
|
*
|
|
5
|
-
* Wire format:
|
|
5
|
+
* Wire format: `⟦xkbdt3⟧` — U+27E6/U+27E7 (mathematical white square brackets)
|
|
6
|
+
* around `<4-letter random id><index>`.
|
|
6
7
|
*
|
|
7
|
-
* Design constraints, each one a measured failure of
|
|
8
|
-
*
|
|
8
|
+
* Design constraints, each one a measured failure of an earlier format against
|
|
9
|
+
* real MT engines:
|
|
9
10
|
*
|
|
10
|
-
* - No
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* the
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* already sit in the on-disk and browser caches.
|
|
11
|
+
* - No letters spelling a pronounceable word and no underscores. The previous
|
|
12
|
+
* `__DSHMASKxkbdt_3__` format named a token that small models abbreviated
|
|
13
|
+
* back to its recognizable core: the UI showed bare `DSH` runs instead of
|
|
14
|
+
* the protected fragments.
|
|
15
|
+
* - Random letters keep two mask passes in the same session from colliding and
|
|
16
|
+
* make the token unambiguous in the translated text.
|
|
17
|
+
* - `matchMaskToken` accepts exactly what this module emits (plus the same
|
|
18
|
+
* token with a dropped closing bracket, an engine rewrite seen in practice).
|
|
19
|
+
* It deliberately does NOT accept anything else: a translation that damaged
|
|
20
|
+
* a token beyond recognition is discarded rather than repaired, because
|
|
21
|
+
* repairing it is what put mixed or duplicated text on screen.
|
|
22
22
|
*/
|
|
23
23
|
export declare function randomTokenId(): string;
|
|
24
24
|
export interface MaskTokenFormat {
|
|
@@ -29,48 +29,54 @@ export interface MaskTokenFormat {
|
|
|
29
29
|
}
|
|
30
30
|
export declare function createMaskTokenFormat(): MaskTokenFormat;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* separator may be replaced by whitespace/dot/hyphen. The two index branches
|
|
43
|
-
* stay unambiguous: an id is always a letter run, the index always digits.
|
|
44
|
-
* Capture groups: `[1] = legacy index` (undefined for the current format),
|
|
45
|
-
* `[2] = token id` (undefined for the legacy format), `[3] = current index`.
|
|
32
|
+
* Scanner for complete tokens of this format. A token whose closing bracket the
|
|
33
|
+
* engine dropped is still resolved (see `matchMaskToken`), but it is not
|
|
34
|
+
* recognized as a complete token here: the space `mask()` inserts sits outside
|
|
35
|
+
* that token, and matching a truncated one would strip a space of the
|
|
36
|
+
* translation's own.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MASK_TOKEN_PATTERN_SOURCE = "\u27E6([a-z]{4})(\\d+)\u27E7";
|
|
39
|
+
/**
|
|
40
|
+
* The opening bracket, the id and the index of a token, without its closing
|
|
41
|
+
* bracket. Used to resolve a token the engine truncated.
|
|
46
42
|
*/
|
|
47
|
-
export declare const
|
|
43
|
+
export declare const MASK_TOKEN_PREFIX_PATTERN_SOURCE = "\u27E6\\s*([a-z]{4})(\\d+)";
|
|
44
|
+
/** Regex source for one retired-format token, for text that still contains one. */
|
|
45
|
+
export declare const LEGACY_MASK_TOKEN_PATTERN_SOURCE = "_{1,2}DSH\\s*_*\\s*MASKx?\\s*(?:_?\\s*\\d+|_*([a-z]{2,8})\\s*_+\\s*(\\d+))_{0,2}";
|
|
48
46
|
export interface MaskTokenMatch {
|
|
47
|
+
/** Index into the mask list of the `mask()` call that produced the token. */
|
|
49
48
|
index: number;
|
|
50
|
-
/** false when the token carried an id from a different `mask()` call. */
|
|
51
|
-
acceptsId: boolean;
|
|
52
49
|
/** Length of the matched token in characters. */
|
|
53
50
|
length: number;
|
|
51
|
+
/** True when the closing bracket was missing and only the prefix matched. */
|
|
52
|
+
truncated: boolean;
|
|
54
53
|
}
|
|
55
54
|
/**
|
|
56
|
-
* Match
|
|
55
|
+
* Match exactly one mask token of this format at `start` in `text`.
|
|
57
56
|
*
|
|
58
|
-
*
|
|
59
|
-
* pass
|
|
60
|
-
*
|
|
57
|
+
* The id must match the id of the masking pass that owns the token: every mask
|
|
58
|
+
* pass carries its own random id, so a token carrying another id belongs to
|
|
59
|
+
* another call and must not be resolved here.
|
|
61
60
|
*/
|
|
62
61
|
export declare function matchMaskToken(text: string, start: number, id: string): MaskTokenMatch | null;
|
|
63
|
-
/**
|
|
62
|
+
/** Every token of this format in `text`, left to right. */
|
|
63
|
+
export declare function findMaskTokens(text: string): Array<{
|
|
64
|
+
match: MaskTokenMatch;
|
|
65
|
+
raw: string;
|
|
66
|
+
}>;
|
|
67
|
+
/** True when `text` still carries a token of the current format. */
|
|
64
68
|
export declare function hasMaskResidue(text: string): boolean;
|
|
65
|
-
/**
|
|
66
|
-
export declare function
|
|
69
|
+
/** True when `text` carries a token of a retired format. */
|
|
70
|
+
export declare function hasLegacyMaskResidue(text: string): boolean;
|
|
67
71
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* Residue test for a value that must never be shown or cached: either a
|
|
73
|
+
* current-format token or any retired-format token.
|
|
74
|
+
*/
|
|
75
|
+
export declare function hasAnyMaskResidue(text: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Retired-format tokens inside source text, so `mask()` can protect them with a
|
|
78
|
+
* current-format token of their own. A source that talks about the old
|
|
79
|
+
* placeholder format must survive translation, and the unmask step rejects any
|
|
80
|
+
* retired-format token it sees.
|
|
75
81
|
*/
|
|
76
|
-
export declare function
|
|
82
|
+
export declare function findLegacyMaskTokens(text: string): string[];
|
|
@@ -1,27 +1,50 @@
|
|
|
1
|
+
import { hasMaskResidue } from './mask-tokens.ts';
|
|
1
2
|
export interface MaskResult {
|
|
2
3
|
maskedText: string;
|
|
4
|
+
/**
|
|
5
|
+
* Restore every protected fragment. Throws `MaskRestoreError` when the
|
|
6
|
+
* translated text did not carry the token sequence back intact; the caller
|
|
7
|
+
* must discard that translation instead of showing a repaired guess.
|
|
8
|
+
*/
|
|
3
9
|
unmask: (translatedText: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Retired-format tokens the source itself contained. They come back on
|
|
12
|
+
* purpose and are content, so the caller's leak check must not flag them.
|
|
13
|
+
*/
|
|
14
|
+
legacyFragments: string[];
|
|
15
|
+
}
|
|
16
|
+
/** Which spaces `mask()` inserted directly before and after a token. */
|
|
17
|
+
export interface InsertedSpacing {
|
|
18
|
+
leading: boolean;
|
|
19
|
+
trailing: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare class MaskRestoreError extends Error {
|
|
22
|
+
readonly expectedCount: number;
|
|
23
|
+
readonly foundCount: number;
|
|
24
|
+
constructor(message: string, expectedCount: number, foundCount: number);
|
|
4
25
|
}
|
|
5
26
|
export declare class ContentMaskingPipeline {
|
|
6
27
|
mask(text: string): MaskResult;
|
|
7
28
|
}
|
|
8
29
|
/**
|
|
9
|
-
*
|
|
30
|
+
* Restore every token of `id` in `text` from `masks`.
|
|
31
|
+
*
|
|
32
|
+
* Each fragment of the pass is resolved exactly once, no matter how the engine
|
|
33
|
+
* reordered the sentence: the index embedded in the token identifies the
|
|
34
|
+
* fragment, and the random id identifies the masking pass that owns it. The
|
|
35
|
+
* spaces recorded in `inserted` are removed together with their token, so the
|
|
36
|
+
* spacing the translation produced around the fragment survives untouched.
|
|
10
37
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
38
|
+
* @throws MaskRestoreError when the text does not carry every token exactly
|
|
39
|
+
* once, or still shows a current-format token or a retired token that the
|
|
40
|
+
* source did not already contain — the caller discards such a translation
|
|
41
|
+
* instead of rendering a repaired guess.
|
|
14
42
|
*/
|
|
15
|
-
export declare function replaceMaskTokens(text: string, id: string,
|
|
43
|
+
export declare function replaceMaskTokens(text: string, id: string, masks: string[], inserted?: InsertedSpacing[], legacyIndexes?: ReadonlySet<number>): string;
|
|
16
44
|
/**
|
|
17
|
-
* True when a translated string
|
|
45
|
+
* True when a translated string still shows a mask token to the user.
|
|
18
46
|
* Callers use it to discard a poisoned translation (and to evict poisoned
|
|
19
47
|
* cache entries written by earlier releases).
|
|
20
48
|
*/
|
|
21
49
|
export declare function isMaskLeak(translatedText: string): boolean;
|
|
22
|
-
|
|
23
|
-
* Same as `isMaskLeak`, but a token the source text already contained is not a
|
|
24
|
-
* leak: text that talks about placeholders (this plugin's docs, a bug report)
|
|
25
|
-
* must round-trip untouched.
|
|
26
|
-
*/
|
|
27
|
-
export declare function isMaskLeakAgainst(originalText: string, translatedText: string): boolean;
|
|
50
|
+
export { hasMaskResidue };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 思考正文翻译的请求预算。每次请求的输入估算 token 上限,与本地服务的
|
|
3
|
+
* prefill 批次(MAX_NUM_BATCHED_TOKENS)保持同一量级;输出上限写进请求体的
|
|
4
|
+
* max_tokens,避免长文本把剩余上下文全部花在生成上。
|
|
5
|
+
*/
|
|
6
|
+
export declare const THINK_MAX_INPUT_TOKENS = 4096;
|
|
7
|
+
export declare const THINK_MAX_OUTPUT_TOKENS = 8192;
|
|
8
|
+
/**
|
|
9
|
+
* 保守估算一段文本占用的 token 数:汉字一字一 token,其余每三个字符一 token。
|
|
10
|
+
* 实测本机 hy-mt2-1.8b 的英文语料约为每 3.3 字符一 token,这里取 3 作为上界,
|
|
11
|
+
* 宁可把请求切得比实际需要更碎,也不让服务端收到超出预期的 prefill。
|
|
12
|
+
*/
|
|
13
|
+
export declare function estimateTokens(text: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* 把一个块切成不超过上限的片段:优先空行,其次单行,再次句末,最后硬切。
|
|
16
|
+
* 片段按原顺序拼回即为该块的完整译文。
|
|
17
|
+
*/
|
|
18
|
+
export declare function splitOversizedBlock(text: string, maxTokens?: number): string[];
|
|
19
|
+
/** 一个待翻译的片段:属于哪个块、块内第几段、以及该片段的掩码结果。 */
|
|
20
|
+
export interface ThinkPieceShell<TMask> {
|
|
21
|
+
block: number;
|
|
22
|
+
index: number;
|
|
23
|
+
text: string;
|
|
24
|
+
mask: TMask;
|
|
25
|
+
}
|
|
26
|
+
/** 把片段按原顺序打包成尽量少的请求,且每批不超过输入上限。 */
|
|
27
|
+
export declare function packPieces<T extends {
|
|
28
|
+
text: string;
|
|
29
|
+
}>(pieces: T[], maxTokens?: number): T[][];
|
|
30
|
+
/**
|
|
31
|
+
* 块分隔标记:`⟪<4 letters><index>⟫`。外括号刻意与掩码占位符的 ⟦⟧ 不同形,
|
|
32
|
+
* 掩码残留检测(只认 ⟦⟧)因此不会把这个标记当成泄漏。
|
|
33
|
+
*/
|
|
34
|
+
export interface ThinkBatchFormat {
|
|
35
|
+
id: string;
|
|
36
|
+
token: (index: number) => string;
|
|
37
|
+
}
|
|
38
|
+
export declare function createThinkBatchFormat(): ThinkBatchFormat;
|
|
39
|
+
/** 每个片段以单独一行标记开头,段与段之间空一行。 */
|
|
40
|
+
export declare function buildBatchPayload(pieces: string[], format: ThinkBatchFormat): string;
|
|
41
|
+
/**
|
|
42
|
+
* 按标记把整批译文切回每个片段。
|
|
43
|
+
*
|
|
44
|
+
* 序号必须恰好出现一次、按 0..count-1 升序、每段非空,且不得混入其它批次
|
|
45
|
+
* 的标记;任何一条不满足都返回 null,由调用方作废整批并退回单块重试。
|
|
46
|
+
*/
|
|
47
|
+
export declare function splitBatchTranslation(translated: string, format: ThinkBatchFormat, count: number): string[] | null;
|
|
48
|
+
/** 翻译结果里是否还留着块标记(用于判定整批作废)。 */
|
|
49
|
+
export declare function hasThinkBatchResidue(text: string): boolean;
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ConfigManager } from './config.ts';
|
|
1
|
+
import type { ConnectionFetchRoute } from '@deepseek-ai/dsh-client-connection';
|
|
3
2
|
import type { TranslationDispatcher } from './dispatcher.ts';
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Translation proxy surface, carried by Connection's exact Fetch routes below
|
|
5
|
+
* the shared `/api` channel.
|
|
6
|
+
*
|
|
7
|
+
* Config and credentials have no HTTP endpoints: since 1.2 the settings panel
|
|
8
|
+
* reads and writes through DSH's own channels — the client `SettingsScope`
|
|
9
|
+
* service and the `credentials` Remote API — so the plugin owns exactly three
|
|
10
|
+
* routes: short-text batch translation, think-chain block translation, and the
|
|
11
|
+
* channel probe.
|
|
12
|
+
*/
|
|
13
|
+
/** Batch-translation route path. */
|
|
14
|
+
export declare const TRANSLATE_ROUTE_PATH = "/api/dsh-chat-translate/translate";
|
|
15
|
+
/** Single-channel probe route path. */
|
|
16
|
+
export declare const TEST_CHANNEL_ROUTE_PATH = "/api/dsh-chat-translate/test-channel";
|
|
17
|
+
/** Think-chain block translation route path. */
|
|
18
|
+
export declare const THINK_ROUTE_PATH = "/api/dsh-chat-translate/translate-think";
|
|
19
|
+
/**
|
|
20
|
+
* Build the plugin's exact Fetch routes.
|
|
21
|
+
* @param dispatcher - translation coordinator the routes proxy to.
|
|
22
|
+
* @param ready - settles once host-side async resources are usable; every
|
|
23
|
+
* request waits for it before touching the dispatcher.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createFetchRoutes(dispatcher: TranslationDispatcher, ready?: Promise<unknown>): ConnectionFetchRoute[];
|
|
@@ -3,8 +3,10 @@ export interface PluginConfig {
|
|
|
3
3
|
concurrency: number;
|
|
4
4
|
timeoutMs: number;
|
|
5
5
|
aiTimeoutMs: number;
|
|
6
|
+
thinkTimeoutMs: number;
|
|
6
7
|
aiEnabled: boolean;
|
|
7
8
|
bingEnabled: boolean;
|
|
9
|
+
thinkEnabled: boolean;
|
|
8
10
|
baseUrl: string;
|
|
9
11
|
model: string;
|
|
10
12
|
targetLang: string;
|
|
@@ -20,9 +22,24 @@ export interface TranslateResponse {
|
|
|
20
22
|
results: TranslateItemResult[];
|
|
21
23
|
error?: string;
|
|
22
24
|
}
|
|
25
|
+
/** Per-request knobs an adapter may honor; channels that cannot use them ignore the argument. */
|
|
26
|
+
export interface TranslateAdapterOptions {
|
|
27
|
+
/** Generation cap written into the request body; omitted lets the server decide. */
|
|
28
|
+
maxTokens?: number;
|
|
29
|
+
/** Prompt family: one plain sentence, or a marked multi-part packing. */
|
|
30
|
+
mode?: 'plain' | 'blocks';
|
|
31
|
+
}
|
|
23
32
|
export interface ITranslationAdapter {
|
|
24
33
|
readonly id: string;
|
|
25
34
|
readonly name: string;
|
|
26
35
|
isAvailable(config: PluginConfig): boolean;
|
|
27
|
-
translate(text: string, signal: AbortSignal, config: PluginConfig): Promise<string>;
|
|
36
|
+
translate(text: string, signal: AbortSignal, config: PluginConfig, options?: TranslateAdapterOptions): Promise<string>;
|
|
37
|
+
}
|
|
38
|
+
/** One reasoning block's outcome, aligned by index with the request's block list. */
|
|
39
|
+
export interface ThinkBlockResult {
|
|
40
|
+
original: string;
|
|
41
|
+
translated: string;
|
|
42
|
+
ok: boolean;
|
|
43
|
+
cached: boolean;
|
|
44
|
+
channel: string;
|
|
28
45
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynn123411/dsh-chat-translate",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Tool-call & think-
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Tool-call & think-chain translation with dual AI (OpenAI-compatible) + Bing channels for the DeepSeek Harness Web UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "lynn123411",
|
|
7
7
|
"repository": {
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
"client": {
|
|
48
48
|
"platform": "web",
|
|
49
49
|
"immediately": true,
|
|
50
|
-
"inject": [
|
|
50
|
+
"inject": [
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
52
|
+
]
|
|
51
53
|
}
|
|
52
54
|
},
|
|
53
55
|
"files": [
|
|
@@ -59,17 +61,22 @@
|
|
|
59
61
|
"scripts": {
|
|
60
62
|
"build": "node build.mjs",
|
|
61
63
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
62
|
-
"test": "node scripts/test-client-wiring.mjs && node scripts/test-regression-suite.mjs && node scripts/test-channel-logic.mjs && node scripts/test-store.mjs && node scripts/test-toggle-restore.mjs"
|
|
64
|
+
"test": "node scripts/test-client-wiring.mjs && node scripts/test-regression-suite.mjs && node scripts/test-channel-logic.mjs && node scripts/test-store.mjs && node scripts/test-toggle-restore.mjs && node scripts/test-hold.mjs && node scripts/test-think-pipeline.mjs && node scripts/test-think-client.mjs",
|
|
65
|
+
"assert:lib-untracked": "node scripts/assert-lib-untracked.mjs",
|
|
66
|
+
"check": "pnpm typecheck && pnpm build && pnpm test && pnpm assert:lib-untracked"
|
|
63
67
|
},
|
|
64
68
|
"engines": {
|
|
65
|
-
"node": "^22.19 || >=24"
|
|
69
|
+
"node": "^22.19 || >=24",
|
|
70
|
+
"dsh": "0.1.6-alpha.2"
|
|
66
71
|
},
|
|
67
72
|
"devDependencies": {
|
|
68
73
|
"@deepseek-ai/cordis": "4.0.2",
|
|
69
|
-
"@deepseek-ai/dsh-client-
|
|
70
|
-
"@deepseek-ai/dsh-client-ui-
|
|
71
|
-
"@deepseek-ai/dsh-client-ui-
|
|
72
|
-
"@deepseek-ai/dsh-
|
|
74
|
+
"@deepseek-ai/dsh-client-connection": "0.1.6-alpha.2",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.6-alpha.2",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.6-alpha.2",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.6-alpha.2",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.6-alpha.2",
|
|
79
|
+
"@deepseek-ai/dsh-home-paths": "0.1.6-alpha.2",
|
|
73
80
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
74
81
|
"@eslint/js": "^9.30.0",
|
|
75
82
|
"@types/node": "^22.19.0",
|
|
@@ -79,6 +86,12 @@
|
|
|
79
86
|
"typescript": "^6.0.0"
|
|
80
87
|
},
|
|
81
88
|
"peerDependencies": {
|
|
82
|
-
"@deepseek-ai/dsh-
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-primitives": "*",
|
|
90
|
+
"@deepseek-ai/dsh-home-paths": "0.1.6-alpha.2"
|
|
91
|
+
},
|
|
92
|
+
"peerDependenciesMeta": {
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-primitives": {
|
|
94
|
+
"optional": true
|
|
95
|
+
}
|
|
83
96
|
}
|
|
84
97
|
}
|