deeper-cli 1.0.6 → 1.2.1
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 -0
- package/dist/cli/index.js +581 -156
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/chat-repl.ts +1251 -961
- package/src/model/DeepSeekClient.ts +35 -37
- package/src/model/RetryManager.ts +54 -7
- package/src/model/StreamHandler.ts +111 -17
- package/src/model/types.ts +25 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChatMessage, DeepSeekConfig, StreamChunk } from './types.js';
|
|
2
|
-
import type { ToolDefinition
|
|
2
|
+
import type { ToolDefinition } from '../tools/tool-types.js';
|
|
3
3
|
import { RetryManager } from './RetryManager.js';
|
|
4
4
|
import { StreamHandler } from './StreamHandler.js';
|
|
5
5
|
import { logger } from '../core/logger.js';
|
|
@@ -41,13 +41,22 @@ interface ChatCompletionResponse {
|
|
|
41
41
|
const DEFAULT_TIMEOUT_MS = 120000;
|
|
42
42
|
const MAX_RETRIES = 3;
|
|
43
43
|
|
|
44
|
+
const isRetryable = (error: Error, _attempt: number): boolean => {
|
|
45
|
+
const msg = error.message.toLowerCase();
|
|
46
|
+
if (msg.includes('429') || msg.includes('rate limit')) return true;
|
|
47
|
+
if (msg.includes('500') || msg.includes('502') || msg.includes('503') || msg.includes('504')) return true;
|
|
48
|
+
if (msg.includes('timeout') || msg.includes('abort')) return _attempt < 2;
|
|
49
|
+
if (msg.includes('econnreset') || msg.includes('econnrefused')) return true;
|
|
50
|
+
return false;
|
|
51
|
+
};
|
|
52
|
+
|
|
44
53
|
export class DeepSeekClient {
|
|
45
54
|
private config: DeepSeekConfig;
|
|
46
55
|
private retryManager: RetryManager;
|
|
47
56
|
|
|
48
57
|
constructor(config: DeepSeekConfig) {
|
|
49
58
|
this.config = config;
|
|
50
|
-
this.retryManager = new RetryManager(
|
|
59
|
+
this.retryManager = new RetryManager(MAX_RETRIES);
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
async chat(
|
|
@@ -59,12 +68,12 @@ export class DeepSeekClient {
|
|
|
59
68
|
const body = this.buildRequestBody(messages, tools, cfg, false);
|
|
60
69
|
|
|
61
70
|
const response = await this.retryManager.execute(async () => {
|
|
62
|
-
|
|
71
|
+
return this.retryManager.withTimeout(
|
|
63
72
|
() => this.makeRequest(cfg, body),
|
|
64
|
-
|
|
73
|
+
DEFAULT_TIMEOUT_MS,
|
|
74
|
+
cfg.signal,
|
|
65
75
|
);
|
|
66
|
-
|
|
67
|
-
}, this.shouldRetry);
|
|
76
|
+
}, isRetryable);
|
|
68
77
|
|
|
69
78
|
const data = (await response.json()) as ChatCompletionResponse;
|
|
70
79
|
|
|
@@ -89,7 +98,7 @@ export class DeepSeekClient {
|
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
if (message.reasoning_content) {
|
|
92
|
-
result.
|
|
101
|
+
result.reasoning_content = message.reasoning_content;
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
return result;
|
|
@@ -104,17 +113,12 @@ export class DeepSeekClient {
|
|
|
104
113
|
const body = this.buildRequestBody(messages, tools, cfg, true);
|
|
105
114
|
|
|
106
115
|
const response = await this.retryManager.execute(async () => {
|
|
107
|
-
|
|
116
|
+
return this.retryManager.withTimeout(
|
|
108
117
|
() => this.makeRequest(cfg, body),
|
|
109
118
|
DEFAULT_TIMEOUT_MS,
|
|
119
|
+
cfg.signal,
|
|
110
120
|
);
|
|
111
|
-
|
|
112
|
-
}, this.shouldRetry);
|
|
113
|
-
|
|
114
|
-
if (!response.ok) {
|
|
115
|
-
const errorBody = await response.text().catch(() => '');
|
|
116
|
-
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorBody}`);
|
|
117
|
-
}
|
|
121
|
+
}, isRetryable);
|
|
118
122
|
|
|
119
123
|
if (!response.body) {
|
|
120
124
|
throw new Error('Response body is empty');
|
|
@@ -201,7 +205,7 @@ export class DeepSeekClient {
|
|
|
201
205
|
if (m.name) {
|
|
202
206
|
msg.name = m.name;
|
|
203
207
|
}
|
|
204
|
-
const rc =
|
|
208
|
+
const rc = m.reasoning_content || m.thinking;
|
|
205
209
|
if (rc) msg.reasoning_content = rc;
|
|
206
210
|
return msg;
|
|
207
211
|
}),
|
|
@@ -210,6 +214,13 @@ export class DeepSeekClient {
|
|
|
210
214
|
stream,
|
|
211
215
|
};
|
|
212
216
|
|
|
217
|
+
if (config.think?.enabled) {
|
|
218
|
+
(body as Record<string, unknown>).thinking = {
|
|
219
|
+
type: 'enabled',
|
|
220
|
+
budget_tokens: config.think.budgetTokens,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
213
224
|
if (tools && tools.length > 0) {
|
|
214
225
|
body.tools = tools.map((tool) => ({
|
|
215
226
|
type: 'function',
|
|
@@ -230,7 +241,7 @@ export class DeepSeekClient {
|
|
|
230
241
|
|
|
231
242
|
logger.debug('DeepSeek API request', { url, model: config.model });
|
|
232
243
|
|
|
233
|
-
const
|
|
244
|
+
const fetchOpts: RequestInit = {
|
|
234
245
|
method: 'POST',
|
|
235
246
|
headers: {
|
|
236
247
|
'Content-Type': 'application/json',
|
|
@@ -238,7 +249,13 @@ export class DeepSeekClient {
|
|
|
238
249
|
'Accept': 'application/json',
|
|
239
250
|
},
|
|
240
251
|
body,
|
|
241
|
-
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if (config.signal) {
|
|
255
|
+
fetchOpts.signal = config.signal;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const response = await fetch(url, fetchOpts);
|
|
242
259
|
|
|
243
260
|
if (!response.ok) {
|
|
244
261
|
const errorBody = await response.text().catch(() => '');
|
|
@@ -253,25 +270,6 @@ export class DeepSeekClient {
|
|
|
253
270
|
return response;
|
|
254
271
|
}
|
|
255
272
|
|
|
256
|
-
private shouldRetry(error: Error, attempt: number): boolean {
|
|
257
|
-
const message = error.message.toLowerCase();
|
|
258
|
-
|
|
259
|
-
if (message.includes('429') || message.includes('rate limit')) {
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
if (message.includes('5') && (message.includes('500') || message.includes('502') || message.includes('503') || message.includes('504'))) {
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
if (message.includes('timeout') || message.includes('abort')) {
|
|
266
|
-
return attempt < 2;
|
|
267
|
-
}
|
|
268
|
-
if (message.includes('econnreset') || message.includes('econnrefused')) {
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
273
|
private parseArguments(argsStr: string): Record<string, unknown> {
|
|
276
274
|
try {
|
|
277
275
|
return JSON.parse(argsStr) as Record<string, unknown>;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export interface RetryOptions {
|
|
2
|
+
onRetry?: (error: Error, attempt: number, delayMs: number) => void;
|
|
3
|
+
}
|
|
4
|
+
|
|
1
5
|
export class RetryManager {
|
|
2
6
|
private maxRetries: number;
|
|
3
7
|
private baseDelayMs: number;
|
|
@@ -9,7 +13,11 @@ export class RetryManager {
|
|
|
9
13
|
this.maxDelayMs = maxDelayMs;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
async execute<T>(
|
|
16
|
+
async execute<T>(
|
|
17
|
+
fn: () => Promise<T>,
|
|
18
|
+
shouldRetry?: (error: Error, attempt: number) => boolean,
|
|
19
|
+
options?: RetryOptions,
|
|
20
|
+
): Promise<T> {
|
|
13
21
|
let lastError: Error | undefined;
|
|
14
22
|
|
|
15
23
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
@@ -26,10 +34,15 @@ export class RetryManager {
|
|
|
26
34
|
break;
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
if (!shouldRetry && this.isDefaultNonRetryable(lastError)) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const delay = this.calculateDelay(attempt);
|
|
42
|
+
|
|
43
|
+
if (options?.onRetry) {
|
|
44
|
+
options.onRetry(lastError, attempt, delay);
|
|
45
|
+
}
|
|
33
46
|
|
|
34
47
|
await this.sleep(delay);
|
|
35
48
|
}
|
|
@@ -39,19 +52,24 @@ export class RetryManager {
|
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
withTimeout<T>(fn: () => Promise<T>, timeoutMs: number, signal?: AbortSignal): Promise<T> {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
|
|
42
57
|
return new Promise<T>((resolve, reject) => {
|
|
43
58
|
const timer = setTimeout(() => {
|
|
59
|
+
controller.abort();
|
|
44
60
|
reject(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
45
61
|
}, timeoutMs);
|
|
46
62
|
|
|
47
63
|
const onAbort = (): void => {
|
|
48
64
|
clearTimeout(timer);
|
|
65
|
+
controller.abort();
|
|
49
66
|
reject(new Error('Request was aborted'));
|
|
50
67
|
};
|
|
51
68
|
|
|
52
69
|
if (signal) {
|
|
53
70
|
if (signal.aborted) {
|
|
54
71
|
clearTimeout(timer);
|
|
72
|
+
controller.abort();
|
|
55
73
|
reject(new Error('Request was aborted'));
|
|
56
74
|
return;
|
|
57
75
|
}
|
|
@@ -76,7 +94,36 @@ export class RetryManager {
|
|
|
76
94
|
});
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
private
|
|
80
|
-
|
|
97
|
+
private calculateDelay(attempt: number): number {
|
|
98
|
+
const exponentialDelay = this.baseDelayMs * Math.pow(2, attempt);
|
|
99
|
+
const jitter = Math.random() * exponentialDelay;
|
|
100
|
+
return Math.min(exponentialDelay + jitter, this.maxDelayMs);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private isDefaultNonRetryable(error: Error): boolean {
|
|
104
|
+
const msg = error.message.toLowerCase();
|
|
105
|
+
if (msg.includes('400') && !msg.includes('429')) return true;
|
|
106
|
+
if (msg.includes('401') || msg.includes('unauthorized')) return true;
|
|
107
|
+
if (msg.includes('403') || msg.includes('forbidden')) return true;
|
|
108
|
+
if (msg.includes('404') || msg.includes('not found')) return true;
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const timer = setTimeout(resolve, ms);
|
|
115
|
+
if (signal) {
|
|
116
|
+
const onAbort = () => {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
reject(new Error('Retry sleep was aborted'));
|
|
119
|
+
};
|
|
120
|
+
if (signal.aborted) {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
reject(new Error('Retry sleep was aborted'));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
81
128
|
}
|
|
82
129
|
}
|
|
@@ -7,17 +7,27 @@ interface ParsedSSEEvent {
|
|
|
7
7
|
id?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
interface PendingToolCall {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
argsStr: string;
|
|
14
|
+
index: number;
|
|
15
|
+
started: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
export class StreamHandler {
|
|
11
19
|
private textBuffer: string;
|
|
12
20
|
private thinkingBuffer: string;
|
|
13
|
-
private toolCallBuffer: Map<number,
|
|
21
|
+
private toolCallBuffer: Map<number, PendingToolCall>;
|
|
14
22
|
private finished: boolean;
|
|
23
|
+
private lastYieldedIndex: number;
|
|
15
24
|
|
|
16
25
|
constructor() {
|
|
17
26
|
this.textBuffer = '';
|
|
18
27
|
this.thinkingBuffer = '';
|
|
19
28
|
this.toolCallBuffer = new Map();
|
|
20
29
|
this.finished = false;
|
|
30
|
+
this.lastYieldedIndex = -1;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
reset(): void {
|
|
@@ -25,11 +35,20 @@ export class StreamHandler {
|
|
|
25
35
|
this.thinkingBuffer = '';
|
|
26
36
|
this.toolCallBuffer.clear();
|
|
27
37
|
this.finished = false;
|
|
38
|
+
this.lastYieldedIndex = -1;
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
handleEvent(event: string, data: string): StreamChunk | StreamChunk[] | null {
|
|
42
|
+
if (event === 'error') {
|
|
43
|
+
return { type: 'error', error: data || 'SSE error event' };
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
if (event === '[DONE]' || data === '[DONE]') {
|
|
47
|
+
const results = this.finalizePendingToolCalls();
|
|
32
48
|
this.finished = true;
|
|
49
|
+
if (results.length > 0) {
|
|
50
|
+
return [...results, { type: 'done' } as StreamChunk];
|
|
51
|
+
}
|
|
33
52
|
return { type: 'done' };
|
|
34
53
|
}
|
|
35
54
|
|
|
@@ -49,12 +68,24 @@ export class StreamHandler {
|
|
|
49
68
|
const delta = choice.delta as Record<string, unknown> | undefined;
|
|
50
69
|
|
|
51
70
|
if (!delta) {
|
|
71
|
+
const finishReason = choice.finish_reason as string | undefined;
|
|
72
|
+
if (finishReason === 'stop' || finishReason === 'length' || finishReason === 'tool_calls') {
|
|
73
|
+
const results = this.finalizePendingToolCalls();
|
|
74
|
+
this.finished = true;
|
|
75
|
+
if (results.length > 0) {
|
|
76
|
+
return [...results, { type: 'done' } as StreamChunk];
|
|
77
|
+
}
|
|
78
|
+
return { type: 'done' };
|
|
79
|
+
}
|
|
52
80
|
return null;
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
if (delta.reasoning_content) {
|
|
56
84
|
const thinkingChunk = delta.reasoning_content as string;
|
|
57
85
|
this.thinkingBuffer += thinkingChunk;
|
|
86
|
+
if (this.thinkingBuffer.length > 100_000) {
|
|
87
|
+
this.thinkingBuffer = this.thinkingBuffer.slice(-80_000);
|
|
88
|
+
}
|
|
58
89
|
return {
|
|
59
90
|
type: 'thinking',
|
|
60
91
|
content: thinkingChunk,
|
|
@@ -68,6 +99,9 @@ export class StreamHandler {
|
|
|
68
99
|
if (delta.content) {
|
|
69
100
|
const textChunk = delta.content as string;
|
|
70
101
|
this.textBuffer += textChunk;
|
|
102
|
+
if (this.textBuffer.length > 500_000) {
|
|
103
|
+
this.textBuffer = this.textBuffer.slice(-400_000);
|
|
104
|
+
}
|
|
71
105
|
return {
|
|
72
106
|
type: 'text',
|
|
73
107
|
content: textChunk,
|
|
@@ -76,7 +110,11 @@ export class StreamHandler {
|
|
|
76
110
|
|
|
77
111
|
const finishReason = choice.finish_reason as string | undefined;
|
|
78
112
|
if (finishReason === 'stop' || finishReason === 'length' || finishReason === 'tool_calls') {
|
|
113
|
+
const results = this.finalizePendingToolCalls();
|
|
79
114
|
this.finished = true;
|
|
115
|
+
if (results.length > 0) {
|
|
116
|
+
return [...results, { type: 'done' } as StreamChunk];
|
|
117
|
+
}
|
|
80
118
|
return { type: 'done' };
|
|
81
119
|
}
|
|
82
120
|
} catch {
|
|
@@ -99,10 +137,16 @@ export class StreamHandler {
|
|
|
99
137
|
|
|
100
138
|
getToolCalls(): ToolCall[] {
|
|
101
139
|
const result: ToolCall[] = [];
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
140
|
+
const indices = [...this.toolCallBuffer.keys()].sort((a, b) => a - b);
|
|
141
|
+
for (const idx of indices) {
|
|
142
|
+
const pending = this.toolCallBuffer.get(idx);
|
|
143
|
+
if (pending) {
|
|
144
|
+
result.push({
|
|
145
|
+
id: pending.id,
|
|
146
|
+
name: pending.name,
|
|
147
|
+
arguments: this.parseArgsStr(pending.argsStr),
|
|
148
|
+
index: pending.index,
|
|
149
|
+
});
|
|
106
150
|
}
|
|
107
151
|
}
|
|
108
152
|
return result;
|
|
@@ -114,6 +158,7 @@ export class StreamHandler {
|
|
|
114
158
|
|
|
115
159
|
private handleToolCallsDelta(toolCalls: Array<Record<string, unknown>>): StreamChunk[] {
|
|
116
160
|
const results: StreamChunk[] = [];
|
|
161
|
+
|
|
117
162
|
for (const tc of toolCalls) {
|
|
118
163
|
const index = tc.index as number;
|
|
119
164
|
const id = tc.id as string | undefined;
|
|
@@ -123,8 +168,9 @@ export class StreamHandler {
|
|
|
123
168
|
this.toolCallBuffer.set(index, {
|
|
124
169
|
id: id ?? '',
|
|
125
170
|
name: fn?.name as string ?? '',
|
|
126
|
-
|
|
171
|
+
argsStr: '',
|
|
127
172
|
index,
|
|
173
|
+
started: false,
|
|
128
174
|
});
|
|
129
175
|
}
|
|
130
176
|
|
|
@@ -137,24 +183,72 @@ export class StreamHandler {
|
|
|
137
183
|
existing.name = fn.name as string;
|
|
138
184
|
}
|
|
139
185
|
if (fn?.arguments) {
|
|
140
|
-
|
|
141
|
-
const argsStr = fn.arguments as string;
|
|
142
|
-
const parsed = JSON.parse(argsStr) as Record<string, unknown>;
|
|
143
|
-
existing.arguments = { ...existing.arguments, ...parsed };
|
|
144
|
-
} catch {
|
|
145
|
-
existing.arguments = existing.arguments || {};
|
|
146
|
-
}
|
|
186
|
+
existing.argsStr += fn.arguments as string;
|
|
147
187
|
}
|
|
148
188
|
|
|
149
|
-
|
|
150
|
-
|
|
189
|
+
if (!existing.started) {
|
|
190
|
+
existing.started = true;
|
|
191
|
+
if (this.lastYieldedIndex >= 0) {
|
|
192
|
+
const prev = this.toolCallBuffer.get(this.lastYieldedIndex);
|
|
193
|
+
if (prev && prev !== existing) {
|
|
194
|
+
results.push({
|
|
195
|
+
type: 'tool_call_end',
|
|
196
|
+
tool_call: {
|
|
197
|
+
id: prev.id,
|
|
198
|
+
name: prev.name,
|
|
199
|
+
arguments: this.parseArgsStr(prev.argsStr),
|
|
200
|
+
index: prev.index,
|
|
201
|
+
},
|
|
202
|
+
} as StreamChunk);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.lastYieldedIndex = index;
|
|
151
206
|
results.push({
|
|
152
|
-
type: '
|
|
153
|
-
tool_call: {
|
|
207
|
+
type: 'tool_call_start',
|
|
208
|
+
tool_call: { id: existing.id, name: existing.name, index: existing.index },
|
|
154
209
|
} as StreamChunk);
|
|
155
210
|
}
|
|
156
211
|
}
|
|
157
212
|
|
|
158
213
|
return results;
|
|
159
214
|
}
|
|
215
|
+
|
|
216
|
+
private finalizePendingToolCalls(): StreamChunk[] {
|
|
217
|
+
const results: StreamChunk[] = [];
|
|
218
|
+
if (this.lastYieldedIndex >= 0) {
|
|
219
|
+
const last = this.toolCallBuffer.get(this.lastYieldedIndex);
|
|
220
|
+
if (last) {
|
|
221
|
+
results.push({
|
|
222
|
+
type: 'tool_call_end',
|
|
223
|
+
tool_call: {
|
|
224
|
+
id: last.id,
|
|
225
|
+
name: last.name,
|
|
226
|
+
arguments: this.parseArgsStr(last.argsStr),
|
|
227
|
+
index: last.index,
|
|
228
|
+
},
|
|
229
|
+
} as StreamChunk);
|
|
230
|
+
}
|
|
231
|
+
this.lastYieldedIndex = -1;
|
|
232
|
+
}
|
|
233
|
+
return results;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private parseArgsStr(argsStr: string): Record<string, unknown> {
|
|
237
|
+
if (!argsStr) return {};
|
|
238
|
+
try {
|
|
239
|
+
return JSON.parse(argsStr) as Record<string, unknown>;
|
|
240
|
+
} catch {
|
|
241
|
+
const trimmed = argsStr.trim();
|
|
242
|
+
if (trimmed.startsWith('{') && !trimmed.endsWith('}')) {
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(trimmed + '}') as Record<string, unknown>;
|
|
245
|
+
} catch {
|
|
246
|
+
try {
|
|
247
|
+
return JSON.parse(trimmed + '}}') as Record<string, unknown>;
|
|
248
|
+
} catch {}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return {};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
160
254
|
}
|
package/src/model/types.ts
CHANGED
|
@@ -5,17 +5,17 @@ export interface ChatMessage {
|
|
|
5
5
|
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
6
6
|
content: string | null;
|
|
7
7
|
tool_calls?: ToolCall[];
|
|
8
|
-
toolCalls?: ToolCallRecord[];
|
|
9
8
|
tool_call_id?: string;
|
|
10
9
|
name?: string;
|
|
10
|
+
reasoning_content?: string;
|
|
11
11
|
thinking?: string;
|
|
12
12
|
timestamp?: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface StreamChunk {
|
|
16
|
-
type: 'text' | 'thinking' | 'tool_call' | 'done' | 'error';
|
|
16
|
+
type: 'text' | 'thinking' | 'tool_call_start' | 'tool_call_end' | 'tool_call' | 'done' | 'error';
|
|
17
17
|
content?: string;
|
|
18
|
-
tool_call?: ToolCall;
|
|
18
|
+
tool_call?: ToolCall | { id: string; name: string; index?: number };
|
|
19
19
|
error?: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -29,6 +29,7 @@ export interface DeepSeekConfig {
|
|
|
29
29
|
enabled: boolean;
|
|
30
30
|
budgetTokens: number;
|
|
31
31
|
};
|
|
32
|
+
signal?: AbortSignal;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export interface ToolCallRecord {
|
|
@@ -56,13 +57,31 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
56
57
|
{ command: '/help', description: '显示帮助信息' },
|
|
57
58
|
{ command: '/clear', description: '清空对话' },
|
|
58
59
|
{ command: '/quit', description: '退出 DeeperCode' },
|
|
60
|
+
{ command: '/save [name]', description: '保存当前会话' },
|
|
61
|
+
{ command: '/load [name]', description: '加载历史会话' },
|
|
62
|
+
{ command: '/resume [name]', description: '恢复历史会话' },
|
|
63
|
+
{ command: '/sessions', description: '会话列表' },
|
|
59
64
|
{ command: '/model', description: '查看/切换模型' },
|
|
60
65
|
{ command: '/config', description: '查看/修改配置' },
|
|
61
|
-
{ command: '/tools', description: '列出可用工具' },
|
|
66
|
+
{ command: '/tools [cat]', description: '列出可用工具' },
|
|
62
67
|
{ command: '/skills', description: '列出已加载技能' },
|
|
63
68
|
{ command: '/mcp', description: '管理 MCP 连接' },
|
|
64
|
-
{ command: '/
|
|
65
|
-
{ command: '/
|
|
69
|
+
{ command: '/memory', description: '记忆系统' },
|
|
70
|
+
{ command: '/tasks', description: '任务列表' },
|
|
71
|
+
{ command: '/rules', description: '规则管理' },
|
|
72
|
+
{ command: '/stats', description: '统计信息' },
|
|
73
|
+
{ command: '/status', description: '当前状态' },
|
|
74
|
+
{ command: '/cwd', description: '当前目录' },
|
|
75
|
+
{ command: '/export', description: '导出对话' },
|
|
76
|
+
{ command: '/init', description: '初始化项目' },
|
|
77
|
+
{ command: '/plan <任务>', description: '先出方案再实施' },
|
|
78
|
+
{ command: '/spec <任务>', description: '先出规格再实施' },
|
|
79
|
+
{ command: '/review <路径>', description: '代码审查' },
|
|
80
|
+
{ command: '/fix [目标]', description: '自动修复构建/测试错误' },
|
|
81
|
+
{ command: '/commit', description: '智能分析变更并提交' },
|
|
82
|
+
{ command: '/analyze [路径]', description: '项目架构分析' },
|
|
83
|
+
{ command: '/diff <文件>', description: '查看文件变更' },
|
|
84
|
+
{ command: '/undo', description: '撤销最近文件修改' },
|
|
66
85
|
];
|
|
67
86
|
|
|
68
87
|
export type DeepSeekMessage = ChatMessage;
|