@xalia/agent 0.6.10 → 0.6.11
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 +5 -2
- package/.env.development +0 -6
- package/.env.test +0 -7
- package/.prettierrc.json +0 -11
- package/context_system.md +0 -498
- package/eslint.config.mjs +0 -38
- package/scripts/chat_server +0 -8
- package/scripts/git_message +0 -31
- package/scripts/git_wip +0 -21
- package/scripts/pr_message +0 -18
- package/scripts/pr_review +0 -16
- package/scripts/setup_chat +0 -90
- package/scripts/shutdown_chat_server +0 -42
- package/scripts/start_chat_server +0 -24
- package/scripts/sudomcp_import +0 -23
- package/scripts/test_chat +0 -327
- package/src/agent/agent.ts +0 -699
- package/src/agent/agentUtils.ts +0 -286
- package/src/agent/compressingContextManager.ts +0 -129
- package/src/agent/context.ts +0 -265
- package/src/agent/contextWithWorkspace.ts +0 -162
- package/src/agent/documentSummarizer.ts +0 -157
- package/src/agent/dummyLLM.ts +0 -130
- package/src/agent/iAgentEventHandler.ts +0 -64
- package/src/agent/imageGenLLM.ts +0 -101
- package/src/agent/imageGenerator.ts +0 -45
- package/src/agent/iplatform.ts +0 -18
- package/src/agent/llm.ts +0 -74
- package/src/agent/mcpServerManager.ts +0 -541
- package/src/agent/nullAgentEventHandler.ts +0 -26
- package/src/agent/nullPlatform.ts +0 -13
- package/src/agent/openAI.ts +0 -123
- package/src/agent/openAILLM.ts +0 -99
- package/src/agent/openAILLMStreaming.ts +0 -648
- package/src/agent/promptProvider.ts +0 -87
- package/src/agent/repeatLLM.ts +0 -62
- package/src/agent/sudoMcpServerManager.ts +0 -361
- package/src/agent/test_data/harrypotter.txt +0 -6065
- package/src/agent/tokenAuth.ts +0 -50
- package/src/agent/tokenCounter.test.ts +0 -243
- package/src/agent/tokenCounter.ts +0 -483
- package/src/agent/toolSettings.ts +0 -24
- package/src/agent/tools/calculatorTool.ts +0 -50
- package/src/agent/tools/contentExtractors/htmlToText.ts +0 -61
- package/src/agent/tools/contentExtractors/pdfToText.ts +0 -60
- package/src/agent/tools/datetimeTool.ts +0 -41
- package/src/agent/tools/fileManager/fileManagerTool.ts +0 -199
- package/src/agent/tools/fileManager/index.ts +0 -50
- package/src/agent/tools/fileManager/memoryFileManager.ts +0 -120
- package/src/agent/tools/fileManager/mimeTypes.ts +0 -60
- package/src/agent/tools/fileManager/prompt.ts +0 -38
- package/src/agent/tools/fileManager/types.ts +0 -189
- package/src/agent/tools/index.ts +0 -49
- package/src/agent/tools/openUrlTool.ts +0 -62
- package/src/agent/tools/renderTool.ts +0 -92
- package/src/agent/tools/utils.ts +0 -74
- package/src/agent/tools/webSearch.ts +0 -138
- package/src/agent/tools/webSearchTool.ts +0 -44
- package/src/chat/client/chatClient.ts +0 -967
- package/src/chat/client/connection.test.ts +0 -241
- package/src/chat/client/connection.ts +0 -286
- package/src/chat/client/constants.ts +0 -1
- package/src/chat/client/index.ts +0 -21
- package/src/chat/client/interfaces.ts +0 -34
- package/src/chat/client/sessionClient.ts +0 -574
- package/src/chat/client/sessionFiles.ts +0 -142
- package/src/chat/client/teamManager.ts +0 -29
- package/src/chat/constants.ts +0 -6
- package/src/chat/data/apiKeyManager.ts +0 -76
- package/src/chat/data/dataModels.ts +0 -107
- package/src/chat/data/database.ts +0 -997
- package/src/chat/data/dbMcpServerConfigs.ts +0 -59
- package/src/chat/data/dbSessionFiles.ts +0 -107
- package/src/chat/data/dbSessionMessages.ts +0 -102
- package/src/chat/protocol/connectionMessages.ts +0 -49
- package/src/chat/protocol/constants.ts +0 -55
- package/src/chat/protocol/errors.ts +0 -16
- package/src/chat/protocol/messages.ts +0 -899
- package/src/chat/server/README.md +0 -127
- package/src/chat/server/chatContextManager.ts +0 -660
- package/src/chat/server/connectionManager.test.ts +0 -246
- package/src/chat/server/connectionManager.ts +0 -506
- package/src/chat/server/conversation.ts +0 -319
- package/src/chat/server/errorUtils.ts +0 -28
- package/src/chat/server/imageGeneratorTools.ts +0 -179
- package/src/chat/server/openAIRouterLLM.ts +0 -168
- package/src/chat/server/openSession.ts +0 -1945
- package/src/chat/server/openSessionMessageSender.ts +0 -4
- package/src/chat/server/promptRefiner.ts +0 -106
- package/src/chat/server/server.ts +0 -178
- package/src/chat/server/sessionFileManager.ts +0 -151
- package/src/chat/server/sessionRegistry.test.ts +0 -137
- package/src/chat/server/sessionRegistry.ts +0 -1553
- package/src/chat/server/test-utils/mockFactories.ts +0 -422
- package/src/chat/server/titleGenerator.test.ts +0 -103
- package/src/chat/server/titleGenerator.ts +0 -143
- package/src/chat/server/tools.ts +0 -170
- package/src/chat/utils/agentSessionMap.ts +0 -76
- package/src/chat/utils/approvalManager.ts +0 -189
- package/src/chat/utils/asyncLock.ts +0 -43
- package/src/chat/utils/asyncQueue.ts +0 -62
- package/src/chat/utils/multiAsyncQueue.ts +0 -66
- package/src/chat/utils/responseAwaiter.ts +0 -181
- package/src/chat/utils/userResolver.ts +0 -48
- package/src/chat/utils/websocket.ts +0 -16
- package/src/index.ts +0 -0
- package/src/test/agent.test.ts +0 -584
- package/src/test/approvalManager.test.ts +0 -141
- package/src/test/chatContextManager.test.ts +0 -552
- package/src/test/clientServerConnection.test.ts +0 -205
- package/src/test/compressingContextManager.test.ts +0 -77
- package/src/test/context.test.ts +0 -150
- package/src/test/contextTestTools.ts +0 -95
- package/src/test/conversation.test.ts +0 -109
- package/src/test/db.test.ts +0 -363
- package/src/test/dbMcpServerConfigs.test.ts +0 -112
- package/src/test/dbSessionFiles.test.ts +0 -258
- package/src/test/dbSessionMessages.test.ts +0 -85
- package/src/test/dbTestTools.ts +0 -157
- package/src/test/imageLoad.test.ts +0 -15
- package/src/test/mcpServerManager.test.ts +0 -114
- package/src/test/multiAsyncQueue.test.ts +0 -183
- package/src/test/openaiStreaming.test.ts +0 -177
- package/src/test/prompt.test.ts +0 -27
- package/src/test/promptProvider.test.ts +0 -33
- package/src/test/responseAwaiter.test.ts +0 -103
- package/src/test/sudoMcpServerManager.test.ts +0 -63
- package/src/test/testTools.ts +0 -176
- package/src/test/tools.test.ts +0 -64
- package/src/tool/agentChat.ts +0 -203
- package/src/tool/agentMain.ts +0 -180
- package/src/tool/chatMain.ts +0 -621
- package/src/tool/commandPrompt.ts +0 -264
- package/src/tool/files.ts +0 -82
- package/src/tool/main.ts +0 -25
- package/src/tool/nodePlatform.ts +0 -73
- package/src/tool/options.ts +0 -144
- package/src/tool/prompt.ts +0 -101
- package/test_data/background_test_profile.json +0 -6
- package/test_data/background_test_script.json +0 -11
- package/test_data/dummyllm_script_crash.json +0 -32
- package/test_data/dummyllm_script_image_gen.json +0 -19
- package/test_data/dummyllm_script_image_gen_fe.json +0 -29
- package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
- package/test_data/dummyllm_script_render_tool.json +0 -29
- package/test_data/dummyllm_script_simplecalc.json +0 -28
- package/test_data/dummyllm_script_test_auto_approve.json +0 -81
- package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
- package/test_data/frog.png +0 -0
- package/test_data/frog.png.b64 +0 -1
- package/test_data/git_message_profile.json +0 -4
- package/test_data/git_wip_system.txt +0 -5
- package/test_data/image_gen_test_profile.json +0 -5
- package/test_data/pr_message_profile.json +0 -4
- package/test_data/pr_review_profile.json +0 -4
- package/test_data/prompt_simplecalc.txt +0 -1
- package/test_data/simplecalc_profile.json +0 -4
- package/test_data/sudomcp_import_profile.json +0 -4
- package/test_data/test_script_profile.json +0 -8
- package/tsconfig.json +0 -13
- package/vitest.config.ts +0 -39
|
@@ -1,648 +0,0 @@
|
|
|
1
|
-
import { OpenAI } from "openai";
|
|
2
|
-
import { strict as assert } from "assert";
|
|
3
|
-
|
|
4
|
-
import { getLogger } from "@xalia/xmcp/sdk";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
ILLM,
|
|
8
|
-
Message,
|
|
9
|
-
XALIA_APP_HEADER,
|
|
10
|
-
MessageToolCall,
|
|
11
|
-
Choice,
|
|
12
|
-
Completion,
|
|
13
|
-
MessageParam,
|
|
14
|
-
ToolDescriptor,
|
|
15
|
-
} from "./llm";
|
|
16
|
-
import {
|
|
17
|
-
Reasoning,
|
|
18
|
-
ChatCompletionChunkChoiceDeltaWithReasoning,
|
|
19
|
-
choiceDeltaExtractReasoning,
|
|
20
|
-
} from "./openAI";
|
|
21
|
-
|
|
22
|
-
const logger = getLogger();
|
|
23
|
-
|
|
24
|
-
function initialToolCallFunction(
|
|
25
|
-
deltaFn:
|
|
26
|
-
| OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall.Function
|
|
27
|
-
| undefined
|
|
28
|
-
): OpenAI.Chat.Completions.ChatCompletionMessageFunctionToolCall.Function {
|
|
29
|
-
// export interface ChatCompletionChunk.Choice.Delta.ToolCall.Function {
|
|
30
|
-
// arguments?: string;
|
|
31
|
-
// name?: string;
|
|
32
|
-
// }
|
|
33
|
-
//
|
|
34
|
-
// ->
|
|
35
|
-
//
|
|
36
|
-
// export interface Function {
|
|
37
|
-
// arguments: string;
|
|
38
|
-
// name: string;
|
|
39
|
-
// }
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
arguments: deltaFn?.arguments || "",
|
|
43
|
-
name: deltaFn?.name || "",
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function updateToolCallFunction(
|
|
48
|
-
existingFn: OpenAI.Chat.Completions.ChatCompletionMessageFunctionToolCall.Function, // eslint-disable-line
|
|
49
|
-
deltaFn: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall.Function // eslint-disable-line
|
|
50
|
-
) {
|
|
51
|
-
// export interface ChatCompletionChunk.Choice.Delta.ToolCall.Function {
|
|
52
|
-
// arguments?: string;
|
|
53
|
-
// name?: string;
|
|
54
|
-
// }
|
|
55
|
-
// ->
|
|
56
|
-
// export interface Function {
|
|
57
|
-
// arguments: string;
|
|
58
|
-
// name: string;
|
|
59
|
-
// }
|
|
60
|
-
|
|
61
|
-
// The function can have either (or possibly both) field(s) empty.
|
|
62
|
-
// `arguments` has been observered to arrive in chunks. The same is
|
|
63
|
-
// probably true of `name`.
|
|
64
|
-
|
|
65
|
-
if (deltaFn.name) {
|
|
66
|
-
existingFn.name += deltaFn.name;
|
|
67
|
-
}
|
|
68
|
-
if (deltaFn.arguments) {
|
|
69
|
-
existingFn.arguments += deltaFn.arguments;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function initialToolCall(
|
|
74
|
-
delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall
|
|
75
|
-
): MessageToolCall {
|
|
76
|
-
return {
|
|
77
|
-
id: delta.id || "",
|
|
78
|
-
function: initialToolCallFunction(delta.function),
|
|
79
|
-
type: "function",
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function updateToolCall(
|
|
84
|
-
existing: MessageToolCall,
|
|
85
|
-
delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall
|
|
86
|
-
) {
|
|
87
|
-
// export interface ChatCompletionChunk.Choice.Delta.ToolCall {
|
|
88
|
-
// index: number;
|
|
89
|
-
// id?: string;
|
|
90
|
-
// function?: ToolCall.Function;
|
|
91
|
-
// type?: 'function';
|
|
92
|
-
// }
|
|
93
|
-
//
|
|
94
|
-
// ->
|
|
95
|
-
//
|
|
96
|
-
// export interface ChatCompletionMessageToolCall {
|
|
97
|
-
// id: string;
|
|
98
|
-
// function: ChatCompletionMessageToolCall.Function;
|
|
99
|
-
// type: 'function';
|
|
100
|
-
// }
|
|
101
|
-
|
|
102
|
-
if (delta.id) {
|
|
103
|
-
if (existing.id.length > 0) {
|
|
104
|
-
assert(delta.id == existing.id);
|
|
105
|
-
} else {
|
|
106
|
-
existing.id = delta.id;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (delta.function) {
|
|
111
|
-
updateToolCallFunction(existing.function, delta.function);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (delta.type) {
|
|
115
|
-
assert((delta.type = "function"));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function updateToolCalls(
|
|
120
|
-
toolCalls: MessageToolCall[] | undefined,
|
|
121
|
-
deltaToolCall: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall // eslint-disable-line
|
|
122
|
-
): MessageToolCall[] {
|
|
123
|
-
// export interface ChatCompletionChunk.Choice.Delta.ToolCall {
|
|
124
|
-
// index: number;
|
|
125
|
-
// id?: string;
|
|
126
|
-
// function?: ToolCall.Function;
|
|
127
|
-
// type?: 'function';
|
|
128
|
-
// }
|
|
129
|
-
//
|
|
130
|
-
// ->
|
|
131
|
-
//
|
|
132
|
-
// export interface ChatCompletionMessageToolCall {
|
|
133
|
-
// id: string;
|
|
134
|
-
// function: ChatCompletionMessageToolCall.Function;
|
|
135
|
-
// type: 'function';
|
|
136
|
-
// }
|
|
137
|
-
|
|
138
|
-
// The delta can arrive with any or none of the given fields. Only `index`
|
|
139
|
-
// can be relied upon.
|
|
140
|
-
|
|
141
|
-
if (typeof toolCalls === "undefined") {
|
|
142
|
-
toolCalls = [];
|
|
143
|
-
}
|
|
144
|
-
if (
|
|
145
|
-
deltaToolCall.index >= toolCalls.length ||
|
|
146
|
-
!toolCalls[deltaToolCall.index]
|
|
147
|
-
) {
|
|
148
|
-
toolCalls[deltaToolCall.index] = initialToolCall(deltaToolCall);
|
|
149
|
-
} else {
|
|
150
|
-
updateToolCall(toolCalls[deltaToolCall.index], deltaToolCall);
|
|
151
|
-
}
|
|
152
|
-
return toolCalls;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function initializeCompletionMessage(
|
|
156
|
-
delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta
|
|
157
|
-
): Message {
|
|
158
|
-
assert(delta.role === undefined || delta.role == "assistant");
|
|
159
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
160
|
-
assert(!delta.function_call);
|
|
161
|
-
|
|
162
|
-
// export interface ChatCompletionChunk.Choice.Delta {
|
|
163
|
-
// content?: string | null;
|
|
164
|
-
// function_call?: Delta.FunctionCall;
|
|
165
|
-
// refusal?: string | null;
|
|
166
|
-
// role?: 'developer' | 'system' | 'user' | 'assistant' | 'tool';
|
|
167
|
-
// tool_calls?: Array<Delta.ToolCall>;
|
|
168
|
-
// }
|
|
169
|
-
//
|
|
170
|
-
// ->
|
|
171
|
-
//
|
|
172
|
-
// export interface ChatCompletionMessage {
|
|
173
|
-
// content: string | null;
|
|
174
|
-
// refusal: string | null;
|
|
175
|
-
// role: 'assistant';
|
|
176
|
-
// annotations?: Array<ChatCompletionMessage.Annotation>;
|
|
177
|
-
// audio?: ChatCompletionAudio | null;
|
|
178
|
-
// function_call?: ChatCompletionMessage.FunctionCall | null;
|
|
179
|
-
// tool_calls?: Array<ChatCompletionMessageToolCall>;
|
|
180
|
-
// }
|
|
181
|
-
|
|
182
|
-
let toolCalls: MessageToolCall[] | undefined = undefined;
|
|
183
|
-
if (delta.tool_calls) {
|
|
184
|
-
for (const t of delta.tool_calls) {
|
|
185
|
-
toolCalls = updateToolCalls(toolCalls, t);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
content: delta.content || null,
|
|
191
|
-
refusal: delta.refusal || null,
|
|
192
|
-
role: "assistant",
|
|
193
|
-
// annotations?: Array<ChatCompletionMessage.Annotation>;
|
|
194
|
-
// audio?: ChatCompletionAudio | null;
|
|
195
|
-
// function_call: delta.function_call,
|
|
196
|
-
tool_calls: toolCalls,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function updateReasoning(message: Message, reasoning: string) {
|
|
201
|
-
if (!message.reasoning) {
|
|
202
|
-
message.reasoning = reasoning;
|
|
203
|
-
} else {
|
|
204
|
-
message.reasoning += reasoning;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function updateCompletionMessage(
|
|
209
|
-
message: Message,
|
|
210
|
-
delta: ChatCompletionChunkChoiceDeltaWithReasoning
|
|
211
|
-
) {
|
|
212
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
213
|
-
assert(message.role === "assistant");
|
|
214
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
215
|
-
assert(!message.function_call);
|
|
216
|
-
assert(!message.audio);
|
|
217
|
-
assert(
|
|
218
|
-
message.tool_calls instanceof Array ||
|
|
219
|
-
typeof message.tool_calls === "undefined"
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
// export interface ChatCompletionChunkChoiceDeltaWithReasoning {
|
|
223
|
-
// content?: string | null;
|
|
224
|
-
// function_call?: Delta.FunctionCall;
|
|
225
|
-
// refusal?: string | null;
|
|
226
|
-
// role?: 'developer' | 'system' | 'user' | 'assistant' | 'tool';
|
|
227
|
-
// tool_calls?: Array<Delta.ToolCall>;
|
|
228
|
-
//
|
|
229
|
-
// reasoning?: string;
|
|
230
|
-
// reasoning_details?: {
|
|
231
|
-
// type: "reasoning.text",
|
|
232
|
-
// text?: string,
|
|
233
|
-
// format?: string,
|
|
234
|
-
// index?:0
|
|
235
|
-
// }[]
|
|
236
|
-
// }
|
|
237
|
-
//
|
|
238
|
-
// ->
|
|
239
|
-
//
|
|
240
|
-
// export interface ChatCompletionMessage {
|
|
241
|
-
// content: string | null;
|
|
242
|
-
// refusal: string | null;
|
|
243
|
-
// role: 'assistant';
|
|
244
|
-
// annotations?: Array<ChatCompletionMessage.Annotation>;
|
|
245
|
-
// audio?: ChatCompletionAudio | null;
|
|
246
|
-
// function_call?: ChatCompletionMessage.FunctionCall | null;
|
|
247
|
-
// tool_calls?: Array<ChatCompletionMessageToolCall>;
|
|
248
|
-
//
|
|
249
|
-
// reasoning?: string;
|
|
250
|
-
// }
|
|
251
|
-
|
|
252
|
-
if (delta.content) {
|
|
253
|
-
if (message.content) {
|
|
254
|
-
message.content += delta.content;
|
|
255
|
-
} else {
|
|
256
|
-
message.content = delta.content;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
260
|
-
assert(!delta.function_call);
|
|
261
|
-
if (delta.refusal) {
|
|
262
|
-
if (message.refusal) {
|
|
263
|
-
message.refusal += delta.refusal;
|
|
264
|
-
} else {
|
|
265
|
-
message.refusal = delta.refusal;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
assert(delta.role === undefined || delta.role == "assistant");
|
|
269
|
-
if (delta.tool_calls) {
|
|
270
|
-
for (const t of delta.tool_calls) {
|
|
271
|
-
message.tool_calls = updateToolCalls(message.tool_calls, t);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const reasoning = choiceDeltaExtractReasoning(delta);
|
|
276
|
-
if (reasoning) {
|
|
277
|
-
updateReasoning(message, reasoning);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function initializeCompletionChoice(
|
|
282
|
-
chunkChoice: OpenAI.Chat.Completions.ChatCompletionChunk.Choice
|
|
283
|
-
): { choice: Choice; done: boolean } {
|
|
284
|
-
// export interface ChatCompletionChunk.Choice {
|
|
285
|
-
// delta: Choice.Delta;
|
|
286
|
-
// finish_reason:
|
|
287
|
-
// 'stop'|'length'|'tool_calls'|'content_filter'|'function_call'|null;
|
|
288
|
-
// index: number;
|
|
289
|
-
// logprobs?: Choice.Logprobs | null;
|
|
290
|
-
// }
|
|
291
|
-
//
|
|
292
|
-
// ->
|
|
293
|
-
//
|
|
294
|
-
// export interface ChatCompletion.Choice {
|
|
295
|
-
// message: CompletionsCompletionsAPI.ChatCompletionMessage;
|
|
296
|
-
// finish_reason:
|
|
297
|
-
// 'stop'|'length'|'tool_calls'|'content_filter'|'function_call';
|
|
298
|
-
// index: number;
|
|
299
|
-
// logprobs: Choice.Logprobs | null;
|
|
300
|
-
// }
|
|
301
|
-
|
|
302
|
-
const message = initializeCompletionMessage(chunkChoice.delta);
|
|
303
|
-
return {
|
|
304
|
-
choice: {
|
|
305
|
-
message,
|
|
306
|
-
// We use `null` to signal that `finish_reason` is unset
|
|
307
|
-
finish_reason: chunkChoice.finish_reason || (null as unknown as "stop"),
|
|
308
|
-
index: chunkChoice.index,
|
|
309
|
-
logprobs: chunkChoice.logprobs || null,
|
|
310
|
-
},
|
|
311
|
-
done: !!chunkChoice.finish_reason,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function updateCompletionChoice(
|
|
316
|
-
completionChoice: Choice,
|
|
317
|
-
chunkChoice: OpenAI.Chat.Completions.ChatCompletionChunk.Choice
|
|
318
|
-
): boolean {
|
|
319
|
-
// export interface ChatCompletionChunk.Choice {
|
|
320
|
-
// delta: Choice.Delta;
|
|
321
|
-
// finish_reason:
|
|
322
|
-
// 'stop'|'length'|'tool_calls'|'content_filter'|'function_call'|null;
|
|
323
|
-
// index: number;
|
|
324
|
-
// logprobs?: Choice.Logprobs | null;
|
|
325
|
-
// }
|
|
326
|
-
//
|
|
327
|
-
// ->
|
|
328
|
-
//
|
|
329
|
-
// export interface ChatCompletion.Choice {
|
|
330
|
-
// message: CompletionsCompletionsAPI.ChatCompletionMessage;
|
|
331
|
-
// finish_reason:
|
|
332
|
-
// 'stop'|'length'|'tool_calls'|'content_filter'|'function_call';
|
|
333
|
-
// index: number;
|
|
334
|
-
// logprobs: Choice.Logprobs | null;
|
|
335
|
-
// }
|
|
336
|
-
|
|
337
|
-
// TODO: logprobs
|
|
338
|
-
|
|
339
|
-
assert(completionChoice.index === chunkChoice.index);
|
|
340
|
-
updateCompletionMessage(completionChoice.message, chunkChoice.delta);
|
|
341
|
-
if (chunkChoice.finish_reason) {
|
|
342
|
-
assert(
|
|
343
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
344
|
-
completionChoice.finish_reason === null,
|
|
345
|
-
`finish_reason already set: (${completionChoice.finish_reason})`
|
|
346
|
-
);
|
|
347
|
-
completionChoice.finish_reason = chunkChoice.finish_reason;
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
return false;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function initializeCompletionChoices(
|
|
354
|
-
chunkChoices: OpenAI.Chat.Completions.ChatCompletionChunk.Choice[]
|
|
355
|
-
): { choices: Choice[]; done: boolean } {
|
|
356
|
-
// Technically, one choice could be done and the other still have some
|
|
357
|
-
// content to stream. We keep it simple for now and allow zero or one
|
|
358
|
-
// choice per chunk, which allows us to mark everything as done if any
|
|
359
|
-
// choice we hit is done. Zero choices can occur in usage-only chunks at
|
|
360
|
-
// the end of the stream.
|
|
361
|
-
assert(chunkChoices.length < 2);
|
|
362
|
-
|
|
363
|
-
let msgDone = false;
|
|
364
|
-
const choices: Choice[] = [];
|
|
365
|
-
for (const chunkChoice of chunkChoices) {
|
|
366
|
-
const { choice, done } = initializeCompletionChoice(chunkChoice);
|
|
367
|
-
if (done) {
|
|
368
|
-
msgDone = true;
|
|
369
|
-
}
|
|
370
|
-
choices[chunkChoice.index] = choice;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return { choices, done: msgDone };
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function updateCompletionChoices(
|
|
377
|
-
completionChoices: Choice[],
|
|
378
|
-
chunkChoices: OpenAI.Chat.Completions.ChatCompletionChunk.Choice[]
|
|
379
|
-
): boolean {
|
|
380
|
-
// Technically, one choice could be done and the other still have some
|
|
381
|
-
// content to stream. We keep it simple for now and allow zero or one
|
|
382
|
-
// choice per chunk, which allows us to mark everything as done if any
|
|
383
|
-
// choice we hit is done. Zero choices can occur in usage-only chunks at
|
|
384
|
-
// the end of the stream.
|
|
385
|
-
assert(chunkChoices.length < 2);
|
|
386
|
-
assert(completionChoices.length === 1);
|
|
387
|
-
|
|
388
|
-
let msgDone = false;
|
|
389
|
-
for (const chunkChoice of chunkChoices) {
|
|
390
|
-
const choiceIdx = chunkChoice.index;
|
|
391
|
-
const done = updateCompletionChoice(
|
|
392
|
-
completionChoices[choiceIdx],
|
|
393
|
-
chunkChoice
|
|
394
|
-
);
|
|
395
|
-
if (done) {
|
|
396
|
-
msgDone = true;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return msgDone;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
export function initializeCompletion(
|
|
404
|
-
chunk: OpenAI.Chat.Completions.ChatCompletionChunk
|
|
405
|
-
): { initMessage: Completion; done: boolean } {
|
|
406
|
-
// export interface ChatCompletionChunk {
|
|
407
|
-
// id: string;
|
|
408
|
-
// choices: Array<ChatCompletionChunk.Choice>;
|
|
409
|
-
// created: number;
|
|
410
|
-
// model: string;
|
|
411
|
-
// object: 'chat.completion.chunk';
|
|
412
|
-
// service_tier?: 'auto' | 'default' | 'flex' | null;
|
|
413
|
-
// system_fingerprint?: string;
|
|
414
|
-
// usage?: CompletionsAPI.CompletionUsage | null;
|
|
415
|
-
// }
|
|
416
|
-
//
|
|
417
|
-
// ->
|
|
418
|
-
//
|
|
419
|
-
// export interface ChatCompletion {
|
|
420
|
-
// id: string;
|
|
421
|
-
// choices: Array<ChatCompletion.Choice>;
|
|
422
|
-
// created: number;
|
|
423
|
-
// model: string;
|
|
424
|
-
// object: 'chat.completion';
|
|
425
|
-
// service_tier?: 'auto'|'default'|'flex'|null;
|
|
426
|
-
// system_fingerprint?: string;
|
|
427
|
-
// usage?: CompletionsAPI.CompletionUsage;
|
|
428
|
-
// }
|
|
429
|
-
|
|
430
|
-
const { choices, done } = initializeCompletionChoices(chunk.choices);
|
|
431
|
-
return {
|
|
432
|
-
initMessage: {
|
|
433
|
-
id: chunk.id,
|
|
434
|
-
choices,
|
|
435
|
-
created: chunk.created,
|
|
436
|
-
model: chunk.model,
|
|
437
|
-
object: "chat.completion",
|
|
438
|
-
service_tier: chunk.service_tier,
|
|
439
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
440
|
-
system_fingerprint: chunk.system_fingerprint,
|
|
441
|
-
usage: chunk.usage ?? undefined,
|
|
442
|
-
},
|
|
443
|
-
done,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
export function updateCompletion(
|
|
448
|
-
completion: Completion,
|
|
449
|
-
chunk: OpenAI.Chat.Completions.ChatCompletionChunk
|
|
450
|
-
): boolean {
|
|
451
|
-
// export interface ChatCompletionChunk {
|
|
452
|
-
// id: string;
|
|
453
|
-
// choices: Array<ChatCompletionChunk.Choice>;
|
|
454
|
-
// created: number;
|
|
455
|
-
// model: string;
|
|
456
|
-
// object: 'chat.completion.chunk';
|
|
457
|
-
// service_tier?: 'auto' | 'default' | 'flex' | null;
|
|
458
|
-
// system_fingerprint?: string;
|
|
459
|
-
// usage?: CompletionsAPI.CompletionUsage | null;
|
|
460
|
-
// }
|
|
461
|
-
//
|
|
462
|
-
// ->
|
|
463
|
-
//
|
|
464
|
-
// export interface ChatCompletion {
|
|
465
|
-
// id: string;
|
|
466
|
-
// choices: Array<ChatCompletion.Choice>;
|
|
467
|
-
// created: number;
|
|
468
|
-
// model: string;
|
|
469
|
-
// object: 'chat.completion';
|
|
470
|
-
// service_tier?: 'auto'|'default'|'flex'|null;
|
|
471
|
-
// system_fingerprint?: string;
|
|
472
|
-
// usage?: CompletionsAPI.CompletionUsage;
|
|
473
|
-
// }
|
|
474
|
-
|
|
475
|
-
assert(completion.id === chunk.id);
|
|
476
|
-
assert(completion.model === chunk.model);
|
|
477
|
-
completion.service_tier = completion.service_tier || chunk.service_tier;
|
|
478
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
479
|
-
completion.system_fingerprint =
|
|
480
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
481
|
-
completion.system_fingerprint || chunk.system_fingerprint;
|
|
482
|
-
completion.usage = completion.usage || chunk.usage || undefined;
|
|
483
|
-
|
|
484
|
-
return updateCompletionChoices(completion.choices, chunk.choices);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
export class OpenAILLMStreaming implements ILLM {
|
|
488
|
-
private readonly openai: OpenAI;
|
|
489
|
-
private model: string;
|
|
490
|
-
|
|
491
|
-
constructor(apiKey: string, apiUrl: string | undefined, model: string) {
|
|
492
|
-
this.openai = new OpenAI({
|
|
493
|
-
apiKey,
|
|
494
|
-
baseURL: apiUrl,
|
|
495
|
-
dangerouslyAllowBrowser: true,
|
|
496
|
-
defaultHeaders: XALIA_APP_HEADER,
|
|
497
|
-
});
|
|
498
|
-
this.model = model;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
public setModel(model: string) {
|
|
502
|
-
this.model = model;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
getModel(): string {
|
|
506
|
-
return this.model;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
getUrl(): string {
|
|
510
|
-
return this.openai.baseURL;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
public async getConversationResponse(
|
|
514
|
-
messages: MessageParam[],
|
|
515
|
-
tools?: ToolDescriptor[],
|
|
516
|
-
onMessage?: (msg: string, end: boolean) => Promise<void>,
|
|
517
|
-
onReasoning?: (reasoning: string) => Promise<void>
|
|
518
|
-
): Promise<{ stop: (msg: string) => void; completion: Promise<Completion> }> {
|
|
519
|
-
return OpenAILLMStreaming.makeRequest(
|
|
520
|
-
this.openai,
|
|
521
|
-
this.model,
|
|
522
|
-
messages,
|
|
523
|
-
tools,
|
|
524
|
-
onMessage,
|
|
525
|
-
onReasoning
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
public static async makeRequest(
|
|
530
|
-
openai: OpenAI,
|
|
531
|
-
model: string,
|
|
532
|
-
messages: MessageParam[],
|
|
533
|
-
tools?: ToolDescriptor[],
|
|
534
|
-
onMessage?: (msg: string, end: boolean) => Promise<void>,
|
|
535
|
-
onReasoning?: (reasoning: string) => Promise<void>
|
|
536
|
-
): Promise<{ stop: (msg: string) => void; completion: Promise<Completion> }> {
|
|
537
|
-
const reasoning: Reasoning = {
|
|
538
|
-
effort: "medium",
|
|
539
|
-
enabled: true,
|
|
540
|
-
};
|
|
541
|
-
const chunks = await openai.chat.completions.create({
|
|
542
|
-
model: model,
|
|
543
|
-
messages,
|
|
544
|
-
tools,
|
|
545
|
-
stream: true,
|
|
546
|
-
stream_options: {
|
|
547
|
-
include_usage: true,
|
|
548
|
-
},
|
|
549
|
-
extra_body: { reasoning },
|
|
550
|
-
} as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming);
|
|
551
|
-
|
|
552
|
-
// Check the type casting above
|
|
553
|
-
if (!(chunks as unknown as { iterator: unknown }).iterator) {
|
|
554
|
-
throw new Error("not a stream");
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
let stopMsg: string | undefined = undefined;
|
|
558
|
-
|
|
559
|
-
const stop = (msg: string) => {
|
|
560
|
-
stopMsg = msg;
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
const completion: Promise<Completion> = (async () => {
|
|
564
|
-
// Completion built up over successive calls to processChunk.
|
|
565
|
-
let aggregatedMessage: Completion | undefined;
|
|
566
|
-
|
|
567
|
-
const processChunk = async (
|
|
568
|
-
chunk: OpenAI.Chat.Completions.ChatCompletionChunk
|
|
569
|
-
) => {
|
|
570
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
571
|
-
if (chunk.object !== "chat.completion.chunk") {
|
|
572
|
-
// logger.warn("[stream]: unexpected message");
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (!aggregatedMessage) {
|
|
577
|
-
logger.debug(`[stream] first}`);
|
|
578
|
-
const { initMessage } = initializeCompletion(chunk);
|
|
579
|
-
aggregatedMessage = initMessage;
|
|
580
|
-
} else {
|
|
581
|
-
updateCompletion(aggregatedMessage, chunk);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
if (onMessage) {
|
|
585
|
-
// Inform the call of a message fragment if it contains any text.
|
|
586
|
-
// Note: chunks may have zero choices (e.g., usage-only chunks), so
|
|
587
|
-
// we safely access the first choice.
|
|
588
|
-
|
|
589
|
-
const delta = chunk.choices[0]?.delta;
|
|
590
|
-
// eslint-disable-next-line
|
|
591
|
-
if (delta?.content) {
|
|
592
|
-
await onMessage(delta.content, false);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
if (onReasoning) {
|
|
597
|
-
const delta = chunk.choices[0]
|
|
598
|
-
?.delta as ChatCompletionChunkChoiceDeltaWithReasoning;
|
|
599
|
-
const reasoning = choiceDeltaExtractReasoning(delta);
|
|
600
|
-
if (reasoning) {
|
|
601
|
-
await onReasoning(reasoning);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
// Process each chunk, checking for a stop signal.
|
|
607
|
-
for await (const chunk of chunks) {
|
|
608
|
-
logger.debug(`[stream] chunk: ${JSON.stringify(chunk)}`);
|
|
609
|
-
await processChunk(chunk);
|
|
610
|
-
|
|
611
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
612
|
-
if (stopMsg) {
|
|
613
|
-
const choice: OpenAI.Chat.Completions.ChatCompletionChunk.Choice = {
|
|
614
|
-
delta: { content: stopMsg },
|
|
615
|
-
finish_reason:
|
|
616
|
-
aggregatedMessage && aggregatedMessage.choices[0].finish_reason
|
|
617
|
-
? null
|
|
618
|
-
: "stop",
|
|
619
|
-
index: 0,
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
await processChunk({
|
|
623
|
-
id: aggregatedMessage?.id || "user_stop_chunk",
|
|
624
|
-
created: aggregatedMessage?.created || Date.now(),
|
|
625
|
-
model: aggregatedMessage?.model || model,
|
|
626
|
-
object: "chat.completion.chunk",
|
|
627
|
-
choices: [choice],
|
|
628
|
-
});
|
|
629
|
-
break;
|
|
630
|
-
}
|
|
631
|
-
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (onMessage) {
|
|
635
|
-
await onMessage("", true);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
logger.debug(
|
|
639
|
-
`[stream] final message: ${JSON.stringify(aggregatedMessage)}`
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
assert(aggregatedMessage);
|
|
643
|
-
return aggregatedMessage;
|
|
644
|
-
})();
|
|
645
|
-
|
|
646
|
-
return { stop, completion };
|
|
647
|
-
}
|
|
648
|
-
}
|