@lynn123411/dsh-chat-translate 1.3.2 → 1.5.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 +12 -8
- package/docs//351/205/215/347/275/256.md +11 -1
- package/lib/client.js +945 -344
- 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 +6 -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/mount.d.ts +22 -0
- package/lib/types/client/translate/observer.d.ts +17 -0
- package/lib/types/client/translate/think-button.d.ts +19 -0
- package/lib/types/client/translate/think.d.ts +52 -0
- 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,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.5.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-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
|
}
|