@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.
Files changed (32) hide show
  1. package/README.md +14 -9
  2. package/docs//351/205/215/347/275/256.md +11 -1
  3. package/lib/client.js +1527 -489
  4. package/lib/client.js.map +4 -4
  5. package/lib/index.js +565 -173
  6. package/lib/index.js.map +4 -4
  7. package/lib/types/client/index.d.ts +7 -5
  8. package/lib/types/client/locales.d.ts +51 -4
  9. package/lib/types/client/reading-settings-page.d.ts +1 -1
  10. package/lib/types/client/settings/store.d.ts +17 -4
  11. package/lib/types/client/settings/ui.d.ts +5 -3
  12. package/lib/types/client/translate/api.d.ts +13 -0
  13. package/lib/types/client/translate/client-cache.d.ts +6 -1
  14. package/lib/types/client/translate/hold.d.ts +135 -0
  15. package/lib/types/client/translate/lazy.d.ts +8 -0
  16. package/lib/types/client/translate/mount.d.ts +22 -0
  17. package/lib/types/client/translate/observer.d.ts +57 -0
  18. package/lib/types/client/translate/think-button.d.ts +19 -0
  19. package/lib/types/client/translate/think.d.ts +55 -0
  20. package/lib/types/client/translate/viewport-observer.d.ts +14 -2
  21. package/lib/types/index.d.ts +11 -18
  22. package/lib/types/server/adapters/base.d.ts +1 -1
  23. package/lib/types/server/adapters/openai.d.ts +2 -2
  24. package/lib/types/server/cache.d.ts +5 -1
  25. package/lib/types/server/config.d.ts +35 -7
  26. package/lib/types/server/dispatcher.d.ts +30 -3
  27. package/lib/types/server/pipeline/mask-tokens.d.ts +54 -48
  28. package/lib/types/server/pipeline/masking.d.ts +35 -12
  29. package/lib/types/server/pipeline/think.d.ts +49 -0
  30. package/lib/types/server/router.d.ts +24 -3
  31. package/lib/types/server/types.d.ts +18 -1
  32. 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
- constructor(maxEntries?: number);
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 type { LruDiskCache } from './cache.ts';
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
- constructor(configManager: ConfigManager, cache: LruDiskCache, credentials?: KeyReader);
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
- * tolerant matcher and the leftover detector.
3
+ * matcher and the leftover detector.
4
4
  *
5
- * Wire format: `__DSHMASKx<letters>_<index>__` (e.g. `__DSHMASKxkbdt_3__`).
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 the previous
8
- * `__DSH_MASK_<index>__` format against real MT engines:
8
+ * Design constraints, each one a measured failure of an earlier format against
9
+ * real MT engines:
9
10
  *
10
- * - No underscore between `DSH` and `MASK`. Small models routinely drop that
11
- * one separator (`__DSH_MASK_1__` -> `__DSHMASK_1__`), which defeated the
12
- * old strict unmask regex and leaked the raw token into the UI.
13
- * - A random letter run (`x<letters>`) makes the token collision-free against
14
- * the source text and unambiguous to parse: the index is the digit run after
15
- * the last `_`, so no lookbehind is needed.
16
- * - The leading/trailing `__` stay: they survive MT and mark the token as
17
- * emphasis to the model.
18
- *
19
- * `matchMaskToken` stays tolerant of the *legacy* format and of the spacing /
20
- * casing / separator damage observed in the wild, because poisoned translations
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
- * Loose matcher covering every observed engine rewrite:
33
- *
34
- * __DSHMASKxkbdt_3__ exact
35
- * __DSHMASKXKBDT_3__ case folded
36
- * __DSH MASK xkbdt _ 3__ spaces inserted around the structural underscores
37
- * __DSHMASKkbdt_3__ the `x` id separator eaten
38
- * _DSHMASKxkbdt_3_ one boundary underscore dropped
39
- * __DSH_MASK_3__ / __dsh_mask_3__ legacy format (no random id)
40
- *
41
- * Both boundaries and the internal separators are optional, and the `x` id
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 MASK_TOKEN_PATTERN_SOURCE = "_*\\s*DSH\\s*_*\\s*MASK\\s*(?:_?\\s*(\\d+)|_*[xX]?[\\s._-]*([a-z]{2,8})\\s*_+\\s*(\\d+))(?:_{0,2}(?=[^\\w]|$))?";
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 a single mask token at `start` in `text`.
55
+ * Match exactly one mask token of this format at `start` in `text`.
57
56
  *
58
- * `acceptsId` is false when the token's random id belongs to another masking
59
- * pass — the caller must then leave the token untouched, because its index
60
- * would resolve to unrelated content.
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
- /** True when translated text still carries a mask token of any format. */
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
- /** Every mask-token-looking fragment in `text`, verbatim. */
66
- export declare function maskResidues(text: string): string[];
69
+ /** True when `text` carries a token of a retired format. */
70
+ export declare function hasLegacyMaskResidue(text: string): boolean;
67
71
  /**
68
- * True when `translatedText` exposes a mask token that was NOT already part of
69
- * `originalText`.
70
- *
71
- * A placeholder surviving the round trip is a leak; but a source text that
72
- * legitimately talks *about* placeholders (this plugin's own documentation,
73
- * say) must survive translation, so tokens the source already carried are not
74
- * treated as residue.
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 hasNewMaskResidue(originalText: string, translatedText: string): boolean;
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
- * Resolve every in-range mask token in `text` through `resolve`.
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
- * A token whose random id belongs to another masking pass, an out-of-range
12
- * index and any unmatched text are all kept verbatim; `hasMaskResidue()` on the
13
- * result is what flags a leaked token.
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, resolve: (index: number) => string | undefined): 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 would still show a mask token to the user.
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 { IncomingMessage, ServerResponse } from 'node:http';
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
- export declare function createHttpHandler(configManager: ConfigManager, dispatcher: TranslationDispatcher): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
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.3.2",
4
- "description": "Tool-call & think-summary translation with dual AI (OpenAI-compatible) + Bing channels for the DeepSeek Harness Web UI",
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-ui-renderer": "0.1.5-rc.1",
70
- "@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.1",
71
- "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.1",
72
- "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.1",
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-home-paths": "^0.1.5-rc.1"
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
  }