@userdaoo/iflow-api-bridge 0.1.1 → 0.1.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/README.md +107 -134
- package/dist/adapter.d.ts +15 -24
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +107 -45
- package/dist/adapter.js.map +1 -1
- package/dist/openai/transformer.d.ts +6 -31
- package/dist/openai/transformer.d.ts.map +1 -1
- package/dist/openai/transformer.js +28 -36
- package/dist/openai/transformer.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +30 -19
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter.ts +144 -46
- package/src/openai/transformer.ts +33 -37
- package/src/server.ts +46 -21
package/src/adapter.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* iFlow SDK 适配器(使用子进程模式)
|
|
3
|
-
* 封装与 iFlow CLI
|
|
3
|
+
* 封装与 iFlow CLI 的通信,支持对话上下文管理
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { spawn, type ChildProcess } from 'child_process';
|
|
@@ -27,24 +27,115 @@ export interface IFlowAdapterOptions {
|
|
|
27
27
|
apiKey?: string;
|
|
28
28
|
baseUrl?: string;
|
|
29
29
|
timeout?: number;
|
|
30
|
+
maxHistoryLength?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface Message {
|
|
34
|
+
role: 'user' | 'assistant' | 'system';
|
|
35
|
+
content: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Conversation {
|
|
39
|
+
messages: Message[];
|
|
40
|
+
lastAccessTime: number;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
|
-
/**
|
|
33
|
-
* iFlow 适配器 - 使用子进程模式
|
|
34
|
-
*/
|
|
35
43
|
export class IFlowAdapter {
|
|
36
44
|
private options: IFlowAdapterOptions;
|
|
37
|
-
private defaultTimeout =
|
|
45
|
+
private defaultTimeout = 300000;
|
|
46
|
+
private conversations: Map<string, Conversation> = new Map();
|
|
47
|
+
private readonly maxHistoryLength: number;
|
|
48
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
38
49
|
|
|
39
50
|
constructor(options: IFlowAdapterOptions = {}) {
|
|
40
51
|
this.options = options;
|
|
52
|
+
this.maxHistoryLength = options.maxHistoryLength || 20;
|
|
53
|
+
this.cleanupInterval = setInterval(() => {
|
|
54
|
+
this.cleanupExpiredConversations();
|
|
55
|
+
}, 30 * 60 * 1000);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private getConversation(conversationId: string): Conversation {
|
|
59
|
+
let conversation = this.conversations.get(conversationId);
|
|
60
|
+
if (!conversation) {
|
|
61
|
+
conversation = {
|
|
62
|
+
messages: [],
|
|
63
|
+
lastAccessTime: Date.now(),
|
|
64
|
+
};
|
|
65
|
+
this.conversations.set(conversationId, conversation);
|
|
66
|
+
} else {
|
|
67
|
+
conversation.lastAccessTime = Date.now();
|
|
68
|
+
}
|
|
69
|
+
return conversation;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
private buildPromptWithHistory(
|
|
73
|
+
conversationId: string,
|
|
74
|
+
systemMessage: string | undefined,
|
|
75
|
+
userMessage: string
|
|
76
|
+
): string {
|
|
77
|
+
const conversation = this.getConversation(conversationId);
|
|
78
|
+
const parts: string[] = [];
|
|
79
|
+
|
|
80
|
+
if (systemMessage) {
|
|
81
|
+
parts.push(`System: ${systemMessage}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const msg of conversation.messages) {
|
|
85
|
+
if (msg.role === 'user') {
|
|
86
|
+
parts.push(`User: ${msg.content}`);
|
|
87
|
+
} else if (msg.role === 'assistant') {
|
|
88
|
+
parts.push(`Assistant: ${msg.content}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
parts.push(`User: ${userMessage}`);
|
|
93
|
+
return parts.join('\n\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private saveMessage(conversationId: string, role: 'user' | 'assistant', content: string): void {
|
|
97
|
+
const conversation = this.getConversation(conversationId);
|
|
98
|
+
conversation.messages.push({ role, content });
|
|
99
|
+
|
|
100
|
+
if (conversation.messages.length > this.maxHistoryLength) {
|
|
101
|
+
const systemMessages = conversation.messages.filter(m => m.role === 'system');
|
|
102
|
+
const otherMessages = conversation.messages.filter(m => m.role !== 'system');
|
|
103
|
+
const keepCount = this.maxHistoryLength - systemMessages.length;
|
|
104
|
+
conversation.messages = [
|
|
105
|
+
...systemMessages,
|
|
106
|
+
...otherMessages.slice(-keepCount),
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
conversation.lastAccessTime = Date.now();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private cleanupExpiredConversations(): void {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const expireTime = 24 * 60 * 60 * 1000;
|
|
115
|
+
let cleaned = 0;
|
|
116
|
+
|
|
117
|
+
for (const [id, conversation] of this.conversations.entries()) {
|
|
118
|
+
if (now - conversation.lastAccessTime > expireTime) {
|
|
119
|
+
this.conversations.delete(id);
|
|
120
|
+
cleaned++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (cleaned > 0) {
|
|
125
|
+
console.log(`[Adapter] 清理了 ${cleaned} 个过期会话`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async sendMessage(
|
|
130
|
+
conversationId: string,
|
|
131
|
+
systemMessage: string | undefined,
|
|
132
|
+
userMessage: string
|
|
133
|
+
): Promise<IFlowResponse> {
|
|
134
|
+
const prompt = this.buildPromptWithHistory(conversationId, systemMessage, userMessage);
|
|
135
|
+
console.log(`[Adapter] 发送消息(非流式)会话: ${conversationId}`);
|
|
136
|
+
console.log(`[Adapter] 历史消息数: ${this.getConversation(conversationId).messages.length}`);
|
|
137
|
+
|
|
138
|
+
this.saveMessage(conversationId, 'user', userMessage);
|
|
48
139
|
|
|
49
140
|
return new Promise((resolve, reject) => {
|
|
50
141
|
const args = ['-p', prompt];
|
|
@@ -52,7 +143,7 @@ export class IFlowAdapter {
|
|
|
52
143
|
args.push('-m', this.options.model);
|
|
53
144
|
}
|
|
54
145
|
|
|
55
|
-
console.log('[Adapter] 启动 iflow:', args.join(' '));
|
|
146
|
+
console.log('[Adapter] 启动 iflow:', args.slice(0, 3).join(' ') + '...');
|
|
56
147
|
const child = spawn('iflow', args, {
|
|
57
148
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
58
149
|
});
|
|
@@ -82,11 +173,12 @@ export class IFlowAdapter {
|
|
|
82
173
|
console.log('[Adapter] iFlow 退出,code:', code);
|
|
83
174
|
|
|
84
175
|
if (code !== 0 && code !== null) {
|
|
85
|
-
console.error('[Adapter] iFlow stderr:', stderr);
|
|
176
|
+
console.error('[Adapter] iFlow stderr:', stderr.substring(0, 500));
|
|
86
177
|
}
|
|
87
178
|
|
|
88
|
-
// 解析响应
|
|
89
179
|
const content = this.parseResponse(stdout);
|
|
180
|
+
this.saveMessage(conversationId, 'assistant', content);
|
|
181
|
+
|
|
90
182
|
resolve({
|
|
91
183
|
content,
|
|
92
184
|
stopReason: code === 0 ? 'end_turn' : 'error',
|
|
@@ -95,24 +187,31 @@ export class IFlowAdapter {
|
|
|
95
187
|
});
|
|
96
188
|
}
|
|
97
189
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
190
|
+
async *sendMessageStream(
|
|
191
|
+
conversationId: string,
|
|
192
|
+
systemMessage: string | undefined,
|
|
193
|
+
userMessage: string
|
|
194
|
+
): AsyncGenerator<StreamChunk> {
|
|
195
|
+
const prompt = this.buildPromptWithHistory(conversationId, systemMessage, userMessage);
|
|
196
|
+
console.log(`[Adapter] 发送消息(流式)会话: ${conversationId}`);
|
|
197
|
+
console.log(`[Adapter] 历史消息数: ${this.getConversation(conversationId).messages.length}`);
|
|
198
|
+
|
|
199
|
+
this.saveMessage(conversationId, 'user', userMessage);
|
|
103
200
|
|
|
104
201
|
const args = ['-p', prompt];
|
|
105
202
|
if (this.options.model) {
|
|
106
203
|
args.push('-m', this.options.model);
|
|
107
204
|
}
|
|
108
205
|
|
|
109
|
-
console.log('[Adapter] 启动 iflow:', args.join(' '));
|
|
206
|
+
console.log('[Adapter] 启动 iflow:', args.slice(0, 3).join(' ') + '...');
|
|
110
207
|
const child = spawn('iflow', args, {
|
|
111
208
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
112
209
|
});
|
|
113
210
|
|
|
114
211
|
let buffer = '';
|
|
115
212
|
let isDone = false;
|
|
213
|
+
let fullContent = '';
|
|
214
|
+
|
|
116
215
|
const timeout = setTimeout(() => {
|
|
117
216
|
if (!isDone) {
|
|
118
217
|
child.kill('SIGTERM');
|
|
@@ -127,23 +226,20 @@ export class IFlowAdapter {
|
|
|
127
226
|
|
|
128
227
|
child.stderr?.on('data', (data) => {
|
|
129
228
|
const msg = data.toString();
|
|
130
|
-
console.log('[Adapter] iFlow stderr:', msg.trim());
|
|
229
|
+
console.log('[Adapter] iFlow stderr:', msg.trim().substring(0, 200));
|
|
131
230
|
});
|
|
132
231
|
|
|
133
|
-
// 模拟流式输出 - 逐字符发送
|
|
134
232
|
let lastSentIndex = 0;
|
|
135
233
|
while (!isDone) {
|
|
136
|
-
// 检查进程是否结束
|
|
137
234
|
if (child.exitCode !== null) {
|
|
138
235
|
isDone = true;
|
|
139
236
|
}
|
|
140
237
|
|
|
141
|
-
// 发送新内容
|
|
142
238
|
if (buffer.length > lastSentIndex) {
|
|
143
239
|
const newContent = buffer.slice(lastSentIndex);
|
|
144
240
|
lastSentIndex = buffer.length;
|
|
241
|
+
fullContent += newContent;
|
|
145
242
|
|
|
146
|
-
// 逐行或逐字符发送
|
|
147
243
|
const lines = newContent.split('\n');
|
|
148
244
|
for (const line of lines) {
|
|
149
245
|
if (line.trim()) {
|
|
@@ -162,28 +258,27 @@ export class IFlowAdapter {
|
|
|
162
258
|
|
|
163
259
|
clearTimeout(timeout);
|
|
164
260
|
|
|
165
|
-
// 发送剩余内容
|
|
166
261
|
if (buffer.length > lastSentIndex) {
|
|
262
|
+
const remaining = buffer.slice(lastSentIndex);
|
|
263
|
+
fullContent += remaining;
|
|
167
264
|
yield {
|
|
168
265
|
type: 'content',
|
|
169
|
-
content:
|
|
266
|
+
content: remaining,
|
|
170
267
|
};
|
|
171
268
|
}
|
|
172
269
|
|
|
270
|
+
const finalContent = this.parseResponse(fullContent);
|
|
271
|
+
this.saveMessage(conversationId, 'assistant', finalContent);
|
|
272
|
+
|
|
173
273
|
yield { type: 'done' };
|
|
174
274
|
}
|
|
175
275
|
|
|
176
|
-
/**
|
|
177
|
-
* 解析 iFlow 输出,提取实际回复内容
|
|
178
|
-
*/
|
|
179
276
|
private parseResponse(stdout: string): string {
|
|
180
|
-
// 尝试提取 <Execution Info> 之前的内容作为回复
|
|
181
277
|
const executionInfoMatch = stdout.match(/<Execution Info>[\s\S]*$/);
|
|
182
278
|
if (executionInfoMatch) {
|
|
183
279
|
return stdout.substring(0, executionInfoMatch.index).trim();
|
|
184
280
|
}
|
|
185
281
|
|
|
186
|
-
// 如果没有 Execution Info,返回全部内容(去掉开头的警告)
|
|
187
282
|
const lines = stdout.split('\n');
|
|
188
283
|
const startIndex = lines.findIndex((line) =>
|
|
189
284
|
!line.includes('DeprecationWarning') &&
|
|
@@ -198,30 +293,33 @@ export class IFlowAdapter {
|
|
|
198
293
|
return stdout.trim();
|
|
199
294
|
}
|
|
200
295
|
|
|
201
|
-
/**
|
|
202
|
-
* 连接到 iFlow
|
|
203
|
-
*/
|
|
204
296
|
async connect(): Promise<void> {
|
|
205
|
-
|
|
206
|
-
console.log('[Adapter] 子进程模式已就绪');
|
|
297
|
+
console.log('[Adapter] 子进程模式已就绪,支持上下文管理');
|
|
207
298
|
}
|
|
208
299
|
|
|
209
|
-
/**
|
|
210
|
-
* 断开连接
|
|
211
|
-
*/
|
|
212
300
|
disconnect(): void {
|
|
213
|
-
|
|
214
|
-
|
|
301
|
+
console.log('[Adapter] 子进程模式断开,清理资源...');
|
|
302
|
+
if (this.cleanupInterval) {
|
|
303
|
+
clearInterval(this.cleanupInterval);
|
|
304
|
+
this.cleanupInterval = null;
|
|
305
|
+
}
|
|
306
|
+
this.conversations.clear();
|
|
215
307
|
}
|
|
216
308
|
|
|
217
|
-
/**
|
|
218
|
-
* 检查是否已连接
|
|
219
|
-
*/
|
|
220
309
|
isConnected(): boolean {
|
|
221
|
-
// 子进程模式总是"已连接"
|
|
222
310
|
return true;
|
|
223
311
|
}
|
|
312
|
+
|
|
313
|
+
getStats(): { conversationCount: number; totalMessages: number } {
|
|
314
|
+
let totalMessages = 0;
|
|
315
|
+
for (const conv of this.conversations.values()) {
|
|
316
|
+
totalMessages += conv.messages.length;
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
conversationCount: this.conversations.size,
|
|
320
|
+
totalMessages,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
224
323
|
}
|
|
225
324
|
|
|
226
325
|
export default IFlowAdapter;
|
|
227
|
-
|
|
@@ -12,23 +12,14 @@ import type {
|
|
|
12
12
|
ToolCall,
|
|
13
13
|
} from './types.js';
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* 生成唯一 ID
|
|
17
|
-
*/
|
|
18
15
|
export function generateId(): string {
|
|
19
16
|
return `chatcmpl-${Date.now().toString(36)}${Math.random().toString(36).substring(2, 10)}`;
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
/**
|
|
23
|
-
* 获取当前时间戳
|
|
24
|
-
*/
|
|
25
19
|
export function getTimestamp(): number {
|
|
26
20
|
return Math.floor(Date.now() / 1000);
|
|
27
21
|
}
|
|
28
22
|
|
|
29
|
-
/**
|
|
30
|
-
* 将 OpenAI 消息格式转换为 iFlow 文本格式
|
|
31
|
-
*/
|
|
32
23
|
export function messagesToIFlowPrompt(messages: ChatCompletionMessage[]): string {
|
|
33
24
|
return messages.map(msg => {
|
|
34
25
|
if (typeof msg.content === 'string') {
|
|
@@ -38,9 +29,26 @@ export function messagesToIFlowPrompt(messages: ChatCompletionMessage[]): string
|
|
|
38
29
|
}).join('\n\n');
|
|
39
30
|
}
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
export function extractMessages(messages: ChatCompletionMessage[]): {
|
|
33
|
+
systemMessage: string | undefined;
|
|
34
|
+
userMessage: string;
|
|
35
|
+
} {
|
|
36
|
+
let systemMessage: string | undefined;
|
|
37
|
+
let userMessage = '';
|
|
38
|
+
|
|
39
|
+
for (const msg of messages) {
|
|
40
|
+
if (typeof msg.content !== 'string') continue;
|
|
41
|
+
|
|
42
|
+
if (msg.role === 'system') {
|
|
43
|
+
systemMessage = msg.content;
|
|
44
|
+
} else if (msg.role === 'user') {
|
|
45
|
+
userMessage = msg.content;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { systemMessage, userMessage };
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
export function createStreamChunk(
|
|
45
53
|
id: string,
|
|
46
54
|
model: string,
|
|
@@ -62,9 +70,6 @@ export function createStreamChunk(
|
|
|
62
70
|
};
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
/**
|
|
66
|
-
* 创建完整响应
|
|
67
|
-
*/
|
|
68
73
|
export function createCompletionResponse(
|
|
69
74
|
id: string,
|
|
70
75
|
model: string,
|
|
@@ -91,17 +96,10 @@ export function createCompletionResponse(
|
|
|
91
96
|
};
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
/**
|
|
95
|
-
* 估算 token 数量(简化版)
|
|
96
|
-
*/
|
|
97
99
|
export function estimateTokens(text: string): number {
|
|
98
|
-
// 简化估算:假设平均每 4 个字符一个 token
|
|
99
100
|
return Math.ceil(text.length / 4);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
/**
|
|
103
|
-
* 计算使用量
|
|
104
|
-
*/
|
|
105
103
|
export function calculateUsage(prompt: string, completion: string): UsageInfo {
|
|
106
104
|
const promptTokens = estimateTokens(prompt);
|
|
107
105
|
const completionTokens = estimateTokens(completion);
|
|
@@ -113,30 +111,28 @@ export function calculateUsage(prompt: string, completion: string): UsageInfo {
|
|
|
113
111
|
};
|
|
114
112
|
}
|
|
115
113
|
|
|
116
|
-
/**
|
|
117
|
-
* SSE 格式化
|
|
118
|
-
*/
|
|
119
114
|
export function formatSSE(data: unknown): string {
|
|
120
115
|
return `data: ${JSON.stringify(data)}\n\n`;
|
|
121
116
|
}
|
|
122
117
|
|
|
123
|
-
/**
|
|
124
|
-
* SSE 结束标记
|
|
125
|
-
*/
|
|
126
118
|
export const SSE_DONE = 'data: [DONE]\n\n';
|
|
127
119
|
|
|
128
120
|
/**
|
|
129
|
-
* 支持的模型列表
|
|
121
|
+
* iFlow 支持的模型列表
|
|
122
|
+
* 基于用户实际的 iflow CLI 配置
|
|
130
123
|
*/
|
|
131
124
|
export const AVAILABLE_MODELS = [
|
|
132
|
-
{ id: '
|
|
133
|
-
{ id: 'iflow-
|
|
134
|
-
{ id: '
|
|
125
|
+
{ id: 'glm-4.7', name: 'GLM-4.7 (Default)' },
|
|
126
|
+
{ id: 'iflow-rome-30ba3b', name: 'iFlow-ROME-30BA3B (Preview)' },
|
|
127
|
+
{ id: 'deepseek-v3.2', name: 'DeepSeek-V3.2' },
|
|
128
|
+
{ id: 'glm-5', name: 'GLM-5' },
|
|
129
|
+
{ id: 'qwen3-coder-plus', name: 'Qwen3-Coder-Plus' },
|
|
130
|
+
{ id: 'kimi-k2-thinking', name: 'Kimi-K2-Thinking' },
|
|
131
|
+
{ id: 'minimax-m2.5', name: 'MiniMax-M2.5' },
|
|
132
|
+
{ id: 'kimi-k2.5', name: 'Kimi-K2.5' },
|
|
133
|
+
{ id: 'kimi-k2-0905', name: 'Kimi-K2-0905' },
|
|
135
134
|
];
|
|
136
135
|
|
|
137
|
-
/**
|
|
138
|
-
* 获取默认模型 ID
|
|
139
|
-
*/
|
|
140
136
|
export function getDefaultModel(): string {
|
|
141
|
-
return '
|
|
142
|
-
}
|
|
137
|
+
return 'glm-4.7';
|
|
138
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
generateId,
|
|
17
17
|
getTimestamp,
|
|
18
|
-
|
|
18
|
+
extractMessages,
|
|
19
19
|
createCompletionResponse,
|
|
20
20
|
createStreamChunk,
|
|
21
21
|
calculateUsage,
|
|
@@ -43,6 +43,7 @@ export class IFlowAPIServer {
|
|
|
43
43
|
this.app = express();
|
|
44
44
|
this.adapter = new IFlowAdapter({
|
|
45
45
|
model: options.model,
|
|
46
|
+
maxHistoryLength: 20,
|
|
46
47
|
});
|
|
47
48
|
|
|
48
49
|
this.setupMiddleware();
|
|
@@ -95,9 +96,12 @@ export class IFlowAPIServer {
|
|
|
95
96
|
private setupRoutes(): void {
|
|
96
97
|
// 健康检查
|
|
97
98
|
this.app.get('/health', (req: Request, res: Response) => {
|
|
99
|
+
const stats = this.adapter.getStats();
|
|
98
100
|
res.json({
|
|
99
101
|
status: 'ok',
|
|
100
102
|
connected: this.adapter.isConnected(),
|
|
103
|
+
conversations: stats.conversationCount,
|
|
104
|
+
totalMessages: stats.totalMessages,
|
|
101
105
|
timestamp: new Date().toISOString(),
|
|
102
106
|
});
|
|
103
107
|
});
|
|
@@ -119,7 +123,6 @@ export class IFlowAPIServer {
|
|
|
119
123
|
// 聊天完成
|
|
120
124
|
this.app.post('/v1/chat/completions', async (req: Request, res: Response) => {
|
|
121
125
|
console.log(`[${new Date().toISOString()}] 收到聊天请求`);
|
|
122
|
-
console.log('请求体:', JSON.stringify(req.body, null, 2));
|
|
123
126
|
|
|
124
127
|
try {
|
|
125
128
|
const body = req.body as ChatCompletionRequest;
|
|
@@ -133,18 +136,28 @@ export class IFlowAPIServer {
|
|
|
133
136
|
const isStream = body.stream === true;
|
|
134
137
|
const model = body.model || getDefaultModel();
|
|
135
138
|
|
|
136
|
-
|
|
139
|
+
// 提取 system 和 user 消息
|
|
140
|
+
const { systemMessage, userMessage } = extractMessages(body.messages);
|
|
141
|
+
|
|
142
|
+
if (!userMessage) {
|
|
143
|
+
return res.status(400).json(this.createError('未找到 user 消息', 'invalid_request_error'));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 生成或使用已有的 conversation ID
|
|
147
|
+
// opencode 可能在 header 中传递,或者我们需要生成一个
|
|
148
|
+
let conversationId = req.headers['x-conversation-id'] as string | undefined;
|
|
149
|
+
if (!conversationId) {
|
|
150
|
+
// 从消息中生成一个稳定的 ID(基于 system + user 消息)
|
|
151
|
+
conversationId = `${Date.now()}-${Math.random().toString(36).substring(2, 10)}`;
|
|
152
|
+
}
|
|
137
153
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
console.log('提示词:', prompt.substring(0, 100) + '...');
|
|
154
|
+
console.log(`会话: ${conversationId}, 流式: ${isStream}, 模型: ${model}`);
|
|
155
|
+
console.log(`系统消息: ${systemMessage ? '有' : '无'}, 用户消息: ${userMessage.substring(0, 50)}...`);
|
|
141
156
|
|
|
142
157
|
if (isStream) {
|
|
143
|
-
|
|
144
|
-
await this.handleStreamResponse(res, prompt, model);
|
|
158
|
+
await this.handleStreamResponse(res, conversationId, systemMessage, userMessage, model);
|
|
145
159
|
} else {
|
|
146
|
-
|
|
147
|
-
await this.handleNonStreamResponse(res, prompt, model);
|
|
160
|
+
await this.handleNonStreamResponse(res, conversationId, systemMessage, userMessage, model);
|
|
148
161
|
}
|
|
149
162
|
} catch (error) {
|
|
150
163
|
console.error('处理请求错误:', error);
|
|
@@ -156,18 +169,24 @@ export class IFlowAPIServer {
|
|
|
156
169
|
/**
|
|
157
170
|
* 处理流式响应
|
|
158
171
|
*/
|
|
159
|
-
private async handleStreamResponse(
|
|
172
|
+
private async handleStreamResponse(
|
|
173
|
+
res: Response,
|
|
174
|
+
conversationId: string,
|
|
175
|
+
systemMessage: string | undefined,
|
|
176
|
+
userMessage: string,
|
|
177
|
+
model: string
|
|
178
|
+
): Promise<void> {
|
|
160
179
|
const id = generateId();
|
|
161
180
|
|
|
162
181
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
163
182
|
res.setHeader('Cache-Control', 'no-cache');
|
|
164
183
|
res.setHeader('Connection', 'keep-alive');
|
|
165
184
|
|
|
166
|
-
// 设置
|
|
185
|
+
// 设置 5 分钟超时
|
|
167
186
|
const timeout = setTimeout(() => {
|
|
168
187
|
res.write(formatSSE(this.createError('请求超时', 'timeout_error')));
|
|
169
188
|
res.end();
|
|
170
|
-
},
|
|
189
|
+
}, 300000);
|
|
171
190
|
|
|
172
191
|
try {
|
|
173
192
|
// 发送开始标记(角色)
|
|
@@ -187,7 +206,7 @@ export class IFlowAPIServer {
|
|
|
187
206
|
// 流式发送内容
|
|
188
207
|
let fullContent = '';
|
|
189
208
|
let isDone = false;
|
|
190
|
-
for await (const chunk of this.adapter.sendMessageStream(
|
|
209
|
+
for await (const chunk of this.adapter.sendMessageStream(conversationId, systemMessage, userMessage)) {
|
|
191
210
|
if (isDone) break;
|
|
192
211
|
|
|
193
212
|
switch (chunk.type) {
|
|
@@ -205,7 +224,6 @@ export class IFlowAPIServer {
|
|
|
205
224
|
break;
|
|
206
225
|
|
|
207
226
|
case 'done':
|
|
208
|
-
// 收到完成信号,标记结束
|
|
209
227
|
isDone = true;
|
|
210
228
|
break;
|
|
211
229
|
|
|
@@ -238,19 +256,25 @@ export class IFlowAPIServer {
|
|
|
238
256
|
/**
|
|
239
257
|
* 处理非流式响应
|
|
240
258
|
*/
|
|
241
|
-
private async handleNonStreamResponse(
|
|
259
|
+
private async handleNonStreamResponse(
|
|
260
|
+
res: Response,
|
|
261
|
+
conversationId: string,
|
|
262
|
+
systemMessage: string | undefined,
|
|
263
|
+
userMessage: string,
|
|
264
|
+
model: string
|
|
265
|
+
): Promise<void> {
|
|
242
266
|
const id = generateId();
|
|
243
267
|
|
|
244
|
-
// 设置
|
|
268
|
+
// 设置 5 分钟超时
|
|
245
269
|
const timeout = setTimeout(() => {
|
|
246
270
|
res.status(504).json(this.createError('请求超时', 'timeout_error'));
|
|
247
|
-
},
|
|
271
|
+
}, 300000);
|
|
248
272
|
|
|
249
273
|
try {
|
|
250
|
-
const response = await this.adapter.sendMessage(
|
|
274
|
+
const response = await this.adapter.sendMessage(conversationId, systemMessage, userMessage);
|
|
251
275
|
clearTimeout(timeout);
|
|
252
276
|
|
|
253
|
-
const usage = calculateUsage(
|
|
277
|
+
const usage = calculateUsage(userMessage, response.content);
|
|
254
278
|
|
|
255
279
|
const completionResponse = createCompletionResponse(
|
|
256
280
|
id,
|
|
@@ -317,6 +341,7 @@ export class IFlowAPIServer {
|
|
|
317
341
|
console.log(`\n🚀 iFlow API 桥接服务已启动`);
|
|
318
342
|
console.log(`📍 服务地址: http://${host}:${port}`);
|
|
319
343
|
console.log(`🤖 使用模型: ${model}`);
|
|
344
|
+
console.log(`💬 支持上下文管理: 是`);
|
|
320
345
|
console.log(`🔗 OpenAI API: http://${host}:${port}/v1/chat/completions`);
|
|
321
346
|
console.log(`📋 模型列表: http://${host}:${port}/v1/models`);
|
|
322
347
|
console.log(`❤️ 健康检查: http://${host}:${port}/health`);
|
|
@@ -338,4 +363,4 @@ export class IFlowAPIServer {
|
|
|
338
363
|
async stop(): Promise<void> {
|
|
339
364
|
await this.adapter.disconnect();
|
|
340
365
|
}
|
|
341
|
-
}
|
|
366
|
+
}
|