snow-ai 0.1.12 → 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/dist/api/chat.d.ts +65 -2
- package/dist/api/chat.js +299 -16
- package/dist/api/responses.d.ts +52 -0
- package/dist/api/responses.js +541 -0
- package/dist/api/systemPrompt.d.ts +4 -0
- package/dist/api/systemPrompt.js +43 -0
- package/dist/app.js +15 -4
- package/dist/cli.js +38 -2
- package/dist/hooks/useConversation.d.ts +32 -0
- package/dist/hooks/useConversation.js +403 -0
- package/dist/hooks/useGlobalNavigation.d.ts +6 -0
- package/dist/hooks/useGlobalNavigation.js +15 -0
- package/dist/hooks/useSessionManagement.d.ts +10 -0
- package/dist/hooks/useSessionManagement.js +43 -0
- package/dist/hooks/useSessionSave.d.ts +8 -0
- package/dist/hooks/useSessionSave.js +52 -0
- package/dist/hooks/useToolConfirmation.d.ts +18 -0
- package/dist/hooks/useToolConfirmation.js +49 -0
- package/dist/mcp/bash.d.ts +57 -0
- package/dist/mcp/bash.js +138 -0
- package/dist/mcp/filesystem.d.ts +307 -0
- package/dist/mcp/filesystem.js +520 -0
- package/dist/mcp/todo.d.ts +55 -0
- package/dist/mcp/todo.js +329 -0
- package/dist/test/logger-test.d.ts +1 -0
- package/dist/test/logger-test.js +7 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/ui/components/ChatInput.d.ts +15 -2
- package/dist/ui/components/ChatInput.js +445 -59
- package/dist/ui/components/CommandPanel.d.ts +2 -2
- package/dist/ui/components/CommandPanel.js +11 -7
- package/dist/ui/components/DiffViewer.d.ts +9 -0
- package/dist/ui/components/DiffViewer.js +93 -0
- package/dist/ui/components/FileList.d.ts +14 -0
- package/dist/ui/components/FileList.js +131 -0
- package/dist/ui/components/MCPInfoPanel.d.ts +2 -0
- package/dist/ui/components/MCPInfoPanel.js +74 -0
- package/dist/ui/components/MCPInfoScreen.d.ts +7 -0
- package/dist/ui/components/MCPInfoScreen.js +27 -0
- package/dist/ui/components/MarkdownRenderer.d.ts +7 -0
- package/dist/ui/components/MarkdownRenderer.js +110 -0
- package/dist/ui/components/Menu.d.ts +5 -2
- package/dist/ui/components/Menu.js +60 -9
- package/dist/ui/components/MessageList.d.ts +30 -2
- package/dist/ui/components/MessageList.js +64 -12
- package/dist/ui/components/PendingMessages.js +1 -1
- package/dist/ui/components/ScrollableSelectInput.d.ts +29 -0
- package/dist/ui/components/ScrollableSelectInput.js +157 -0
- package/dist/ui/components/SessionListScreen.d.ts +7 -0
- package/dist/ui/components/SessionListScreen.js +196 -0
- package/dist/ui/components/SessionListScreenWrapper.d.ts +7 -0
- package/dist/ui/components/SessionListScreenWrapper.js +14 -0
- package/dist/ui/components/TodoTree.d.ts +15 -0
- package/dist/ui/components/TodoTree.js +60 -0
- package/dist/ui/components/ToolConfirmation.d.ts +8 -0
- package/dist/ui/components/ToolConfirmation.js +38 -0
- package/dist/ui/components/ToolResultPreview.d.ts +12 -0
- package/dist/ui/components/ToolResultPreview.js +115 -0
- package/dist/ui/pages/ChatScreen.d.ts +4 -0
- package/dist/ui/pages/ChatScreen.js +385 -196
- package/dist/ui/pages/MCPConfigScreen.d.ts +6 -0
- package/dist/ui/pages/MCPConfigScreen.js +55 -0
- package/dist/ui/pages/ModelConfigScreen.js +73 -12
- package/dist/ui/pages/WelcomeScreen.js +17 -11
- package/dist/utils/apiConfig.d.ts +12 -0
- package/dist/utils/apiConfig.js +95 -9
- package/dist/utils/commandExecutor.d.ts +2 -1
- package/dist/utils/commands/init.d.ts +2 -0
- package/dist/utils/commands/init.js +93 -0
- package/dist/utils/commands/mcp.d.ts +2 -0
- package/dist/utils/commands/mcp.js +12 -0
- package/dist/utils/commands/resume.d.ts +2 -0
- package/dist/utils/commands/resume.js +12 -0
- package/dist/utils/commands/yolo.d.ts +2 -0
- package/dist/utils/commands/yolo.js +12 -0
- package/dist/utils/fileUtils.d.ts +44 -0
- package/dist/utils/fileUtils.js +222 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logger.js +97 -0
- package/dist/utils/mcpToolsManager.d.ts +47 -0
- package/dist/utils/mcpToolsManager.js +476 -0
- package/dist/utils/messageFormatter.d.ts +12 -0
- package/dist/utils/messageFormatter.js +32 -0
- package/dist/utils/sessionConverter.d.ts +6 -0
- package/dist/utils/sessionConverter.js +61 -0
- package/dist/utils/sessionManager.d.ts +39 -0
- package/dist/utils/sessionManager.js +141 -0
- package/dist/utils/textBuffer.d.ts +36 -7
- package/dist/utils/textBuffer.js +265 -179
- package/dist/utils/todoPreprocessor.d.ts +5 -0
- package/dist/utils/todoPreprocessor.js +19 -0
- package/dist/utils/toolExecutor.d.ts +21 -0
- package/dist/utils/toolExecutor.js +28 -0
- package/package.json +12 -3
- package/readme.md +2 -2
package/dist/api/chat.d.ts
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
import type { ChatCompletionTool } from 'openai/resources/chat/completions';
|
|
2
|
+
export interface ImageContent {
|
|
3
|
+
type: 'image';
|
|
4
|
+
data: string;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
}
|
|
1
7
|
export interface ChatMessage {
|
|
2
|
-
role: 'system' | 'user' | 'assistant';
|
|
8
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
3
9
|
content: string;
|
|
10
|
+
tool_call_id?: string;
|
|
11
|
+
tool_calls?: ToolCall[];
|
|
12
|
+
images?: ImageContent[];
|
|
13
|
+
}
|
|
14
|
+
export interface ToolCall {
|
|
15
|
+
id: string;
|
|
16
|
+
type: 'function';
|
|
17
|
+
function: {
|
|
18
|
+
name: string;
|
|
19
|
+
arguments: string;
|
|
20
|
+
};
|
|
4
21
|
}
|
|
5
22
|
export interface ChatCompletionOptions {
|
|
6
23
|
model: string;
|
|
@@ -8,6 +25,13 @@ export interface ChatCompletionOptions {
|
|
|
8
25
|
stream?: boolean;
|
|
9
26
|
temperature?: number;
|
|
10
27
|
max_tokens?: number;
|
|
28
|
+
tools?: ChatCompletionTool[];
|
|
29
|
+
tool_choice?: 'auto' | 'none' | 'required' | {
|
|
30
|
+
type: 'function';
|
|
31
|
+
function: {
|
|
32
|
+
name: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
11
35
|
}
|
|
12
36
|
export interface ChatCompletionChunk {
|
|
13
37
|
id: string;
|
|
@@ -19,11 +43,50 @@ export interface ChatCompletionChunk {
|
|
|
19
43
|
delta: {
|
|
20
44
|
role?: string;
|
|
21
45
|
content?: string;
|
|
46
|
+
tool_calls?: Array<{
|
|
47
|
+
index?: number;
|
|
48
|
+
id?: string;
|
|
49
|
+
type?: 'function';
|
|
50
|
+
function?: {
|
|
51
|
+
name?: string;
|
|
52
|
+
arguments?: string;
|
|
53
|
+
};
|
|
54
|
+
}>;
|
|
22
55
|
};
|
|
23
56
|
finish_reason?: string | null;
|
|
24
57
|
}>;
|
|
25
58
|
}
|
|
26
59
|
export declare function resetOpenAIClient(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Create chat completion with automatic function calling support
|
|
62
|
+
*/
|
|
63
|
+
export declare function createChatCompletionWithTools(options: ChatCompletionOptions, maxToolRounds?: number): Promise<{
|
|
64
|
+
content: string;
|
|
65
|
+
toolCalls: ToolCall[];
|
|
66
|
+
}>;
|
|
27
67
|
export declare function createChatCompletion(options: ChatCompletionOptions): Promise<string>;
|
|
28
|
-
export
|
|
68
|
+
export interface UsageInfo {
|
|
69
|
+
prompt_tokens: number;
|
|
70
|
+
completion_tokens: number;
|
|
71
|
+
total_tokens: number;
|
|
72
|
+
}
|
|
73
|
+
export interface StreamChunk {
|
|
74
|
+
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'reasoning_delta' | 'done' | 'usage';
|
|
75
|
+
content?: string;
|
|
76
|
+
tool_calls?: Array<{
|
|
77
|
+
id: string;
|
|
78
|
+
type: 'function';
|
|
79
|
+
function: {
|
|
80
|
+
name: string;
|
|
81
|
+
arguments: string;
|
|
82
|
+
};
|
|
83
|
+
}>;
|
|
84
|
+
delta?: string;
|
|
85
|
+
usage?: UsageInfo;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Simple streaming chat completion - only handles OpenAI interaction
|
|
89
|
+
* Tool execution should be handled by the caller
|
|
90
|
+
*/
|
|
91
|
+
export declare function createStreamingChatCompletion(options: ChatCompletionOptions, abortSignal?: AbortSignal): AsyncGenerator<StreamChunk, void, unknown>;
|
|
29
92
|
export declare function validateChatOptions(options: ChatCompletionOptions): string[];
|
package/dist/api/chat.js
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
import { getOpenAiConfig } from '../utils/apiConfig.js';
|
|
3
|
+
import { executeMCPTool } from '../utils/mcpToolsManager.js';
|
|
4
|
+
import { SYSTEM_PROMPT } from './systemPrompt.js';
|
|
5
|
+
/**
|
|
6
|
+
* Convert our ChatMessage format to OpenAI's ChatCompletionMessageParam format
|
|
7
|
+
* Automatically prepends system prompt if not present
|
|
8
|
+
*/
|
|
9
|
+
function convertToOpenAIMessages(messages, includeSystemPrompt = true) {
|
|
10
|
+
let result = messages.map(msg => {
|
|
11
|
+
// 如果消息包含图片,使用 content 数组格式
|
|
12
|
+
if (msg.role === 'user' && msg.images && msg.images.length > 0) {
|
|
13
|
+
const contentParts = [];
|
|
14
|
+
// 添加文本内容
|
|
15
|
+
if (msg.content) {
|
|
16
|
+
contentParts.push({
|
|
17
|
+
type: 'text',
|
|
18
|
+
text: msg.content
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
// 添加图片内容
|
|
22
|
+
for (const image of msg.images) {
|
|
23
|
+
contentParts.push({
|
|
24
|
+
type: 'image_url',
|
|
25
|
+
image_url: {
|
|
26
|
+
url: image.data // Base64 data URL
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
role: 'user',
|
|
32
|
+
content: contentParts
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const baseMessage = {
|
|
36
|
+
role: msg.role,
|
|
37
|
+
content: msg.content
|
|
38
|
+
};
|
|
39
|
+
if (msg.role === 'assistant' && msg.tool_calls) {
|
|
40
|
+
return {
|
|
41
|
+
...baseMessage,
|
|
42
|
+
tool_calls: msg.tool_calls
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (msg.role === 'tool' && msg.tool_call_id) {
|
|
46
|
+
return {
|
|
47
|
+
role: 'tool',
|
|
48
|
+
content: msg.content,
|
|
49
|
+
tool_call_id: msg.tool_call_id
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return baseMessage;
|
|
53
|
+
});
|
|
54
|
+
// 如果需要系统提示词且第一条消息不是 system 消息,则添加
|
|
55
|
+
if (includeSystemPrompt && (result.length === 0 || result[0]?.role !== 'system')) {
|
|
56
|
+
result = [
|
|
57
|
+
{
|
|
58
|
+
role: 'system',
|
|
59
|
+
content: SYSTEM_PROMPT
|
|
60
|
+
},
|
|
61
|
+
...result
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
3
66
|
let openaiClient = null;
|
|
4
67
|
function getOpenAIClient() {
|
|
5
68
|
if (!openaiClient) {
|
|
@@ -17,17 +80,134 @@ function getOpenAIClient() {
|
|
|
17
80
|
export function resetOpenAIClient() {
|
|
18
81
|
openaiClient = null;
|
|
19
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Create chat completion with automatic function calling support
|
|
85
|
+
*/
|
|
86
|
+
export async function createChatCompletionWithTools(options, maxToolRounds = 5) {
|
|
87
|
+
const client = getOpenAIClient();
|
|
88
|
+
let messages = [...options.messages];
|
|
89
|
+
let allToolCalls = [];
|
|
90
|
+
let rounds = 0;
|
|
91
|
+
try {
|
|
92
|
+
while (rounds < maxToolRounds) {
|
|
93
|
+
const response = await client.chat.completions.create({
|
|
94
|
+
model: options.model,
|
|
95
|
+
messages: convertToOpenAIMessages(messages),
|
|
96
|
+
stream: false,
|
|
97
|
+
temperature: options.temperature || 0.7,
|
|
98
|
+
max_tokens: options.max_tokens,
|
|
99
|
+
tools: options.tools,
|
|
100
|
+
tool_choice: options.tool_choice,
|
|
101
|
+
});
|
|
102
|
+
const message = response.choices[0]?.message;
|
|
103
|
+
if (!message) {
|
|
104
|
+
throw new Error('No response from AI');
|
|
105
|
+
}
|
|
106
|
+
// Add assistant message to conversation
|
|
107
|
+
messages.push({
|
|
108
|
+
role: 'assistant',
|
|
109
|
+
content: message.content || '',
|
|
110
|
+
tool_calls: message.tool_calls
|
|
111
|
+
});
|
|
112
|
+
// Check if AI wants to call tools
|
|
113
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
114
|
+
allToolCalls.push(...message.tool_calls);
|
|
115
|
+
// Execute each tool call
|
|
116
|
+
for (const toolCall of message.tool_calls) {
|
|
117
|
+
if (toolCall.type === 'function') {
|
|
118
|
+
try {
|
|
119
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
120
|
+
const result = await executeMCPTool(toolCall.function.name, args);
|
|
121
|
+
// Add tool result to conversation
|
|
122
|
+
messages.push({
|
|
123
|
+
role: 'tool',
|
|
124
|
+
content: JSON.stringify(result),
|
|
125
|
+
tool_call_id: toolCall.id
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// Add error result to conversation
|
|
130
|
+
messages.push({
|
|
131
|
+
role: 'tool',
|
|
132
|
+
content: `Error: ${error instanceof Error ? error.message : 'Tool execution failed'}`,
|
|
133
|
+
tool_call_id: toolCall.id
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
rounds++;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// No tool calls, return the content
|
|
142
|
+
return {
|
|
143
|
+
content: message.content || '',
|
|
144
|
+
toolCalls: allToolCalls
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
throw new Error(`Maximum tool calling rounds (${maxToolRounds}) exceeded`);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error instanceof Error) {
|
|
151
|
+
throw new Error(`Chat completion with tools failed: ${error.message}`);
|
|
152
|
+
}
|
|
153
|
+
throw new Error('Chat completion with tools failed: Unknown error');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
20
156
|
export async function createChatCompletion(options) {
|
|
21
157
|
const client = getOpenAIClient();
|
|
158
|
+
let messages = [...options.messages];
|
|
22
159
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
160
|
+
while (true) {
|
|
161
|
+
const response = await client.chat.completions.create({
|
|
162
|
+
model: options.model,
|
|
163
|
+
messages: convertToOpenAIMessages(messages),
|
|
164
|
+
stream: false,
|
|
165
|
+
temperature: options.temperature || 0.7,
|
|
166
|
+
max_tokens: options.max_tokens,
|
|
167
|
+
tools: options.tools,
|
|
168
|
+
tool_choice: options.tool_choice,
|
|
169
|
+
});
|
|
170
|
+
const message = response.choices[0]?.message;
|
|
171
|
+
if (!message) {
|
|
172
|
+
throw new Error('No response from AI');
|
|
173
|
+
}
|
|
174
|
+
// Add assistant message to conversation
|
|
175
|
+
messages.push({
|
|
176
|
+
role: 'assistant',
|
|
177
|
+
content: message.content || '',
|
|
178
|
+
tool_calls: message.tool_calls
|
|
179
|
+
});
|
|
180
|
+
// Check if AI wants to call tools
|
|
181
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
182
|
+
// Execute each tool call
|
|
183
|
+
for (const toolCall of message.tool_calls) {
|
|
184
|
+
if (toolCall.type === 'function') {
|
|
185
|
+
try {
|
|
186
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
187
|
+
const result = await executeMCPTool(toolCall.function.name, args);
|
|
188
|
+
// Add tool result to conversation
|
|
189
|
+
messages.push({
|
|
190
|
+
role: 'tool',
|
|
191
|
+
content: JSON.stringify(result),
|
|
192
|
+
tool_call_id: toolCall.id
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
// Add error result to conversation
|
|
197
|
+
messages.push({
|
|
198
|
+
role: 'tool',
|
|
199
|
+
content: `Error: ${error instanceof Error ? error.message : 'Tool execution failed'}`,
|
|
200
|
+
tool_call_id: toolCall.id
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Continue the conversation with tool results
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
// No tool calls, return the content
|
|
209
|
+
return message.content || '';
|
|
210
|
+
}
|
|
31
211
|
}
|
|
32
212
|
catch (error) {
|
|
33
213
|
if (error instanceof Error) {
|
|
@@ -36,31 +216,129 @@ export async function createChatCompletion(options) {
|
|
|
36
216
|
throw new Error('Chat completion failed: Unknown error');
|
|
37
217
|
}
|
|
38
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Simple streaming chat completion - only handles OpenAI interaction
|
|
221
|
+
* Tool execution should be handled by the caller
|
|
222
|
+
*/
|
|
39
223
|
export async function* createStreamingChatCompletion(options, abortSignal) {
|
|
40
224
|
const client = getOpenAIClient();
|
|
41
225
|
try {
|
|
42
226
|
const stream = await client.chat.completions.create({
|
|
43
227
|
model: options.model,
|
|
44
|
-
messages: options.messages,
|
|
228
|
+
messages: convertToOpenAIMessages(options.messages),
|
|
45
229
|
stream: true,
|
|
230
|
+
stream_options: { include_usage: true }, // Request usage data in stream
|
|
46
231
|
temperature: options.temperature || 0.7,
|
|
47
232
|
max_tokens: options.max_tokens,
|
|
233
|
+
tools: options.tools,
|
|
234
|
+
tool_choice: options.tool_choice,
|
|
48
235
|
}, {
|
|
49
236
|
signal: abortSignal,
|
|
50
237
|
});
|
|
238
|
+
let contentBuffer = '';
|
|
239
|
+
let toolCallsBuffer = {};
|
|
240
|
+
let hasToolCalls = false;
|
|
241
|
+
let usageData;
|
|
51
242
|
for await (const chunk of stream) {
|
|
52
243
|
if (abortSignal?.aborted) {
|
|
53
|
-
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Capture usage information if available (usually in the last chunk)
|
|
247
|
+
const usageValue = chunk.usage;
|
|
248
|
+
if (usageValue !== null && usageValue !== undefined) {
|
|
249
|
+
usageData = {
|
|
250
|
+
prompt_tokens: usageValue.prompt_tokens || 0,
|
|
251
|
+
completion_tokens: usageValue.completion_tokens || 0,
|
|
252
|
+
total_tokens: usageValue.total_tokens || 0
|
|
253
|
+
};
|
|
54
254
|
}
|
|
55
|
-
|
|
255
|
+
// Skip content processing if no choices (but usage is already captured above)
|
|
256
|
+
const choice = chunk.choices[0];
|
|
257
|
+
if (!choice) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
// Stream content chunks
|
|
261
|
+
const content = choice.delta?.content;
|
|
56
262
|
if (content) {
|
|
57
|
-
|
|
263
|
+
contentBuffer += content;
|
|
264
|
+
yield {
|
|
265
|
+
type: 'content',
|
|
266
|
+
content
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// Stream reasoning content (for o1 models, etc.)
|
|
270
|
+
// Note: reasoning_content is NOT included in the response, only counted for tokens
|
|
271
|
+
const reasoningContent = choice.delta?.reasoning_content;
|
|
272
|
+
if (reasoningContent) {
|
|
273
|
+
yield {
|
|
274
|
+
type: 'reasoning_delta',
|
|
275
|
+
delta: reasoningContent
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// Accumulate tool calls and stream deltas
|
|
279
|
+
const deltaToolCalls = choice.delta?.tool_calls;
|
|
280
|
+
if (deltaToolCalls) {
|
|
281
|
+
hasToolCalls = true;
|
|
282
|
+
for (const deltaCall of deltaToolCalls) {
|
|
283
|
+
const index = deltaCall.index ?? 0;
|
|
284
|
+
if (!toolCallsBuffer[index]) {
|
|
285
|
+
toolCallsBuffer[index] = {
|
|
286
|
+
id: '',
|
|
287
|
+
type: 'function',
|
|
288
|
+
function: {
|
|
289
|
+
name: '',
|
|
290
|
+
arguments: ''
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (deltaCall.id) {
|
|
295
|
+
toolCallsBuffer[index].id = deltaCall.id;
|
|
296
|
+
}
|
|
297
|
+
// Yield tool call deltas for token counting
|
|
298
|
+
let deltaText = '';
|
|
299
|
+
if (deltaCall.function?.name) {
|
|
300
|
+
toolCallsBuffer[index].function.name += deltaCall.function.name;
|
|
301
|
+
deltaText += deltaCall.function.name;
|
|
302
|
+
}
|
|
303
|
+
if (deltaCall.function?.arguments) {
|
|
304
|
+
toolCallsBuffer[index].function.arguments += deltaCall.function.arguments;
|
|
305
|
+
deltaText += deltaCall.function.arguments;
|
|
306
|
+
}
|
|
307
|
+
// Stream the delta to frontend for real-time token counting
|
|
308
|
+
if (deltaText) {
|
|
309
|
+
yield {
|
|
310
|
+
type: 'tool_call_delta',
|
|
311
|
+
delta: deltaText
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (choice.finish_reason) {
|
|
317
|
+
break;
|
|
58
318
|
}
|
|
59
319
|
}
|
|
320
|
+
// If there are tool calls, yield them
|
|
321
|
+
if (hasToolCalls) {
|
|
322
|
+
yield {
|
|
323
|
+
type: 'tool_calls',
|
|
324
|
+
tool_calls: Object.values(toolCallsBuffer)
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
// Yield usage information if available
|
|
328
|
+
if (usageData) {
|
|
329
|
+
yield {
|
|
330
|
+
type: 'usage',
|
|
331
|
+
usage: usageData
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
// Signal completion
|
|
335
|
+
yield {
|
|
336
|
+
type: 'done'
|
|
337
|
+
};
|
|
60
338
|
}
|
|
61
339
|
catch (error) {
|
|
62
340
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
63
|
-
return;
|
|
341
|
+
return;
|
|
64
342
|
}
|
|
65
343
|
if (error instanceof Error) {
|
|
66
344
|
throw new Error(`Streaming chat completion failed: ${error.message}`);
|
|
@@ -77,11 +355,16 @@ export function validateChatOptions(options) {
|
|
|
77
355
|
errors.push('At least one message is required');
|
|
78
356
|
}
|
|
79
357
|
for (const message of options.messages || []) {
|
|
80
|
-
if (!message.role || !['system', 'user', 'assistant'].includes(message.role)) {
|
|
358
|
+
if (!message.role || !['system', 'user', 'assistant', 'tool'].includes(message.role)) {
|
|
81
359
|
errors.push('Invalid message role');
|
|
82
360
|
}
|
|
83
|
-
|
|
84
|
-
|
|
361
|
+
// Tool messages must have tool_call_id
|
|
362
|
+
if (message.role === 'tool' && !message.tool_call_id) {
|
|
363
|
+
errors.push('Tool messages must have tool_call_id');
|
|
364
|
+
}
|
|
365
|
+
// Content can be empty for tool calls
|
|
366
|
+
if (message.role !== 'tool' && (!message.content || message.content.trim().length === 0)) {
|
|
367
|
+
errors.push('Message content cannot be empty (except for tool messages)');
|
|
85
368
|
}
|
|
86
369
|
}
|
|
87
370
|
return errors;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ChatMessage, ToolCall } from './chat.js';
|
|
2
|
+
export interface ResponseOptions {
|
|
3
|
+
model: string;
|
|
4
|
+
messages: ChatMessage[];
|
|
5
|
+
stream?: boolean;
|
|
6
|
+
temperature?: number;
|
|
7
|
+
max_tokens?: number;
|
|
8
|
+
tools?: Array<{
|
|
9
|
+
type: 'function';
|
|
10
|
+
function: {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
parameters?: Record<string, any>;
|
|
14
|
+
};
|
|
15
|
+
}>;
|
|
16
|
+
tool_choice?: 'auto' | 'none' | 'required';
|
|
17
|
+
reasoning?: {
|
|
18
|
+
summary?: 'auto' | 'none';
|
|
19
|
+
effort?: 'low' | 'medium' | 'high';
|
|
20
|
+
};
|
|
21
|
+
prompt_cache_key?: string;
|
|
22
|
+
store?: boolean;
|
|
23
|
+
include?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface UsageInfo {
|
|
26
|
+
prompt_tokens: number;
|
|
27
|
+
completion_tokens: number;
|
|
28
|
+
total_tokens: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ResponseStreamChunk {
|
|
31
|
+
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'reasoning_delta' | 'done' | 'usage';
|
|
32
|
+
content?: string;
|
|
33
|
+
tool_calls?: ToolCall[];
|
|
34
|
+
delta?: string;
|
|
35
|
+
usage?: UsageInfo;
|
|
36
|
+
}
|
|
37
|
+
export declare function resetOpenAIClient(): void;
|
|
38
|
+
/**
|
|
39
|
+
* 使用 Responses API 创建响应(非流式,带自动工具调用)
|
|
40
|
+
*/
|
|
41
|
+
export declare function createResponse(options: ResponseOptions): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* 使用 Responses API 创建流式响应(带自动工具调用)
|
|
44
|
+
*/
|
|
45
|
+
export declare function createStreamingResponse(options: ResponseOptions, abortSignal?: AbortSignal): AsyncGenerator<ResponseStreamChunk, void, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* 使用 Responses API 创建响应(限制工具调用轮数)
|
|
48
|
+
*/
|
|
49
|
+
export declare function createResponseWithTools(options: ResponseOptions, maxToolRounds?: number): Promise<{
|
|
50
|
+
content: string;
|
|
51
|
+
toolCalls: ToolCall[];
|
|
52
|
+
}>;
|