@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.
Files changed (161) hide show
  1. package/package.json +5 -2
  2. package/.env.development +0 -6
  3. package/.env.test +0 -7
  4. package/.prettierrc.json +0 -11
  5. package/context_system.md +0 -498
  6. package/eslint.config.mjs +0 -38
  7. package/scripts/chat_server +0 -8
  8. package/scripts/git_message +0 -31
  9. package/scripts/git_wip +0 -21
  10. package/scripts/pr_message +0 -18
  11. package/scripts/pr_review +0 -16
  12. package/scripts/setup_chat +0 -90
  13. package/scripts/shutdown_chat_server +0 -42
  14. package/scripts/start_chat_server +0 -24
  15. package/scripts/sudomcp_import +0 -23
  16. package/scripts/test_chat +0 -327
  17. package/src/agent/agent.ts +0 -699
  18. package/src/agent/agentUtils.ts +0 -286
  19. package/src/agent/compressingContextManager.ts +0 -129
  20. package/src/agent/context.ts +0 -265
  21. package/src/agent/contextWithWorkspace.ts +0 -162
  22. package/src/agent/documentSummarizer.ts +0 -157
  23. package/src/agent/dummyLLM.ts +0 -130
  24. package/src/agent/iAgentEventHandler.ts +0 -64
  25. package/src/agent/imageGenLLM.ts +0 -101
  26. package/src/agent/imageGenerator.ts +0 -45
  27. package/src/agent/iplatform.ts +0 -18
  28. package/src/agent/llm.ts +0 -74
  29. package/src/agent/mcpServerManager.ts +0 -541
  30. package/src/agent/nullAgentEventHandler.ts +0 -26
  31. package/src/agent/nullPlatform.ts +0 -13
  32. package/src/agent/openAI.ts +0 -123
  33. package/src/agent/openAILLM.ts +0 -99
  34. package/src/agent/openAILLMStreaming.ts +0 -648
  35. package/src/agent/promptProvider.ts +0 -87
  36. package/src/agent/repeatLLM.ts +0 -62
  37. package/src/agent/sudoMcpServerManager.ts +0 -361
  38. package/src/agent/test_data/harrypotter.txt +0 -6065
  39. package/src/agent/tokenAuth.ts +0 -50
  40. package/src/agent/tokenCounter.test.ts +0 -243
  41. package/src/agent/tokenCounter.ts +0 -483
  42. package/src/agent/toolSettings.ts +0 -24
  43. package/src/agent/tools/calculatorTool.ts +0 -50
  44. package/src/agent/tools/contentExtractors/htmlToText.ts +0 -61
  45. package/src/agent/tools/contentExtractors/pdfToText.ts +0 -60
  46. package/src/agent/tools/datetimeTool.ts +0 -41
  47. package/src/agent/tools/fileManager/fileManagerTool.ts +0 -199
  48. package/src/agent/tools/fileManager/index.ts +0 -50
  49. package/src/agent/tools/fileManager/memoryFileManager.ts +0 -120
  50. package/src/agent/tools/fileManager/mimeTypes.ts +0 -60
  51. package/src/agent/tools/fileManager/prompt.ts +0 -38
  52. package/src/agent/tools/fileManager/types.ts +0 -189
  53. package/src/agent/tools/index.ts +0 -49
  54. package/src/agent/tools/openUrlTool.ts +0 -62
  55. package/src/agent/tools/renderTool.ts +0 -92
  56. package/src/agent/tools/utils.ts +0 -74
  57. package/src/agent/tools/webSearch.ts +0 -138
  58. package/src/agent/tools/webSearchTool.ts +0 -44
  59. package/src/chat/client/chatClient.ts +0 -967
  60. package/src/chat/client/connection.test.ts +0 -241
  61. package/src/chat/client/connection.ts +0 -286
  62. package/src/chat/client/constants.ts +0 -1
  63. package/src/chat/client/index.ts +0 -21
  64. package/src/chat/client/interfaces.ts +0 -34
  65. package/src/chat/client/sessionClient.ts +0 -574
  66. package/src/chat/client/sessionFiles.ts +0 -142
  67. package/src/chat/client/teamManager.ts +0 -29
  68. package/src/chat/constants.ts +0 -6
  69. package/src/chat/data/apiKeyManager.ts +0 -76
  70. package/src/chat/data/dataModels.ts +0 -107
  71. package/src/chat/data/database.ts +0 -997
  72. package/src/chat/data/dbMcpServerConfigs.ts +0 -59
  73. package/src/chat/data/dbSessionFiles.ts +0 -107
  74. package/src/chat/data/dbSessionMessages.ts +0 -102
  75. package/src/chat/protocol/connectionMessages.ts +0 -49
  76. package/src/chat/protocol/constants.ts +0 -55
  77. package/src/chat/protocol/errors.ts +0 -16
  78. package/src/chat/protocol/messages.ts +0 -899
  79. package/src/chat/server/README.md +0 -127
  80. package/src/chat/server/chatContextManager.ts +0 -660
  81. package/src/chat/server/connectionManager.test.ts +0 -246
  82. package/src/chat/server/connectionManager.ts +0 -506
  83. package/src/chat/server/conversation.ts +0 -319
  84. package/src/chat/server/errorUtils.ts +0 -28
  85. package/src/chat/server/imageGeneratorTools.ts +0 -179
  86. package/src/chat/server/openAIRouterLLM.ts +0 -168
  87. package/src/chat/server/openSession.ts +0 -1945
  88. package/src/chat/server/openSessionMessageSender.ts +0 -4
  89. package/src/chat/server/promptRefiner.ts +0 -106
  90. package/src/chat/server/server.ts +0 -178
  91. package/src/chat/server/sessionFileManager.ts +0 -151
  92. package/src/chat/server/sessionRegistry.test.ts +0 -137
  93. package/src/chat/server/sessionRegistry.ts +0 -1553
  94. package/src/chat/server/test-utils/mockFactories.ts +0 -422
  95. package/src/chat/server/titleGenerator.test.ts +0 -103
  96. package/src/chat/server/titleGenerator.ts +0 -143
  97. package/src/chat/server/tools.ts +0 -170
  98. package/src/chat/utils/agentSessionMap.ts +0 -76
  99. package/src/chat/utils/approvalManager.ts +0 -189
  100. package/src/chat/utils/asyncLock.ts +0 -43
  101. package/src/chat/utils/asyncQueue.ts +0 -62
  102. package/src/chat/utils/multiAsyncQueue.ts +0 -66
  103. package/src/chat/utils/responseAwaiter.ts +0 -181
  104. package/src/chat/utils/userResolver.ts +0 -48
  105. package/src/chat/utils/websocket.ts +0 -16
  106. package/src/index.ts +0 -0
  107. package/src/test/agent.test.ts +0 -584
  108. package/src/test/approvalManager.test.ts +0 -141
  109. package/src/test/chatContextManager.test.ts +0 -552
  110. package/src/test/clientServerConnection.test.ts +0 -205
  111. package/src/test/compressingContextManager.test.ts +0 -77
  112. package/src/test/context.test.ts +0 -150
  113. package/src/test/contextTestTools.ts +0 -95
  114. package/src/test/conversation.test.ts +0 -109
  115. package/src/test/db.test.ts +0 -363
  116. package/src/test/dbMcpServerConfigs.test.ts +0 -112
  117. package/src/test/dbSessionFiles.test.ts +0 -258
  118. package/src/test/dbSessionMessages.test.ts +0 -85
  119. package/src/test/dbTestTools.ts +0 -157
  120. package/src/test/imageLoad.test.ts +0 -15
  121. package/src/test/mcpServerManager.test.ts +0 -114
  122. package/src/test/multiAsyncQueue.test.ts +0 -183
  123. package/src/test/openaiStreaming.test.ts +0 -177
  124. package/src/test/prompt.test.ts +0 -27
  125. package/src/test/promptProvider.test.ts +0 -33
  126. package/src/test/responseAwaiter.test.ts +0 -103
  127. package/src/test/sudoMcpServerManager.test.ts +0 -63
  128. package/src/test/testTools.ts +0 -176
  129. package/src/test/tools.test.ts +0 -64
  130. package/src/tool/agentChat.ts +0 -203
  131. package/src/tool/agentMain.ts +0 -180
  132. package/src/tool/chatMain.ts +0 -621
  133. package/src/tool/commandPrompt.ts +0 -264
  134. package/src/tool/files.ts +0 -82
  135. package/src/tool/main.ts +0 -25
  136. package/src/tool/nodePlatform.ts +0 -73
  137. package/src/tool/options.ts +0 -144
  138. package/src/tool/prompt.ts +0 -101
  139. package/test_data/background_test_profile.json +0 -6
  140. package/test_data/background_test_script.json +0 -11
  141. package/test_data/dummyllm_script_crash.json +0 -32
  142. package/test_data/dummyllm_script_image_gen.json +0 -19
  143. package/test_data/dummyllm_script_image_gen_fe.json +0 -29
  144. package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
  145. package/test_data/dummyllm_script_render_tool.json +0 -29
  146. package/test_data/dummyllm_script_simplecalc.json +0 -28
  147. package/test_data/dummyllm_script_test_auto_approve.json +0 -81
  148. package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
  149. package/test_data/frog.png +0 -0
  150. package/test_data/frog.png.b64 +0 -1
  151. package/test_data/git_message_profile.json +0 -4
  152. package/test_data/git_wip_system.txt +0 -5
  153. package/test_data/image_gen_test_profile.json +0 -5
  154. package/test_data/pr_message_profile.json +0 -4
  155. package/test_data/pr_review_profile.json +0 -4
  156. package/test_data/prompt_simplecalc.txt +0 -1
  157. package/test_data/simplecalc_profile.json +0 -4
  158. package/test_data/sudomcp_import_profile.json +0 -4
  159. package/test_data/test_script_profile.json +0 -8
  160. package/tsconfig.json +0 -13
  161. 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
- }