codemini-cli 0.3.4 → 0.3.5
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/package.json +1 -1
- package/souls/anime.md +9 -9
- package/souls/caveman.md +6 -6
- package/souls/ceo.md +10 -9
- package/souls/default.md +1 -1
- package/souls/pirate.md +6 -6
- package/souls/playful.md +7 -7
- package/souls/professional.md +1 -1
- package/src/core/agent-loop.js +8 -2
- package/src/core/chat-runtime.js +8 -0
- package/src/core/provider/anthropic.sdk-backup.js +439 -0
- package/src/core/provider/openai-compatible.js +78 -9
- package/src/core/provider/openai-compatible.sdk-backup.js +412 -0
- package/src/core/session-store.js +8 -0
- package/src/core/shell-profile.js +9 -5
- package/src/core/tools.js +64 -12
- package/src/tui/chat-app.js +30 -0
|
@@ -12,6 +12,20 @@ function extractTextContent(content) {
|
|
|
12
12
|
return '';
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function extractReasoningContent(payload) {
|
|
16
|
+
if (typeof payload === 'string') return payload;
|
|
17
|
+
if (Array.isArray(payload)) {
|
|
18
|
+
return payload
|
|
19
|
+
.map((part) => {
|
|
20
|
+
if (part?.type === 'reasoning') return part.text || '';
|
|
21
|
+
if (part?.type === 'reasoning_content') return part.text || part.reasoning_content || '';
|
|
22
|
+
return '';
|
|
23
|
+
})
|
|
24
|
+
.join('');
|
|
25
|
+
}
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
function emptyToolCall(index) {
|
|
16
30
|
return {
|
|
17
31
|
index,
|
|
@@ -43,6 +57,15 @@ async function parseJsonResponse(response) {
|
|
|
43
57
|
async function* iterateSseEvents(stream) {
|
|
44
58
|
const decoder = new TextDecoder();
|
|
45
59
|
let buffer = '';
|
|
60
|
+
const flushEvent = (rawEvent) => {
|
|
61
|
+
const dataLines = String(rawEvent || '')
|
|
62
|
+
.split(/\r?\n/)
|
|
63
|
+
.filter((line) => line.startsWith('data:'))
|
|
64
|
+
.map((line) => line.slice(5).trimStart());
|
|
65
|
+
const dataText = dataLines.join('\n');
|
|
66
|
+
if (!dataText || dataText === '[DONE]') return null;
|
|
67
|
+
return JSON.parse(dataText);
|
|
68
|
+
};
|
|
46
69
|
|
|
47
70
|
for await (const chunk of stream) {
|
|
48
71
|
buffer += decoder.decode(chunk, { stream: true });
|
|
@@ -55,15 +78,16 @@ async function* iterateSseEvents(stream) {
|
|
|
55
78
|
const separatorLength = useCrlf ? 4 : 2;
|
|
56
79
|
const rawEvent = buffer.slice(0, boundary);
|
|
57
80
|
buffer = buffer.slice(boundary + separatorLength);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
.filter((line) => line.startsWith('data:'))
|
|
61
|
-
.map((line) => line.slice(5).trimStart());
|
|
62
|
-
const dataText = dataLines.join('\n');
|
|
63
|
-
if (!dataText || dataText === '[DONE]') continue;
|
|
64
|
-
yield JSON.parse(dataText);
|
|
81
|
+
const parsed = flushEvent(rawEvent);
|
|
82
|
+
if (parsed) yield parsed;
|
|
65
83
|
}
|
|
66
84
|
}
|
|
85
|
+
|
|
86
|
+
buffer += decoder.decode();
|
|
87
|
+
const trailingEvent = flushEvent(buffer.trim());
|
|
88
|
+
if (trailingEvent) {
|
|
89
|
+
yield trailingEvent;
|
|
90
|
+
}
|
|
67
91
|
}
|
|
68
92
|
|
|
69
93
|
function isMiniMaxModel(model) {
|
|
@@ -112,6 +136,27 @@ function sanitizeGatewayMessages(messages) {
|
|
|
112
136
|
});
|
|
113
137
|
}
|
|
114
138
|
|
|
139
|
+
function buildAssistantMessage({ text = '', toolCalls = [], content, reasoningContent = '' }) {
|
|
140
|
+
const assistantMessage = {
|
|
141
|
+
role: 'assistant',
|
|
142
|
+
content: content ?? text
|
|
143
|
+
};
|
|
144
|
+
if (reasoningContent) {
|
|
145
|
+
assistantMessage.reasoning_content = reasoningContent;
|
|
146
|
+
}
|
|
147
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
148
|
+
assistantMessage.tool_calls = toolCalls.map((tc) => ({
|
|
149
|
+
id: tc.id,
|
|
150
|
+
type: 'function',
|
|
151
|
+
function: {
|
|
152
|
+
name: tc.name,
|
|
153
|
+
arguments: tc.arguments || '{}'
|
|
154
|
+
}
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
return assistantMessage;
|
|
158
|
+
}
|
|
159
|
+
|
|
115
160
|
function sanitizeMiniMaxMessages(messages) {
|
|
116
161
|
const source = Array.isArray(messages) ? messages : [];
|
|
117
162
|
const out = [];
|
|
@@ -282,6 +327,7 @@ export async function createChatCompletion({
|
|
|
282
327
|
const data = await parseJsonResponse(response);
|
|
283
328
|
const message = data?.choices?.[0]?.message || {};
|
|
284
329
|
const text = sanitizeMiniMaxText(model, extractTextContent(message.content));
|
|
330
|
+
const reasoningContent = extractReasoningContent(message.reasoning_content);
|
|
285
331
|
const toolCalls = (message.tool_calls || []).map((tc) => ({
|
|
286
332
|
id: tc.id,
|
|
287
333
|
name: tc.function?.name,
|
|
@@ -304,7 +350,13 @@ export async function createChatCompletion({
|
|
|
304
350
|
return {
|
|
305
351
|
text,
|
|
306
352
|
toolCalls,
|
|
307
|
-
usage: data?.usage || null
|
|
353
|
+
usage: data?.usage || null,
|
|
354
|
+
assistantMessage: buildAssistantMessage({
|
|
355
|
+
text,
|
|
356
|
+
toolCalls,
|
|
357
|
+
content: message.content ?? text,
|
|
358
|
+
reasoningContent
|
|
359
|
+
})
|
|
308
360
|
};
|
|
309
361
|
}
|
|
310
362
|
|
|
@@ -332,6 +384,7 @@ export async function createChatCompletionStream({
|
|
|
332
384
|
throw new Error(`Gateway error ${response.status}: ${text || response.statusText}`);
|
|
333
385
|
}
|
|
334
386
|
let text = '';
|
|
387
|
+
let reasoningContent = '';
|
|
335
388
|
const toolCallsByIndex = new Map();
|
|
336
389
|
let usage = null;
|
|
337
390
|
let miniMaxStreamState = { rawContent: '', visibleText: '' };
|
|
@@ -341,6 +394,10 @@ export async function createChatCompletionStream({
|
|
|
341
394
|
const choice0 = chunk?.choices?.[0] || {};
|
|
342
395
|
const delta = choice0?.delta || {};
|
|
343
396
|
const content = delta.content;
|
|
397
|
+
const reasoningDelta = extractReasoningContent(delta.reasoning_content);
|
|
398
|
+
if (reasoningDelta) {
|
|
399
|
+
reasoningContent += reasoningDelta;
|
|
400
|
+
}
|
|
344
401
|
if (isMiniMaxModel(model)) {
|
|
345
402
|
const next = nextMiniMaxVisibleChunk(miniMaxStreamState, content);
|
|
346
403
|
miniMaxStreamState = next.nextState;
|
|
@@ -378,7 +435,19 @@ export async function createChatCompletionStream({
|
|
|
378
435
|
});
|
|
379
436
|
}
|
|
380
437
|
}
|
|
438
|
+
|
|
439
|
+
if (choice0?.finish_reason) {
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
381
442
|
}
|
|
382
443
|
|
|
383
|
-
|
|
444
|
+
const result = buildFinalStreamResult(text, toolCallsByIndex, usage, messages);
|
|
445
|
+
return {
|
|
446
|
+
...result,
|
|
447
|
+
assistantMessage: buildAssistantMessage({
|
|
448
|
+
text: result.text,
|
|
449
|
+
toolCalls: result.toolCalls,
|
|
450
|
+
reasoningContent
|
|
451
|
+
})
|
|
452
|
+
};
|
|
384
453
|
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
|
|
3
|
+
function extractTextContent(content) {
|
|
4
|
+
if (typeof content === 'string') return content;
|
|
5
|
+
if (Array.isArray(content)) {
|
|
6
|
+
return content
|
|
7
|
+
.map((part) => {
|
|
8
|
+
if (typeof part === 'string') return part;
|
|
9
|
+
if (part?.type === 'text') return part.text || '';
|
|
10
|
+
return '';
|
|
11
|
+
})
|
|
12
|
+
.join('');
|
|
13
|
+
}
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function extractReasoningText(details) {
|
|
18
|
+
const source = Array.isArray(details) ? details : [];
|
|
19
|
+
return source
|
|
20
|
+
.map((detail) => {
|
|
21
|
+
if (typeof detail === 'string') return detail;
|
|
22
|
+
return typeof detail?.text === 'string' ? detail.text : '';
|
|
23
|
+
})
|
|
24
|
+
.join('');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeReasoningDetails(details) {
|
|
28
|
+
if (!Array.isArray(details) || details.length === 0) return undefined;
|
|
29
|
+
const normalized = details
|
|
30
|
+
.map((detail) => {
|
|
31
|
+
if (!detail || typeof detail !== 'object') return null;
|
|
32
|
+
return { ...detail };
|
|
33
|
+
})
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function emptyToolCall(index) {
|
|
39
|
+
return {
|
|
40
|
+
index,
|
|
41
|
+
id: '',
|
|
42
|
+
name: '',
|
|
43
|
+
arguments: ''
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isMiniMaxModel(model) {
|
|
48
|
+
return String(model || '').toLowerCase().includes('minimax');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeToolCallArguments(argumentsText) {
|
|
52
|
+
const raw = typeof argumentsText === 'string' ? argumentsText : JSON.stringify(argumentsText ?? {});
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(raw);
|
|
55
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
56
|
+
return JSON.stringify(parsed);
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
return '{}';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeIncomingToolCallArguments(argumentsValue) {
|
|
63
|
+
if (typeof argumentsValue === 'string') return argumentsValue;
|
|
64
|
+
if (argumentsValue == null) return '{}';
|
|
65
|
+
try {
|
|
66
|
+
return JSON.stringify(argumentsValue);
|
|
67
|
+
} catch {
|
|
68
|
+
return '{}';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sanitizeGatewayMessages(messages) {
|
|
73
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
74
|
+
return source
|
|
75
|
+
.filter((message) => message && typeof message === 'object')
|
|
76
|
+
.map((message) => {
|
|
77
|
+
if (!Array.isArray(message.tool_calls) || message.tool_calls.length === 0) {
|
|
78
|
+
return message;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
...message,
|
|
82
|
+
tool_calls: message.tool_calls.map((toolCall) => ({
|
|
83
|
+
...toolCall,
|
|
84
|
+
function: {
|
|
85
|
+
...toolCall?.function,
|
|
86
|
+
arguments: normalizeToolCallArguments(toolCall?.function?.arguments)
|
|
87
|
+
}
|
|
88
|
+
}))
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function sanitizeMiniMaxMessages(messages) {
|
|
94
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
95
|
+
const out = [];
|
|
96
|
+
let seenNonSystem = false;
|
|
97
|
+
let keptLeadingSystem = false;
|
|
98
|
+
|
|
99
|
+
for (const message of source) {
|
|
100
|
+
if (!message || typeof message !== 'object') continue;
|
|
101
|
+
if (message.role === 'system') {
|
|
102
|
+
if (!seenNonSystem && !keptLeadingSystem) {
|
|
103
|
+
out.push(message);
|
|
104
|
+
keptLeadingSystem = true;
|
|
105
|
+
} else {
|
|
106
|
+
out.push({
|
|
107
|
+
role: 'user',
|
|
108
|
+
content: `[system-note]\n${extractTextContent(message.content)}`
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
seenNonSystem = true;
|
|
114
|
+
out.push(message);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildPayload({ model, temperature, messages, tools, stream = false }) {
|
|
121
|
+
const sanitizedMessages = sanitizeGatewayMessages(messages);
|
|
122
|
+
const payload = {
|
|
123
|
+
model,
|
|
124
|
+
temperature,
|
|
125
|
+
messages: isMiniMaxModel(model) ? sanitizeMiniMaxMessages(sanitizedMessages) : sanitizedMessages
|
|
126
|
+
};
|
|
127
|
+
if (stream) payload.stream = true;
|
|
128
|
+
if (Array.isArray(tools) && tools.length > 0) {
|
|
129
|
+
payload.tools = tools;
|
|
130
|
+
payload.tool_choice = 'auto';
|
|
131
|
+
}
|
|
132
|
+
if (isMiniMaxModel(model)) {
|
|
133
|
+
payload.extra_body = { reasoning_split: true };
|
|
134
|
+
}
|
|
135
|
+
return payload;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function hasTrailingToolContext(messages) {
|
|
139
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
140
|
+
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
141
|
+
const message = source[index];
|
|
142
|
+
if (!message || typeof message !== 'object') continue;
|
|
143
|
+
if (message.role === 'tool') return true;
|
|
144
|
+
if (message.role === 'assistant' || message.role === 'user') return false;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function stripMiniMaxThinkContent(text) {
|
|
150
|
+
const input = String(text || '');
|
|
151
|
+
if (!input) return '';
|
|
152
|
+
|
|
153
|
+
let cursor = 0;
|
|
154
|
+
let out = '';
|
|
155
|
+
let removedThink = false;
|
|
156
|
+
|
|
157
|
+
while (cursor < input.length) {
|
|
158
|
+
const openIdx = input.indexOf('<think>', cursor);
|
|
159
|
+
const closeIdx = input.indexOf('</think>', cursor);
|
|
160
|
+
|
|
161
|
+
if (openIdx === -1 && closeIdx === -1) {
|
|
162
|
+
out += input.slice(cursor);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (closeIdx !== -1 && (openIdx === -1 || closeIdx < openIdx)) {
|
|
167
|
+
removedThink = true;
|
|
168
|
+
cursor = closeIdx + '</think>'.length;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
out += input.slice(cursor, openIdx);
|
|
173
|
+
const closingTagIdx = input.indexOf('</think>', openIdx + '<think>'.length);
|
|
174
|
+
removedThink = true;
|
|
175
|
+
if (closingTagIdx === -1) {
|
|
176
|
+
cursor = input.length;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
cursor = closingTagIdx + '</think>'.length;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return removedThink ? out.trimStart() : out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function sanitizeMiniMaxText(model, text) {
|
|
186
|
+
return isMiniMaxModel(model) ? stripMiniMaxThinkContent(text) : text;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function nextMiniMaxVisibleChunk(state, content) {
|
|
190
|
+
const rawChunk = extractTextContent(content);
|
|
191
|
+
if (!rawChunk) {
|
|
192
|
+
return { textDelta: '', nextState: state };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const nextRawContent = rawChunk.startsWith(state.rawContent) ? rawChunk : `${state.rawContent}${rawChunk}`;
|
|
196
|
+
const nextVisibleText = stripMiniMaxThinkContent(nextRawContent);
|
|
197
|
+
const textDelta = nextVisibleText.startsWith(state.visibleText)
|
|
198
|
+
? nextVisibleText.slice(state.visibleText.length)
|
|
199
|
+
: nextVisibleText;
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
textDelta,
|
|
203
|
+
nextState: {
|
|
204
|
+
rawContent: nextRawContent,
|
|
205
|
+
visibleText: nextVisibleText
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildAssistantMessage({ text = '', toolCalls = [], content, reasoningDetails }) {
|
|
211
|
+
const assistantMessage = {
|
|
212
|
+
role: 'assistant',
|
|
213
|
+
content: content ?? text
|
|
214
|
+
};
|
|
215
|
+
const normalizedReasoningDetails = normalizeReasoningDetails(reasoningDetails);
|
|
216
|
+
if (normalizedReasoningDetails) {
|
|
217
|
+
assistantMessage.reasoning_details = normalizedReasoningDetails;
|
|
218
|
+
assistantMessage.reasoning_content = extractReasoningText(normalizedReasoningDetails);
|
|
219
|
+
}
|
|
220
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
221
|
+
assistantMessage.tool_calls = toolCalls.map((tc) => ({
|
|
222
|
+
id: tc.id,
|
|
223
|
+
type: 'function',
|
|
224
|
+
function: {
|
|
225
|
+
name: tc.name,
|
|
226
|
+
arguments: tc.arguments || '{}'
|
|
227
|
+
}
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
return assistantMessage;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function createClient({ baseUrl, apiKey, timeoutMs = 90000, maxRetries = 2 }) {
|
|
234
|
+
return new OpenAI({
|
|
235
|
+
apiKey,
|
|
236
|
+
baseURL: String(baseUrl || '').replace(/\/$/, ''),
|
|
237
|
+
timeout: timeoutMs,
|
|
238
|
+
maxRetries
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function buildFinalStreamResult({ text, toolCallsByIndex, usage, messages, reasoningDetails }) {
|
|
243
|
+
const toolCalls = Array.from(toolCallsByIndex.entries())
|
|
244
|
+
.sort((a, b) => a[0] - b[0])
|
|
245
|
+
.map(([, tc], i) => ({
|
|
246
|
+
id: tc.id || `tc-${i + 1}`,
|
|
247
|
+
name: tc.name,
|
|
248
|
+
arguments: tc.arguments || '{}'
|
|
249
|
+
}))
|
|
250
|
+
.filter((tc) => tc.name);
|
|
251
|
+
const normalizedText = String(text || '').trim();
|
|
252
|
+
|
|
253
|
+
if (!normalizedText && toolCalls.length === 0) {
|
|
254
|
+
if (hasTrailingToolContext(messages)) {
|
|
255
|
+
return {
|
|
256
|
+
text: '',
|
|
257
|
+
toolCalls: [],
|
|
258
|
+
usage,
|
|
259
|
+
incomplete: true
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
throw new Error('Gateway stream returned empty assistant response');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
text,
|
|
267
|
+
toolCalls,
|
|
268
|
+
usage,
|
|
269
|
+
incomplete: false,
|
|
270
|
+
assistantMessage: buildAssistantMessage({
|
|
271
|
+
text,
|
|
272
|
+
toolCalls,
|
|
273
|
+
reasoningDetails
|
|
274
|
+
})
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function createChatCompletion({
|
|
279
|
+
baseUrl,
|
|
280
|
+
apiKey,
|
|
281
|
+
model,
|
|
282
|
+
messages,
|
|
283
|
+
temperature = 0.2,
|
|
284
|
+
tools,
|
|
285
|
+
timeoutMs = 90000,
|
|
286
|
+
maxRetries = 2
|
|
287
|
+
}) {
|
|
288
|
+
const client = createClient({ baseUrl, apiKey, timeoutMs, maxRetries });
|
|
289
|
+
const response = await client.chat.completions.create(buildPayload({ model, temperature, messages, tools }));
|
|
290
|
+
const message = response?.choices?.[0]?.message || {};
|
|
291
|
+
const text = sanitizeMiniMaxText(model, extractTextContent(message.content));
|
|
292
|
+
const toolCalls = (message.tool_calls || []).map((tc) => ({
|
|
293
|
+
id: tc.id,
|
|
294
|
+
name: tc.function?.name,
|
|
295
|
+
arguments: normalizeIncomingToolCallArguments(tc.function?.arguments)
|
|
296
|
+
}));
|
|
297
|
+
const normalizedText = String(text || '').trim();
|
|
298
|
+
|
|
299
|
+
if (!normalizedText && toolCalls.length === 0) {
|
|
300
|
+
if (hasTrailingToolContext(messages)) {
|
|
301
|
+
return {
|
|
302
|
+
text: '',
|
|
303
|
+
toolCalls: [],
|
|
304
|
+
usage: response?.usage || null,
|
|
305
|
+
incomplete: true
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
throw new Error('Gateway returned empty assistant response');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
text,
|
|
313
|
+
toolCalls,
|
|
314
|
+
usage: response?.usage || null,
|
|
315
|
+
assistantMessage: buildAssistantMessage({
|
|
316
|
+
text,
|
|
317
|
+
toolCalls,
|
|
318
|
+
content: message.content ?? text,
|
|
319
|
+
reasoningDetails: message.reasoning_details
|
|
320
|
+
})
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function createChatCompletionStream({
|
|
325
|
+
baseUrl,
|
|
326
|
+
apiKey,
|
|
327
|
+
model,
|
|
328
|
+
messages,
|
|
329
|
+
temperature = 0.2,
|
|
330
|
+
tools,
|
|
331
|
+
onTextDelta,
|
|
332
|
+
onToolCallDelta,
|
|
333
|
+
timeoutMs = 90000,
|
|
334
|
+
maxRetries = 2
|
|
335
|
+
}) {
|
|
336
|
+
const client = createClient({ baseUrl, apiKey, timeoutMs, maxRetries });
|
|
337
|
+
const stream = await client.chat.completions.create(buildPayload({ model, temperature, messages, tools, stream: true }));
|
|
338
|
+
|
|
339
|
+
let text = '';
|
|
340
|
+
let usage = null;
|
|
341
|
+
const toolCallsByIndex = new Map();
|
|
342
|
+
let miniMaxStreamState = { rawContent: '', visibleText: '' };
|
|
343
|
+
let reasoningDetails = [];
|
|
344
|
+
let previousReasoningText = '';
|
|
345
|
+
|
|
346
|
+
for await (const chunk of stream) {
|
|
347
|
+
usage = chunk?.usage || usage;
|
|
348
|
+
const choice0 = chunk?.choices?.[0] || {};
|
|
349
|
+
const delta = choice0?.delta || {};
|
|
350
|
+
const content = delta.content;
|
|
351
|
+
const nextReasoningDetails = normalizeReasoningDetails(delta.reasoning_details);
|
|
352
|
+
if (nextReasoningDetails) {
|
|
353
|
+
const nextReasoningText = extractReasoningText(nextReasoningDetails);
|
|
354
|
+
if (nextReasoningText.length >= previousReasoningText.length) {
|
|
355
|
+
previousReasoningText = nextReasoningText;
|
|
356
|
+
} else {
|
|
357
|
+
previousReasoningText += nextReasoningText;
|
|
358
|
+
}
|
|
359
|
+
reasoningDetails = previousReasoningText ? [{ type: 'reasoning', text: previousReasoningText }] : reasoningDetails;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (isMiniMaxModel(model)) {
|
|
363
|
+
const next = nextMiniMaxVisibleChunk(miniMaxStreamState, content);
|
|
364
|
+
miniMaxStreamState = next.nextState;
|
|
365
|
+
if (next.textDelta) {
|
|
366
|
+
text += next.textDelta;
|
|
367
|
+
if (onTextDelta) onTextDelta(next.textDelta);
|
|
368
|
+
}
|
|
369
|
+
} else if (typeof content === 'string' && content.length > 0) {
|
|
370
|
+
text += content;
|
|
371
|
+
if (onTextDelta) onTextDelta(content);
|
|
372
|
+
} else if (Array.isArray(content) && content.length > 0) {
|
|
373
|
+
const chunkText = extractTextContent(content);
|
|
374
|
+
if (chunkText) {
|
|
375
|
+
text += chunkText;
|
|
376
|
+
if (onTextDelta) onTextDelta(chunkText);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const toolDeltas = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
|
|
381
|
+
for (const td of toolDeltas) {
|
|
382
|
+
const idx = typeof td.index === 'number' ? td.index : 0;
|
|
383
|
+
const current = toolCallsByIndex.get(idx) || emptyToolCall(idx);
|
|
384
|
+
if (td.id) current.id = td.id;
|
|
385
|
+
if (td.function?.name) current.name = `${current.name}${td.function.name}`;
|
|
386
|
+
if (td.function?.arguments !== undefined) {
|
|
387
|
+
current.arguments = `${current.arguments}${normalizeIncomingToolCallArguments(td.function.arguments)}`;
|
|
388
|
+
}
|
|
389
|
+
toolCallsByIndex.set(idx, current);
|
|
390
|
+
if (onToolCallDelta) {
|
|
391
|
+
onToolCallDelta({
|
|
392
|
+
index: idx,
|
|
393
|
+
id: current.id || `tc-${idx + 1}`,
|
|
394
|
+
name: current.name,
|
|
395
|
+
arguments: current.arguments || '{}'
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (choice0?.finish_reason) {
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return buildFinalStreamResult({
|
|
406
|
+
text,
|
|
407
|
+
toolCallsByIndex,
|
|
408
|
+
usage,
|
|
409
|
+
messages,
|
|
410
|
+
reasoningDetails
|
|
411
|
+
});
|
|
412
|
+
}
|
|
@@ -41,6 +41,14 @@ function sanitizeMessage(msg) {
|
|
|
41
41
|
if (msg?.tool_call_id) out.tool_call_id = String(msg.tool_call_id);
|
|
42
42
|
if (typeof msg?.name === 'string' && msg.name.trim()) out.name = msg.name.trim();
|
|
43
43
|
if (typeof msg?.at === 'string' && msg.at.trim()) out.at = msg.at;
|
|
44
|
+
if (typeof msg?.reasoning_content === 'string' && msg.reasoning_content) {
|
|
45
|
+
out.reasoning_content = msg.reasoning_content;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(msg?.reasoning_details) && msg.reasoning_details.length > 0) {
|
|
48
|
+
out.reasoning_details = msg.reasoning_details
|
|
49
|
+
.filter((detail) => detail && typeof detail === 'object')
|
|
50
|
+
.map((detail) => ({ ...detail }));
|
|
51
|
+
}
|
|
44
52
|
|
|
45
53
|
if (Array.isArray(msg?.tool_calls)) {
|
|
46
54
|
const toolCalls = msg.tool_calls.map(sanitizeToolCall).filter(Boolean);
|
|
@@ -144,13 +144,15 @@ Use update_todos with these rules:
|
|
|
144
144
|
|
|
145
145
|
Some tools are loaded on demand through tool_search. Common examples:
|
|
146
146
|
- glob for pattern-based file lookup
|
|
147
|
-
- ast_query and read_ast_node for AST-scoped edits
|
|
147
|
+
- ast_query and read_ast_node for advanced AST-scoped reads and edits
|
|
148
148
|
- generate_diff and patch for explicit diff workflows
|
|
149
149
|
- list_background_tasks, get_background_task, and stop_background_task for managing long-running background commands
|
|
150
150
|
- remember_user, remember_global, remember_project, list_memory, search_memory, and forget_memory for persistent memory operations
|
|
151
151
|
|
|
152
|
-
For structural code edits (functions, classes, methods),
|
|
153
|
-
|
|
152
|
+
For structural code edits (functions, classes, methods), prefer AST-scoped reads before editing:
|
|
153
|
+
- Common one-shot workflow: read(path, query=..., capture_name=...) → edit with symbol or ast_target
|
|
154
|
+
- If you already have ast_target: read(ast_target=...) → edit with ast_target
|
|
155
|
+
- Advanced multi-step workflow: tool_search("ast_query") → ast_query → read_ast_node → edit with ast_target and kind=replace_block
|
|
154
156
|
Fall back to plain grep/read/edit only when AST is not appropriate.
|
|
155
157
|
|
|
156
158
|
For background commands: use run to launch. If you need management tools that are not currently visible, load list_background_tasks/get_background_task/stop_background_task with tool_search. Prefer reading the returned output_file with read instead of asking for a separate logs tool.
|
|
@@ -192,8 +194,10 @@ Common tool call patterns:
|
|
|
192
194
|
|
|
193
195
|
# Tone and style
|
|
194
196
|
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
+
- Keep answers compact and easy to scan
|
|
198
|
+
- Lead with the answer or next action, not scene-setting
|
|
199
|
+
- Do not restate the user's request unless a brief restatement prevents ambiguity
|
|
197
200
|
- When referencing code, use file_path:line_number format
|
|
201
|
+
- Keep technical wording, commands, paths, and error details exact
|
|
198
202
|
- Only use emojis if the user explicitly requests it`;
|
|
199
203
|
}
|