dsh-repeat-guard 0.2.0 → 0.2.2
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/LICENSE +21 -21
- package/README.md +121 -115
- package/cordis.patch.yml +7 -7
- package/lib/resume.d.ts +2 -2
- package/lib/resume.js +1 -1
- package/package.json +64 -64
- package/src/client.tsx +459 -459
- package/src/config.ts +171 -171
- package/src/detect.ts +86 -86
- package/src/index.ts +48 -48
- package/src/resume.ts +62 -60
- package/src/stream-guard.ts +118 -118
- package/src/turn-stopping-guard.ts +23 -23
- package/src/types.ts +13 -13
package/src/resume.ts
CHANGED
|
@@ -1,60 +1,62 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 续跑:截断之后让本轮不要就此关闭,而是再跑一步。
|
|
3
|
-
*
|
|
4
|
-
* 机制在 dsh 侧:`agent/turn-stopping` 是 serial 事件,在本轮边界提交之前被 await,
|
|
5
|
-
* 之后收件箱会被重新读一次(dsh-agent-loop/lib/index.js:967-973):
|
|
6
|
-
*
|
|
7
|
-
* if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
8
|
-
* await this.dispatch.serial("agent/turn-stopping", { turn, signal });
|
|
9
|
-
* }
|
|
10
|
-
* if (turnEnds && this.inbox.nextStep.length === 0) break; // 重读,读到东西就不 break
|
|
11
|
-
*
|
|
12
|
-
* 所以在监听器里调 `agent.steer(...)` 推入一条输入,本轮就会再跑一步,而不是停下来等
|
|
13
|
-
* 用户输入。dsh 自己的 dsh-hooks-claude-code/lib/index.js:300 就是这么用的。
|
|
14
|
-
*
|
|
15
|
-
* 推什么内容由配置决定(见 config.ts 的 resumeText / resumeSummary)。
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
19
|
-
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
20
|
-
import type { ContextFormed } from '@deepseek-ai/dsh-llm';
|
|
21
|
-
import type { RepeatGuardConfig } from './config.js';
|
|
22
|
-
import type { GuardState } from './types.js';
|
|
23
|
-
|
|
24
|
-
// dsh 0.1.7 起 `MessageSourceMap` 没有通用的 `plugin` kind:每个生产者在自己的模块里
|
|
25
|
-
// 声明自己的 kind(merge-extensible),`form` 另由 ContextFormed 提供。
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
* @param
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 续跑:截断之后让本轮不要就此关闭,而是再跑一步。
|
|
3
|
+
*
|
|
4
|
+
* 机制在 dsh 侧:`agent/turn-stopping` 是 serial 事件,在本轮边界提交之前被 await,
|
|
5
|
+
* 之后收件箱会被重新读一次(dsh-agent-loop/lib/index.js:967-973):
|
|
6
|
+
*
|
|
7
|
+
* if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
8
|
+
* await this.dispatch.serial("agent/turn-stopping", { turn, signal });
|
|
9
|
+
* }
|
|
10
|
+
* if (turnEnds && this.inbox.nextStep.length === 0) break; // 重读,读到东西就不 break
|
|
11
|
+
*
|
|
12
|
+
* 所以在监听器里调 `agent.steer(...)` 推入一条输入,本轮就会再跑一步,而不是停下来等
|
|
13
|
+
* 用户输入。dsh 自己的 dsh-hooks-claude-code/lib/index.js:300 就是这么用的。
|
|
14
|
+
*
|
|
15
|
+
* 推什么内容由配置决定(见 config.ts 的 resumeText / resumeSummary)。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
19
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
20
|
+
import type { ContextFormed } from '@deepseek-ai/dsh-llm';
|
|
21
|
+
import type { RepeatGuardConfig } from './config.js';
|
|
22
|
+
import type { GuardState } from './types.js';
|
|
23
|
+
|
|
24
|
+
// dsh 0.1.7 起 `MessageSourceMap` 没有通用的 `plugin` kind:每个生产者在自己的模块里
|
|
25
|
+
// 声明自己的 kind(merge-extensible),`form` 另由 ContextFormed 提供。
|
|
26
|
+
// 用 `plugin:<名>` 而不是裸名:会话格式 v3→v4 的迁移器对历史消息生成的正是这个前缀
|
|
27
|
+
// (dsh-session-format-v3-to-v4 的 producerKind),裸名会让同一生产者出现两种 kind。
|
|
28
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
29
|
+
interface MessageSourceMap {
|
|
30
|
+
/** 本插件推给模型的续跑提示。 */
|
|
31
|
+
'plugin:repeat-guard': {
|
|
32
|
+
kind: 'plugin:repeat-guard';
|
|
33
|
+
} & ContextFormed;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 本轮即将关闭时,若上一步刚被掐断过,就推一条输入让本轮继续。
|
|
39
|
+
* @param state - 跨监听保留的拦截状态。
|
|
40
|
+
* @param agent - 本轮所属的 agent 句柄。
|
|
41
|
+
* @param config - 当前配置,正文与摘要都从这里取。
|
|
42
|
+
*/
|
|
43
|
+
export function reviveTurn(state: GuardState, agent: Agent, config: RepeatGuardConfig): void {
|
|
44
|
+
const sessionId = agent.session.id;
|
|
45
|
+
if (!state.pending.has(sessionId)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
state.pending.delete(sessionId);
|
|
49
|
+
console.log(`[repeat-guard] turn-stopping:会话 ${sessionId} 续跑一步`);
|
|
50
|
+
agent.steer(
|
|
51
|
+
createUserMessage({
|
|
52
|
+
content: [{ type: 'text', text: config.resumeText }],
|
|
53
|
+
// `form: 'notice'` 要求同时给出 `summary`(dsh-llm 的 ContextFormed),
|
|
54
|
+
// 客户端据此把它渲染成折叠的 context 行,而不是用户气泡。
|
|
55
|
+
source: {
|
|
56
|
+
kind: 'plugin:repeat-guard',
|
|
57
|
+
form: 'notice',
|
|
58
|
+
summary: config.resumeSummary,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
package/src/stream-guard.ts
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `llm/stream` 监听器:包一层上游流,思考段命中复读退化就补完流并提前结束。
|
|
3
|
-
*
|
|
4
|
-
* 只检测 reasoning-delta(思考段)。正文 text-delta 不参与判定,原样透传。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
8
|
-
import type { ConfigSource } from './config.js';
|
|
9
|
-
import { findDegenerateLine } from './detect.js';
|
|
10
|
-
import type { GuardState } from './types.js';
|
|
11
|
-
|
|
12
|
-
/** `llm/stream` 的监听器签名。 */
|
|
13
|
-
type StreamListener = (
|
|
14
|
-
options: GenerateOptions,
|
|
15
|
-
next: () => AsyncIterable<StreamChunk>,
|
|
16
|
-
) => AsyncIterable<StreamChunk>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 逐 chunk 检查上游流的思考段,命中复读退化时补完这段流。
|
|
20
|
-
*
|
|
21
|
-
* 适配器只开一个 reasoning 块(dsh-llm-deepseek: `reasoningBlock` 是单个变量),
|
|
22
|
-
* 所以累积文本用一个字符串即可,不需要按 index 分开存。
|
|
23
|
-
*
|
|
24
|
-
* 命中之后先往下探一格,判"思考段是不是就到这儿了":再往下若还是 reasoning-delta,
|
|
25
|
-
* 说明思考在继续复读,该拦;若换成正文、工具调用,或直接收尾,说明命中行本来就是
|
|
26
|
-
* 这段思考的最后一句,属正常收尾,不拦。
|
|
27
|
-
*
|
|
28
|
-
* @param downstream - 上游模型的流。
|
|
29
|
-
* @param sessionId - 当前会话 id;有值才登记待续跑标记。
|
|
30
|
-
* @param state - 跨监听保留的拦截状态。
|
|
31
|
-
* @param readConfig - 取当前配置;每个 chunk 现取,改设置立即生效。
|
|
32
|
-
* @param countHit - 记一次拦截;只在真的掐断时调用。
|
|
33
|
-
* @returns 包好的流。
|
|
34
|
-
*/
|
|
35
|
-
async function* guardStream(
|
|
36
|
-
downstream: AsyncIterable<StreamChunk>,
|
|
37
|
-
sessionId: GenerateOptions['sessionId'],
|
|
38
|
-
state: GuardState,
|
|
39
|
-
readConfig: ConfigSource,
|
|
40
|
-
countHit: () => void,
|
|
41
|
-
): AsyncGenerator<StreamChunk> {
|
|
42
|
-
const iterator = downstream[Symbol.asyncIterator]();
|
|
43
|
-
let accumulated = '';
|
|
44
|
-
try {
|
|
45
|
-
for (;;) {
|
|
46
|
-
const step = await iterator.next();
|
|
47
|
-
if (step.done) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const chunk = step.value;
|
|
51
|
-
if (chunk.type === 'reasoning-delta') {
|
|
52
|
-
accumulated += chunk.text;
|
|
53
|
-
const config = readConfig();
|
|
54
|
-
const hit = findDegenerateLine(
|
|
55
|
-
accumulated,
|
|
56
|
-
config.fragments,
|
|
57
|
-
config.threshold,
|
|
58
|
-
config.inlineRepeat,
|
|
59
|
-
);
|
|
60
|
-
if (hit !== null) {
|
|
61
|
-
const probe = await iterator.next();
|
|
62
|
-
if (!probe.done && probe.value.type === 'reasoning-delta') {
|
|
63
|
-
countHit();
|
|
64
|
-
if (sessionId !== undefined) {
|
|
65
|
-
state.pending.add(sessionId);
|
|
66
|
-
}
|
|
67
|
-
console.log(
|
|
68
|
-
`[repeat-guard] 检出思考段复读,已掐断本次生成 | 会话=${sessionId ?? '无'} | 命中行=${JSON.stringify(hit)}`,
|
|
69
|
-
);
|
|
70
|
-
// 触发点本身已经产生了,照常放行;要掐掉的是它之后的思考。
|
|
71
|
-
yield chunk;
|
|
72
|
-
yield { type: 'block-end', index: chunk.index, block: { type: 'reasoning', text: accumulated } };
|
|
73
|
-
yield { type: 'finish', reason: { kind: 'stop' } };
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
console.log(
|
|
77
|
-
`[repeat-guard] 命中行位于思考段末尾,未拦截 | 会话=${sessionId ?? '无'} | 命中行=${JSON.stringify(hit)}`,
|
|
78
|
-
);
|
|
79
|
-
yield chunk;
|
|
80
|
-
if (!probe.done) {
|
|
81
|
-
yield probe.value;
|
|
82
|
-
}
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
yield chunk;
|
|
87
|
-
}
|
|
88
|
-
} finally {
|
|
89
|
-
// 提前 return 时必须显式向下游传播:不补这一步上游 HTTP 流会继续跑到结束,
|
|
90
|
-
// 供应商照常计满 token,且连接悬挂。上游可能已经结束/关闭,其异常忽略。
|
|
91
|
-
try {
|
|
92
|
-
await iterator.return?.();
|
|
93
|
-
} catch {
|
|
94
|
-
/* 上游已经结束或关闭,无需处理 */
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 造一个 `llm/stream` 监听器。
|
|
101
|
-
* @param state - 跨监听保留的拦截状态。
|
|
102
|
-
* @param readConfig - 取当前配置。
|
|
103
|
-
* @param countHit - 记一次拦截。
|
|
104
|
-
* @returns 监听器;辅助调用直接透传,其余包一层复读检测。
|
|
105
|
-
*/
|
|
106
|
-
export function createStreamGuard(
|
|
107
|
-
state: GuardState,
|
|
108
|
-
readConfig: ConfigSource,
|
|
109
|
-
countHit: () => void,
|
|
110
|
-
): StreamListener {
|
|
111
|
-
return (options, next) => {
|
|
112
|
-
// 辅助调用(上下文压缩、会话标题)不参与检测。
|
|
113
|
-
if (options.purpose !== undefined) {
|
|
114
|
-
return next();
|
|
115
|
-
}
|
|
116
|
-
return guardStream(next(), options.sessionId, state, readConfig, countHit);
|
|
117
|
-
};
|
|
118
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `llm/stream` 监听器:包一层上游流,思考段命中复读退化就补完流并提前结束。
|
|
3
|
+
*
|
|
4
|
+
* 只检测 reasoning-delta(思考段)。正文 text-delta 不参与判定,原样透传。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import type { ConfigSource } from './config.js';
|
|
9
|
+
import { findDegenerateLine } from './detect.js';
|
|
10
|
+
import type { GuardState } from './types.js';
|
|
11
|
+
|
|
12
|
+
/** `llm/stream` 的监听器签名。 */
|
|
13
|
+
type StreamListener = (
|
|
14
|
+
options: GenerateOptions,
|
|
15
|
+
next: () => AsyncIterable<StreamChunk>,
|
|
16
|
+
) => AsyncIterable<StreamChunk>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 逐 chunk 检查上游流的思考段,命中复读退化时补完这段流。
|
|
20
|
+
*
|
|
21
|
+
* 适配器只开一个 reasoning 块(dsh-llm-deepseek: `reasoningBlock` 是单个变量),
|
|
22
|
+
* 所以累积文本用一个字符串即可,不需要按 index 分开存。
|
|
23
|
+
*
|
|
24
|
+
* 命中之后先往下探一格,判"思考段是不是就到这儿了":再往下若还是 reasoning-delta,
|
|
25
|
+
* 说明思考在继续复读,该拦;若换成正文、工具调用,或直接收尾,说明命中行本来就是
|
|
26
|
+
* 这段思考的最后一句,属正常收尾,不拦。
|
|
27
|
+
*
|
|
28
|
+
* @param downstream - 上游模型的流。
|
|
29
|
+
* @param sessionId - 当前会话 id;有值才登记待续跑标记。
|
|
30
|
+
* @param state - 跨监听保留的拦截状态。
|
|
31
|
+
* @param readConfig - 取当前配置;每个 chunk 现取,改设置立即生效。
|
|
32
|
+
* @param countHit - 记一次拦截;只在真的掐断时调用。
|
|
33
|
+
* @returns 包好的流。
|
|
34
|
+
*/
|
|
35
|
+
async function* guardStream(
|
|
36
|
+
downstream: AsyncIterable<StreamChunk>,
|
|
37
|
+
sessionId: GenerateOptions['sessionId'],
|
|
38
|
+
state: GuardState,
|
|
39
|
+
readConfig: ConfigSource,
|
|
40
|
+
countHit: () => void,
|
|
41
|
+
): AsyncGenerator<StreamChunk> {
|
|
42
|
+
const iterator = downstream[Symbol.asyncIterator]();
|
|
43
|
+
let accumulated = '';
|
|
44
|
+
try {
|
|
45
|
+
for (;;) {
|
|
46
|
+
const step = await iterator.next();
|
|
47
|
+
if (step.done) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const chunk = step.value;
|
|
51
|
+
if (chunk.type === 'reasoning-delta') {
|
|
52
|
+
accumulated += chunk.text;
|
|
53
|
+
const config = readConfig();
|
|
54
|
+
const hit = findDegenerateLine(
|
|
55
|
+
accumulated,
|
|
56
|
+
config.fragments,
|
|
57
|
+
config.threshold,
|
|
58
|
+
config.inlineRepeat,
|
|
59
|
+
);
|
|
60
|
+
if (hit !== null) {
|
|
61
|
+
const probe = await iterator.next();
|
|
62
|
+
if (!probe.done && probe.value.type === 'reasoning-delta') {
|
|
63
|
+
countHit();
|
|
64
|
+
if (sessionId !== undefined) {
|
|
65
|
+
state.pending.add(sessionId);
|
|
66
|
+
}
|
|
67
|
+
console.log(
|
|
68
|
+
`[repeat-guard] 检出思考段复读,已掐断本次生成 | 会话=${sessionId ?? '无'} | 命中行=${JSON.stringify(hit)}`,
|
|
69
|
+
);
|
|
70
|
+
// 触发点本身已经产生了,照常放行;要掐掉的是它之后的思考。
|
|
71
|
+
yield chunk;
|
|
72
|
+
yield { type: 'block-end', index: chunk.index, block: { type: 'reasoning', text: accumulated } };
|
|
73
|
+
yield { type: 'finish', reason: { kind: 'stop' } };
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.log(
|
|
77
|
+
`[repeat-guard] 命中行位于思考段末尾,未拦截 | 会话=${sessionId ?? '无'} | 命中行=${JSON.stringify(hit)}`,
|
|
78
|
+
);
|
|
79
|
+
yield chunk;
|
|
80
|
+
if (!probe.done) {
|
|
81
|
+
yield probe.value;
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
yield chunk;
|
|
87
|
+
}
|
|
88
|
+
} finally {
|
|
89
|
+
// 提前 return 时必须显式向下游传播:不补这一步上游 HTTP 流会继续跑到结束,
|
|
90
|
+
// 供应商照常计满 token,且连接悬挂。上游可能已经结束/关闭,其异常忽略。
|
|
91
|
+
try {
|
|
92
|
+
await iterator.return?.();
|
|
93
|
+
} catch {
|
|
94
|
+
/* 上游已经结束或关闭,无需处理 */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 造一个 `llm/stream` 监听器。
|
|
101
|
+
* @param state - 跨监听保留的拦截状态。
|
|
102
|
+
* @param readConfig - 取当前配置。
|
|
103
|
+
* @param countHit - 记一次拦截。
|
|
104
|
+
* @returns 监听器;辅助调用直接透传,其余包一层复读检测。
|
|
105
|
+
*/
|
|
106
|
+
export function createStreamGuard(
|
|
107
|
+
state: GuardState,
|
|
108
|
+
readConfig: ConfigSource,
|
|
109
|
+
countHit: () => void,
|
|
110
|
+
): StreamListener {
|
|
111
|
+
return (options, next) => {
|
|
112
|
+
// 辅助调用(上下文压缩、会话标题)不参与检测。
|
|
113
|
+
if (options.purpose !== undefined) {
|
|
114
|
+
return next();
|
|
115
|
+
}
|
|
116
|
+
return guardStream(next(), options.sessionId, state, readConfig, countHit);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `agent/turn-stopping` 监听器:把上一步掐断欠下的续跑补上。
|
|
3
|
-
*
|
|
4
|
-
* 监听器的返回值不被使用(该事件是 serial 的副作用钩子),实现方式是调
|
|
5
|
-
* `agent.steer(...)`,见 resume.ts 的说明。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
9
|
-
import type { ConfigSource } from './config.js';
|
|
10
|
-
import { reviveTurn } from './resume.js';
|
|
11
|
-
import type { GuardState } from './types.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 造一个 `agent/turn-stopping` 监听器。
|
|
15
|
-
* @param state - 跨监听保留的拦截状态。
|
|
16
|
-
* @param readConfig - 取当前配置。
|
|
17
|
-
* @returns 监听器;只在有待续跑标记时推一条输入。
|
|
18
|
-
*/
|
|
19
|
-
export function createTurnStoppingGuard(state: GuardState, readConfig: ConfigSource) {
|
|
20
|
-
return (payload: { agent: Agent }): void => {
|
|
21
|
-
reviveTurn(state, payload.agent, readConfig());
|
|
22
|
-
};
|
|
23
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `agent/turn-stopping` 监听器:把上一步掐断欠下的续跑补上。
|
|
3
|
+
*
|
|
4
|
+
* 监听器的返回值不被使用(该事件是 serial 的副作用钩子),实现方式是调
|
|
5
|
+
* `agent.steer(...)`,见 resume.ts 的说明。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
9
|
+
import type { ConfigSource } from './config.js';
|
|
10
|
+
import { reviveTurn } from './resume.js';
|
|
11
|
+
import type { GuardState } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 造一个 `agent/turn-stopping` 监听器。
|
|
15
|
+
* @param state - 跨监听保留的拦截状态。
|
|
16
|
+
* @param readConfig - 取当前配置。
|
|
17
|
+
* @returns 监听器;只在有待续跑标记时推一条输入。
|
|
18
|
+
*/
|
|
19
|
+
export function createTurnStoppingGuard(state: GuardState, readConfig: ConfigSource) {
|
|
20
|
+
return (payload: { agent: Agent }): void => {
|
|
21
|
+
reviveTurn(state, payload.agent, readConfig());
|
|
22
|
+
};
|
|
23
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 本插件自有的类型。
|
|
3
|
-
*
|
|
4
|
-
* 与 dsh 交互的类型不在这里重抄:`Context`、`StreamChunk`、`GenerateOptions`、
|
|
5
|
-
* `Agent`、`UserMessage` 一律从 `@deepseek-ai/*` 引。本地抄一份会在 dsh 升级时
|
|
6
|
-
* 静默过期,而官方声明会让它在构建期就报出来。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** 跨两次监听保留的拦截状态。 */
|
|
10
|
-
export interface GuardState {
|
|
11
|
-
/** 发生过截断、等待下一步续跑的会话 id。 */
|
|
12
|
-
readonly pending: Set<string>;
|
|
13
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 本插件自有的类型。
|
|
3
|
+
*
|
|
4
|
+
* 与 dsh 交互的类型不在这里重抄:`Context`、`StreamChunk`、`GenerateOptions`、
|
|
5
|
+
* `Agent`、`UserMessage` 一律从 `@deepseek-ai/*` 引。本地抄一份会在 dsh 升级时
|
|
6
|
+
* 静默过期,而官方声明会让它在构建期就报出来。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** 跨两次监听保留的拦截状态。 */
|
|
10
|
+
export interface GuardState {
|
|
11
|
+
/** 发生过截断、等待下一步续跑的会话 id。 */
|
|
12
|
+
readonly pending: Set<string>;
|
|
13
|
+
}
|