deeper-cli 1.0.5 → 1.2.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/dist/cli/index.js +1266 -334
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +8 -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 +1264 -965
- package/src/model/DeepSeekClient.ts +39 -39
- package/src/model/RetryManager.ts +54 -7
- package/src/model/StreamHandler.ts +117 -21
- package/src/model/types.ts +25 -6
- package/src/tools/tool-types.ts +1 -0
|
@@ -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');
|
|
@@ -145,7 +149,8 @@ export class DeepSeekClient {
|
|
|
145
149
|
const data = trimmed.slice(6);
|
|
146
150
|
const result = handler.handleEvent('message', data);
|
|
147
151
|
if (result) {
|
|
148
|
-
yield
|
|
152
|
+
if (Array.isArray(result)) { for (const r of result) yield r; }
|
|
153
|
+
else yield result;
|
|
149
154
|
}
|
|
150
155
|
} else if (trimmed === 'data: [DONE]') {
|
|
151
156
|
yield { type: 'done' };
|
|
@@ -160,7 +165,8 @@ export class DeepSeekClient {
|
|
|
160
165
|
const data = trimmed.slice(6);
|
|
161
166
|
const result = handler.handleEvent('message', data);
|
|
162
167
|
if (result) {
|
|
163
|
-
yield
|
|
168
|
+
if (Array.isArray(result)) { for (const r of result) yield r; }
|
|
169
|
+
else yield result;
|
|
164
170
|
}
|
|
165
171
|
}
|
|
166
172
|
}
|
|
@@ -199,7 +205,7 @@ export class DeepSeekClient {
|
|
|
199
205
|
if (m.name) {
|
|
200
206
|
msg.name = m.name;
|
|
201
207
|
}
|
|
202
|
-
const rc =
|
|
208
|
+
const rc = m.reasoning_content || m.thinking;
|
|
203
209
|
if (rc) msg.reasoning_content = rc;
|
|
204
210
|
return msg;
|
|
205
211
|
}),
|
|
@@ -208,6 +214,13 @@ export class DeepSeekClient {
|
|
|
208
214
|
stream,
|
|
209
215
|
};
|
|
210
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
|
+
|
|
211
224
|
if (tools && tools.length > 0) {
|
|
212
225
|
body.tools = tools.map((tool) => ({
|
|
213
226
|
type: 'function',
|
|
@@ -228,7 +241,7 @@ export class DeepSeekClient {
|
|
|
228
241
|
|
|
229
242
|
logger.debug('DeepSeek API request', { url, model: config.model });
|
|
230
243
|
|
|
231
|
-
const
|
|
244
|
+
const fetchOpts: RequestInit = {
|
|
232
245
|
method: 'POST',
|
|
233
246
|
headers: {
|
|
234
247
|
'Content-Type': 'application/json',
|
|
@@ -236,7 +249,13 @@ export class DeepSeekClient {
|
|
|
236
249
|
'Accept': 'application/json',
|
|
237
250
|
},
|
|
238
251
|
body,
|
|
239
|
-
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if (config.signal) {
|
|
255
|
+
fetchOpts.signal = config.signal;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const response = await fetch(url, fetchOpts);
|
|
240
259
|
|
|
241
260
|
if (!response.ok) {
|
|
242
261
|
const errorBody = await response.text().catch(() => '');
|
|
@@ -251,25 +270,6 @@ export class DeepSeekClient {
|
|
|
251
270
|
return response;
|
|
252
271
|
}
|
|
253
272
|
|
|
254
|
-
private shouldRetry(error: Error, attempt: number): boolean {
|
|
255
|
-
const message = error.message.toLowerCase();
|
|
256
|
-
|
|
257
|
-
if (message.includes('429') || message.includes('rate limit')) {
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
if (message.includes('5') && (message.includes('500') || message.includes('502') || message.includes('503') || message.includes('504'))) {
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
|
-
if (message.includes('timeout') || message.includes('abort')) {
|
|
264
|
-
return attempt < 2;
|
|
265
|
-
}
|
|
266
|
-
if (message.includes('econnreset') || message.includes('econnrefused')) {
|
|
267
|
-
return true;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
273
|
private parseArguments(argsStr: string): Record<string, unknown> {
|
|
274
274
|
try {
|
|
275
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
|
-
handleEvent(event: string, data: string): StreamChunk | null {
|
|
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;
|
|
@@ -112,7 +156,9 @@ export class StreamHandler {
|
|
|
112
156
|
return this.finished;
|
|
113
157
|
}
|
|
114
158
|
|
|
115
|
-
private handleToolCallsDelta(toolCalls: Array<Record<string, unknown>>): StreamChunk
|
|
159
|
+
private handleToolCallsDelta(toolCalls: Array<Record<string, unknown>>): StreamChunk[] {
|
|
160
|
+
const results: StreamChunk[] = [];
|
|
161
|
+
|
|
116
162
|
for (const tc of toolCalls) {
|
|
117
163
|
const index = tc.index as number;
|
|
118
164
|
const id = tc.id as string | undefined;
|
|
@@ -122,7 +168,9 @@ export class StreamHandler {
|
|
|
122
168
|
this.toolCallBuffer.set(index, {
|
|
123
169
|
id: id ?? '',
|
|
124
170
|
name: fn?.name as string ?? '',
|
|
125
|
-
|
|
171
|
+
argsStr: '',
|
|
172
|
+
index,
|
|
173
|
+
started: false,
|
|
126
174
|
});
|
|
127
175
|
}
|
|
128
176
|
|
|
@@ -135,24 +183,72 @@ export class StreamHandler {
|
|
|
135
183
|
existing.name = fn.name as string;
|
|
136
184
|
}
|
|
137
185
|
if (fn?.arguments) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
186
|
+
existing.argsStr += fn.arguments as string;
|
|
187
|
+
}
|
|
188
|
+
|
|
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
|
+
}
|
|
144
204
|
}
|
|
205
|
+
this.lastYieldedIndex = index;
|
|
206
|
+
results.push({
|
|
207
|
+
type: 'tool_call_start',
|
|
208
|
+
tool_call: { id: existing.id, name: existing.name, index: existing.index },
|
|
209
|
+
} as StreamChunk);
|
|
145
210
|
}
|
|
146
211
|
}
|
|
147
212
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
213
|
+
return results;
|
|
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;
|
|
154
232
|
}
|
|
233
|
+
return results;
|
|
234
|
+
}
|
|
155
235
|
|
|
156
|
-
|
|
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
|
+
}
|
|
157
253
|
}
|
|
158
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;
|