@xalia/agent 0.6.8 → 0.6.10
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/.env.development +6 -0
- package/.env.test +7 -0
- package/README.md +11 -0
- package/context_system.md +498 -0
- package/dist/agent/src/agent/agent.js +169 -87
- package/dist/agent/src/agent/agentUtils.js +24 -18
- package/dist/agent/src/agent/compressingContextManager.js +10 -14
- package/dist/agent/src/agent/context.js +101 -127
- package/dist/agent/src/agent/contextWithWorkspace.js +133 -0
- package/dist/agent/src/agent/documentSummarizer.js +126 -0
- package/dist/agent/src/agent/dummyLLM.js +25 -22
- package/dist/agent/src/agent/imageGenLLM.js +22 -25
- package/dist/agent/src/agent/imageGenerator.js +2 -10
- package/dist/agent/src/agent/llm.js +1 -1
- package/dist/agent/src/agent/openAILLM.js +15 -12
- package/dist/agent/src/agent/openAILLMStreaming.js +73 -39
- package/dist/agent/src/agent/repeatLLM.js +16 -7
- package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
- package/dist/agent/src/agent/tokenCounter.js +390 -0
- package/dist/agent/src/agent/tokenCounter.test.js +206 -0
- package/dist/agent/src/agent/toolSettings.js +17 -0
- package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
- package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
- package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
- package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
- package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
- package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
- package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
- package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
- package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
- package/dist/agent/src/agent/tools/index.js +64 -0
- package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
- package/dist/agent/src/agent/tools/renderTool.js +89 -0
- package/dist/agent/src/agent/tools/utils.js +61 -0
- package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
- package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
- package/dist/agent/src/chat/client/chatClient.js +63 -2
- package/dist/agent/src/chat/client/connection.js +6 -1
- package/dist/agent/src/chat/client/index.js +4 -1
- package/dist/agent/src/chat/client/sessionClient.js +28 -9
- package/dist/agent/src/chat/constants.js +8 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
- package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
- package/dist/agent/src/chat/protocol/messages.js +9 -0
- package/dist/agent/src/chat/server/chatContextManager.js +186 -156
- package/dist/agent/src/chat/server/conversation.js +3 -0
- package/dist/agent/src/chat/server/imageGeneratorTools.js +39 -16
- package/dist/agent/src/chat/server/openAIRouterLLM.js +111 -0
- package/dist/agent/src/chat/server/openSession.js +253 -91
- package/dist/agent/src/chat/server/promptRefiner.js +86 -0
- package/dist/agent/src/chat/server/server.js +10 -2
- package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
- package/dist/agent/src/chat/server/sessionRegistry.js +152 -6
- package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
- package/dist/agent/src/chat/server/titleGenerator.js +112 -0
- package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
- package/dist/agent/src/chat/server/tools.js +64 -253
- package/dist/agent/src/chat/utils/approvalManager.js +6 -3
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
- package/dist/agent/src/test/agent.test.js +16 -17
- package/dist/agent/src/test/chatContextManager.test.js +44 -30
- package/dist/agent/src/test/clientServerConnection.test.js +1 -2
- package/dist/agent/src/test/compressingContextManager.test.js +22 -36
- package/dist/agent/src/test/context.test.js +55 -17
- package/dist/agent/src/test/contextTestTools.js +87 -0
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
- package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
- package/dist/agent/src/test/testTools.js +6 -1
- package/dist/agent/src/test/tools.test.js +27 -9
- package/dist/agent/src/tool/agentChat.js +5 -2
- package/dist/agent/src/tool/chatMain.js +56 -15
- package/dist/agent/src/tool/commandPrompt.js +2 -2
- package/dist/agent/src/tool/files.js +7 -8
- package/package.json +4 -1
- package/scripts/test_chat +195 -173
- package/src/agent/agent.ts +257 -137
- package/src/agent/agentUtils.ts +32 -20
- package/src/agent/compressingContextManager.ts +13 -44
- package/src/agent/context.ts +165 -159
- package/src/agent/contextWithWorkspace.ts +162 -0
- package/src/agent/documentSummarizer.ts +157 -0
- package/src/agent/dummyLLM.ts +27 -23
- package/src/agent/imageGenLLM.ts +28 -32
- package/src/agent/imageGenerator.ts +3 -18
- package/src/agent/llm.ts +2 -2
- package/src/agent/openAILLM.ts +17 -13
- package/src/agent/openAILLMStreaming.ts +99 -43
- package/src/agent/repeatLLM.ts +19 -7
- package/src/agent/sudoMcpServerManager.ts +41 -20
- package/src/agent/test_data/harrypotter.txt +6065 -0
- package/src/agent/tokenCounter.test.ts +243 -0
- package/src/agent/tokenCounter.ts +483 -0
- package/src/agent/toolSettings.ts +24 -0
- package/src/agent/tools/calculatorTool.ts +50 -0
- package/src/agent/tools/contentExtractors/pdfToText.ts +60 -0
- package/src/agent/tools/datetimeTool.ts +41 -0
- package/src/agent/tools/fileManager/fileManagerTool.ts +199 -0
- package/src/agent/tools/fileManager/index.ts +50 -0
- package/src/agent/tools/fileManager/memoryFileManager.ts +120 -0
- package/src/{chat/data → agent/tools/fileManager}/mimeTypes.ts +3 -1
- package/src/agent/tools/fileManager/prompt.ts +38 -0
- package/src/{chat/data/dbSessionFileModels.ts → agent/tools/fileManager/types.ts} +76 -0
- package/src/agent/tools/index.ts +49 -0
- package/src/agent/tools/openUrlTool.ts +62 -0
- package/src/agent/tools/renderTool.ts +92 -0
- package/src/agent/tools/utils.ts +74 -0
- package/src/{chat/utils/search.ts → agent/tools/webSearch.ts} +0 -1
- package/src/agent/tools/webSearchTool.ts +44 -0
- package/src/chat/client/chatClient.ts +92 -3
- package/src/chat/client/connection.ts +11 -1
- package/src/chat/client/index.ts +3 -0
- package/src/chat/client/sessionClient.ts +40 -11
- package/src/chat/client/sessionFiles.ts +1 -1
- package/src/chat/constants.ts +6 -0
- package/src/chat/data/dataModels.ts +12 -0
- package/src/chat/data/dbSessionFiles.ts +12 -4
- package/src/chat/data/dbSessionMessages.ts +34 -0
- package/src/chat/protocol/messages.ts +94 -14
- package/src/chat/server/chatContextManager.ts +255 -221
- package/src/chat/server/connectionManager.ts +1 -1
- package/src/chat/server/conversation.ts +3 -0
- package/src/chat/server/imageGeneratorTools.ts +62 -30
- package/src/chat/server/openAIRouterLLM.ts +168 -0
- package/src/chat/server/openSession.ts +381 -138
- package/src/chat/server/promptRefiner.ts +106 -0
- package/src/chat/server/server.ts +9 -2
- package/src/chat/server/sessionFileManager.ts +35 -306
- package/src/chat/server/sessionRegistry.test.ts +0 -1
- package/src/chat/server/sessionRegistry.ts +228 -4
- package/src/chat/server/titleGenerator.test.ts +103 -0
- package/src/chat/server/titleGenerator.ts +143 -0
- package/src/chat/server/tools.ts +92 -281
- package/src/chat/utils/approvalManager.ts +9 -3
- package/src/chat/utils/multiAsyncQueue.ts +4 -0
- package/src/test/agent.test.ts +25 -30
- package/src/test/chatContextManager.test.ts +68 -38
- package/src/test/clientServerConnection.test.ts +0 -2
- package/src/test/compressingContextManager.test.ts +29 -34
- package/src/test/context.test.ts +59 -15
- package/src/test/contextTestTools.ts +95 -0
- package/src/test/dbMcpServerConfigs.test.ts +4 -4
- package/src/test/dbSessionFiles.test.ts +16 -16
- package/src/test/testTools.ts +8 -3
- package/src/test/tools.test.ts +30 -5
- package/src/tool/agentChat.ts +12 -3
- package/src/tool/chatMain.ts +59 -18
- package/src/tool/commandPrompt.ts +2 -2
- package/src/tool/files.ts +1 -3
- package/dist/agent/src/agent/tools.js +0 -44
- package/src/agent/tools.ts +0 -57
- /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
- /package/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.ts +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { getOpenAIClient } from "../chat/server/openAIRouterLLM";
|
|
2
|
+
import { getLogger } from "@xalia/xmcp/sdk";
|
|
3
|
+
|
|
4
|
+
const logger = getLogger();
|
|
5
|
+
|
|
6
|
+
const SUMMARY_MODEL = "google/gemini-2.5-flash";
|
|
7
|
+
const SUMMARY_MAX_TOKENS = 500;
|
|
8
|
+
const SUMMARY_TEMPERATURE = 0.3;
|
|
9
|
+
const SUMMARY_TIMEOUT_MS = 30000;
|
|
10
|
+
const MAX_CONTENT_LENGTH = 100000;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* System prompt for document summarization, optimized for recall.
|
|
14
|
+
*/
|
|
15
|
+
const SUMMARY_SYSTEM_PROMPT =
|
|
16
|
+
`You are a document summarizer optimizing for RECALL. Create a summary ` +
|
|
17
|
+
`(3-10 sentences) that captures:
|
|
18
|
+
- Main topic and purpose of the document
|
|
19
|
+
- Key entities (names, organizations, places, dates, numbers)
|
|
20
|
+
- Important concepts, terms, and topics mentioned
|
|
21
|
+
- Any conclusions, results, or key findings
|
|
22
|
+
|
|
23
|
+
Include specific details that would help locate this document later.
|
|
24
|
+
Use keywords and phrases from the original text.
|
|
25
|
+
Do NOT include meta-commentary about the document format.
|
|
26
|
+
Output ONLY the summary text.`;
|
|
27
|
+
|
|
28
|
+
export interface IDocumentSummarizer {
|
|
29
|
+
summarize(content: string): Promise<string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class LLMDocumentSummarizer implements IDocumentSummarizer {
|
|
33
|
+
private model: string;
|
|
34
|
+
|
|
35
|
+
constructor(model: string = SUMMARY_MODEL) {
|
|
36
|
+
this.model = model;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async summarize(content: string): Promise<string> {
|
|
40
|
+
if (!content || content.trim().length === 0) {
|
|
41
|
+
return "Empty document";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const summary = await this.summarizeWithTimeout(content);
|
|
46
|
+
return this.sanitizeSummary(summary);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
49
|
+
logger.warn(
|
|
50
|
+
`[DocumentSummarizer] LLM summarization failed: ${errorMsg}, ` +
|
|
51
|
+
`using fallback`
|
|
52
|
+
);
|
|
53
|
+
return this.fallbackSummary(content);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async summarizeWithTimeout(content: string): Promise<string> {
|
|
58
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
reject(new Error("Summary generation timeout"));
|
|
61
|
+
}, SUMMARY_TIMEOUT_MS);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const summaryPromise = this.callLLM(content);
|
|
65
|
+
|
|
66
|
+
return Promise.race([summaryPromise, timeoutPromise]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async callLLM(content: string): Promise<string> {
|
|
70
|
+
const client = getOpenAIClient(this.model);
|
|
71
|
+
|
|
72
|
+
const truncatedContent =
|
|
73
|
+
content.length > MAX_CONTENT_LENGTH
|
|
74
|
+
? content.slice(0, MAX_CONTENT_LENGTH) + "\n\n[Content truncated...]"
|
|
75
|
+
: content;
|
|
76
|
+
|
|
77
|
+
const response = await client.chat.completions.create({
|
|
78
|
+
model: this.model,
|
|
79
|
+
messages: [
|
|
80
|
+
{
|
|
81
|
+
role: "system",
|
|
82
|
+
content: SUMMARY_SYSTEM_PROMPT,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
role: "user",
|
|
86
|
+
content: `Please summarize this document:\n\n${truncatedContent}`,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
max_tokens: SUMMARY_MAX_TOKENS,
|
|
90
|
+
temperature: SUMMARY_TEMPERATURE,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const summary = response.choices[0]?.message?.content?.trim();
|
|
94
|
+
|
|
95
|
+
if (!summary) {
|
|
96
|
+
throw new Error("Empty response from LLM");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return summary;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private sanitizeSummary(summary: string): string {
|
|
103
|
+
return summary.replace(/\s+/g, " ").trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private fallbackSummary(content: string): string {
|
|
107
|
+
const cleaned = content.trim();
|
|
108
|
+
|
|
109
|
+
if (cleaned.length === 0) {
|
|
110
|
+
return "Empty document";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const firstParagraph = cleaned.split(/\n\n/)[0];
|
|
114
|
+
const maxLength = 500;
|
|
115
|
+
|
|
116
|
+
if (firstParagraph.length <= maxLength) {
|
|
117
|
+
return firstParagraph;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return cleaned.slice(0, maxLength).trim() + "...";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class FallbackDocumentSummarizer implements IDocumentSummarizer {
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
126
|
+
async summarize(content: string): Promise<string> {
|
|
127
|
+
const cleaned = content.trim();
|
|
128
|
+
|
|
129
|
+
if (cleaned.length === 0) {
|
|
130
|
+
return "Empty document";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const firstParagraph = cleaned.split(/\n\n/)[0];
|
|
134
|
+
const maxLength = 500;
|
|
135
|
+
|
|
136
|
+
if (firstParagraph.length <= maxLength) {
|
|
137
|
+
return firstParagraph;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return cleaned.slice(0, maxLength).trim() + "...";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function createDocumentSummarizer(model?: string): IDocumentSummarizer {
|
|
145
|
+
if (process.env.DISABLE_LLM_SUMMARIES === "true") {
|
|
146
|
+
return new FallbackDocumentSummarizer();
|
|
147
|
+
}
|
|
148
|
+
return new LLMDocumentSummarizer(model);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Convenience function for one-off summarization.
|
|
153
|
+
*/
|
|
154
|
+
export async function summarizeDocument(content: string): Promise<string> {
|
|
155
|
+
const summarizer = createDocumentSummarizer();
|
|
156
|
+
return summarizer.summarize(content);
|
|
157
|
+
}
|
package/src/agent/dummyLLM.ts
CHANGED
|
@@ -81,39 +81,43 @@ export class DummyLLM implements ILLM {
|
|
|
81
81
|
_tools?: ToolDescriptor[],
|
|
82
82
|
onMessage?: (msg: string, msgEnd: boolean) => Promise<void>,
|
|
83
83
|
onReasoning?: (reasoning: string) => Promise<void>
|
|
84
|
-
): Promise<Completion> {
|
|
84
|
+
): Promise<{ stop: (msg: string) => void; completion: Promise<Completion> }> {
|
|
85
85
|
await new Promise((r) => setTimeout(r, 0));
|
|
86
|
-
assert(this.idx < this.responses.length);
|
|
87
86
|
|
|
88
87
|
this.lastRequest = messages;
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
const completion: Promise<Completion> = (async () => {
|
|
90
|
+
for (;;) {
|
|
91
|
+
const idx = this.idx++;
|
|
92
|
+
const response = this.responses[idx % this.responses.length];
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
if (response.finish_reason === "error") {
|
|
95
|
+
throw new Error(response.message);
|
|
96
|
+
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
if (response.finish_reason === "reasoning") {
|
|
99
|
+
if (onReasoning) {
|
|
100
|
+
await onReasoning(response.message);
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
100
103
|
}
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if (onMessage) {
|
|
106
|
+
const message = response.message;
|
|
107
|
+
void onMessage(message.content || "", true);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
id: String(idx),
|
|
112
|
+
choices: [response],
|
|
113
|
+
created: Date.now(),
|
|
114
|
+
model: "dummyLlmModel",
|
|
115
|
+
object: "chat.completion",
|
|
116
|
+
};
|
|
107
117
|
}
|
|
118
|
+
})();
|
|
108
119
|
|
|
109
|
-
|
|
110
|
-
id: String(this.idx),
|
|
111
|
-
choices: [response],
|
|
112
|
-
created: Date.now(),
|
|
113
|
-
model: "dummyLlmModel",
|
|
114
|
-
object: "chat.completion",
|
|
115
|
-
};
|
|
116
|
-
}
|
|
120
|
+
return { stop: () => {}, completion };
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
public setModel(_model: string): void {
|
package/src/agent/imageGenLLM.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { OpenAI } from "openai";
|
|
2
2
|
import { strict as assert } from "assert";
|
|
3
|
-
import { writeFileSync } from "fs";
|
|
4
3
|
|
|
5
4
|
import { getLogger } from "@xalia/xmcp/sdk";
|
|
6
5
|
|
|
@@ -54,11 +53,11 @@ export class ImageGenLLM implements ILLM {
|
|
|
54
53
|
return this.openai.baseURL;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
public
|
|
56
|
+
public getConversationResponse(
|
|
58
57
|
messages: MessageParam[],
|
|
59
58
|
tools?: ToolDescriptor[],
|
|
60
59
|
onMessage?: (msg: string, end: boolean) => Promise<void>
|
|
61
|
-
): Promise<Completion> {
|
|
60
|
+
): Promise<{ stop: (msg: string) => void; completion: Promise<Completion> }> {
|
|
62
61
|
assert(!tools || tools.length === 0, "tools not supported in ImageGenLLM");
|
|
63
62
|
|
|
64
63
|
// Designed for image generation using openrouter, which tweaks the Create
|
|
@@ -71,35 +70,32 @@ export class ImageGenLLM implements ILLM {
|
|
|
71
70
|
|
|
72
71
|
logger.info(`[ImageGenLLM] params; ${JSON.stringify(params)}`);
|
|
73
72
|
|
|
74
|
-
const completion = (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
message.images.forEach((image, index) => {
|
|
97
|
-
const imageUrl = image.image_url.url; // Base64 data URL
|
|
98
|
-
const truncated = imageUrl.substring(0, 50);
|
|
99
|
-
logger.info(`[ImageGenLLM] ${String(index + 1)}: ${truncated}...`);
|
|
100
|
-
});
|
|
73
|
+
const completion = (async () => {
|
|
74
|
+
const completion = (await this.openai.chat.completions.create(
|
|
75
|
+
params as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
76
|
+
)) as Completion;
|
|
77
|
+
|
|
78
|
+
// logger.debug(
|
|
79
|
+
// `[ImageGenLLM.getConversationResponse] completion:
|
|
80
|
+
// ${JSON.stringify(completion)}`
|
|
81
|
+
// );
|
|
82
|
+
|
|
83
|
+
if (onMessage) {
|
|
84
|
+
const message = completion.choices[0].message;
|
|
85
|
+
if (message.content) {
|
|
86
|
+
await onMessage(message.content, true);
|
|
87
|
+
}
|
|
88
|
+
if (message.images) {
|
|
89
|
+
message.images.forEach((image, index) => {
|
|
90
|
+
const imageUrl = image.image_url.url; // Base64 data URL
|
|
91
|
+
const truncated = imageUrl.substring(0, 50);
|
|
92
|
+
logger.info(`[ImageGenLLM] ${String(index + 1)}: ${truncated}...`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
101
95
|
}
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
return completion;
|
|
97
|
+
})();
|
|
98
|
+
|
|
99
|
+
return Promise.resolve({ stop: () => {}, completion });
|
|
104
100
|
}
|
|
105
101
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { Agent, createUserMessage } from "./agent";
|
|
2
|
-
import { createLLM } from "./agentUtils";
|
|
3
2
|
import { ContextManager } from "./context";
|
|
4
3
|
import { NULL_AGENT_EVENT_HANDLER } from "./nullAgentEventHandler";
|
|
5
|
-
import {
|
|
6
|
-
import { NODE_PLATFORM } from "../tool/nodePlatform";
|
|
7
|
-
import { DEFAULT_IMAGE_GEN_MODEL } from "./imageGenLLM";
|
|
4
|
+
import { ILLM } from "./llm";
|
|
8
5
|
|
|
9
6
|
const IMAGE_GEN_SYSTEM_PROMPT = "You are an image generator";
|
|
10
7
|
|
|
@@ -17,19 +14,7 @@ export class ImageGenerator {
|
|
|
17
14
|
this.contextManager = contextManager;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
public static
|
|
21
|
-
llmUrl: string,
|
|
22
|
-
llmApiKey: string,
|
|
23
|
-
model?: string
|
|
24
|
-
): Promise<ImageGenerator> {
|
|
25
|
-
const development = !!process.env.DEVELOPMENT;
|
|
26
|
-
const llm = await createLLM(
|
|
27
|
-
llmUrl,
|
|
28
|
-
llmApiKey,
|
|
29
|
-
model || DEFAULT_IMAGE_GEN_MODEL,
|
|
30
|
-
false /* stream */,
|
|
31
|
-
development ? NODE_PLATFORM : NULL_PLATFORM // allow file loading
|
|
32
|
-
);
|
|
17
|
+
public static init(llm: ILLM): ImageGenerator {
|
|
33
18
|
const contextManager = new ContextManager(IMAGE_GEN_SYSTEM_PROMPT, []);
|
|
34
19
|
const agent = Agent.initializeWithLLM(
|
|
35
20
|
NULL_AGENT_EVENT_HANDLER,
|
|
@@ -53,7 +38,7 @@ export class ImageGenerator {
|
|
|
53
38
|
}
|
|
54
39
|
|
|
55
40
|
// Clear the context
|
|
56
|
-
|
|
41
|
+
this.contextManager.clear();
|
|
57
42
|
|
|
58
43
|
return agentResponse.images[0].image_url.url;
|
|
59
44
|
}
|
package/src/agent/llm.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as openai from "./openAI";
|
|
|
2
2
|
import { OpenAI } from "openai";
|
|
3
3
|
|
|
4
4
|
export const XALIA_APP_HEADER = {
|
|
5
|
-
"HTTP-Referer": "xalia.ai",
|
|
5
|
+
"HTTP-Referer": "https://xalia.ai",
|
|
6
6
|
"X-Title": "Xalia",
|
|
7
7
|
};
|
|
8
8
|
|
|
@@ -68,7 +68,7 @@ export interface ILLM {
|
|
|
68
68
|
tools?: ToolDescriptor[],
|
|
69
69
|
onMessage?: (msg: string, end: boolean) => Promise<void>,
|
|
70
70
|
onReasoning?: (reasoning: string) => Promise<void>
|
|
71
|
-
): Promise<Completion>;
|
|
71
|
+
): Promise<{ stop: (msg: string) => void; completion: Promise<Completion> }>;
|
|
72
72
|
|
|
73
73
|
setModel(model: string): void;
|
|
74
74
|
}
|
package/src/agent/openAILLM.ts
CHANGED
|
@@ -73,23 +73,27 @@ export class OpenAILLM implements ILLM {
|
|
|
73
73
|
return this.openai.baseURL;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
public
|
|
76
|
+
public getConversationResponse(
|
|
77
77
|
messages: MessageParam[],
|
|
78
78
|
tools?: ToolDescriptor[],
|
|
79
79
|
onMessage?: (msg: string, end: boolean) => Promise<void>
|
|
80
|
-
): Promise<Completion> {
|
|
81
|
-
const completion =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
): Promise<{ stop: (msg: string) => void; completion: Promise<Completion> }> {
|
|
81
|
+
const completion: Promise<Completion> = (async () => {
|
|
82
|
+
const completion = await this.openai.chat.completions.create({
|
|
83
|
+
model: this.model,
|
|
84
|
+
messages,
|
|
85
|
+
tools,
|
|
86
|
+
});
|
|
87
|
+
if (onMessage) {
|
|
88
|
+
const message = completion.choices[0].message;
|
|
89
|
+
if (message.content) {
|
|
90
|
+
await onMessage(message.content, true);
|
|
91
|
+
}
|
|
90
92
|
}
|
|
91
|
-
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
return completionFromOpenAI(completion);
|
|
95
|
+
})();
|
|
96
|
+
|
|
97
|
+
return Promise.resolve({ stop: () => {}, completion });
|
|
94
98
|
}
|
|
95
99
|
}
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
MessageParam,
|
|
14
14
|
ToolDescriptor,
|
|
15
15
|
} from "./llm";
|
|
16
|
-
|
|
17
16
|
import {
|
|
18
17
|
Reasoning,
|
|
19
18
|
ChatCompletionChunkChoiceDeltaWithReasoning,
|
|
@@ -516,13 +515,31 @@ export class OpenAILLMStreaming implements ILLM {
|
|
|
516
515
|
tools?: ToolDescriptor[],
|
|
517
516
|
onMessage?: (msg: string, end: boolean) => Promise<void>,
|
|
518
517
|
onReasoning?: (reasoning: string) => Promise<void>
|
|
519
|
-
): Promise<Completion> {
|
|
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> }> {
|
|
520
537
|
const reasoning: Reasoning = {
|
|
521
538
|
effort: "medium",
|
|
522
539
|
enabled: true,
|
|
523
540
|
};
|
|
524
|
-
const chunks = await
|
|
525
|
-
model:
|
|
541
|
+
const chunks = await openai.chat.completions.create({
|
|
542
|
+
model: model,
|
|
526
543
|
messages,
|
|
527
544
|
tools,
|
|
528
545
|
stream: true,
|
|
@@ -537,56 +554,95 @@ export class OpenAILLMStreaming implements ILLM {
|
|
|
537
554
|
throw new Error("not a stream");
|
|
538
555
|
}
|
|
539
556
|
|
|
540
|
-
let
|
|
557
|
+
let stopMsg: string | undefined = undefined;
|
|
541
558
|
|
|
542
|
-
|
|
543
|
-
|
|
559
|
+
const stop = (msg: string) => {
|
|
560
|
+
stopMsg = msg;
|
|
561
|
+
};
|
|
544
562
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
563
|
+
const completion: Promise<Completion> = (async () => {
|
|
564
|
+
// Completion built up over successive calls to processChunk.
|
|
565
|
+
let aggregatedMessage: Completion | undefined;
|
|
550
566
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
+
}
|
|
558
575
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
576
|
+
if (!aggregatedMessage) {
|
|
577
|
+
logger.debug(`[stream] first}`);
|
|
578
|
+
const { initMessage } = initializeCompletion(chunk);
|
|
579
|
+
aggregatedMessage = initMessage;
|
|
580
|
+
} else {
|
|
581
|
+
updateCompletion(aggregatedMessage, chunk);
|
|
582
|
+
}
|
|
563
583
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
+
}
|
|
568
594
|
}
|
|
569
|
-
}
|
|
570
595
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
+
}
|
|
577
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 */
|
|
578
632
|
}
|
|
579
|
-
}
|
|
580
633
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
634
|
+
if (onMessage) {
|
|
635
|
+
await onMessage("", true);
|
|
636
|
+
}
|
|
584
637
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
638
|
+
logger.debug(
|
|
639
|
+
`[stream] final message: ${JSON.stringify(aggregatedMessage)}`
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
assert(aggregatedMessage);
|
|
643
|
+
return aggregatedMessage;
|
|
644
|
+
})();
|
|
588
645
|
|
|
589
|
-
|
|
590
|
-
return aggregatedMessage;
|
|
646
|
+
return { stop, completion };
|
|
591
647
|
}
|
|
592
648
|
}
|
package/src/agent/repeatLLM.ts
CHANGED
|
@@ -2,8 +2,17 @@ import { Choice, Completion, ILLM, MessageParam, ToolDescriptor } from "./llm";
|
|
|
2
2
|
import { strict as assert } from "assert";
|
|
3
3
|
|
|
4
4
|
export class RepeatLLM implements ILLM {
|
|
5
|
+
private prefix: string;
|
|
5
6
|
private idx: number = 0;
|
|
6
7
|
|
|
8
|
+
constructor(prefix?: string) {
|
|
9
|
+
if (prefix && prefix.length > 0) {
|
|
10
|
+
this.prefix = prefix;
|
|
11
|
+
} else {
|
|
12
|
+
this.prefix = "Message number";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
public getModel(): string {
|
|
8
17
|
return "repeat";
|
|
9
18
|
}
|
|
@@ -16,10 +25,10 @@ export class RepeatLLM implements ILLM {
|
|
|
16
25
|
_messages: MessageParam[],
|
|
17
26
|
_tools?: ToolDescriptor[],
|
|
18
27
|
onMessage?: (msg: string, msgEnd: boolean) => Promise<void>
|
|
19
|
-
): Promise<Completion> {
|
|
28
|
+
): Promise<{ stop: () => void; completion: Promise<Completion> }> {
|
|
20
29
|
await new Promise((r) => setTimeout(r, 1000));
|
|
21
30
|
|
|
22
|
-
const content =
|
|
31
|
+
const content = `${this.prefix} ${String(this.idx++)}`;
|
|
23
32
|
const response: Choice = {
|
|
24
33
|
finish_reason: "stop",
|
|
25
34
|
index: 0,
|
|
@@ -36,11 +45,14 @@ export class RepeatLLM implements ILLM {
|
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
return {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
stop: () => {},
|
|
49
|
+
completion: Promise.resolve({
|
|
50
|
+
id: String(this.idx),
|
|
51
|
+
choices: [response],
|
|
52
|
+
created: Date.now(),
|
|
53
|
+
model: "dummyLlmModel",
|
|
54
|
+
object: "chat.completion",
|
|
55
|
+
}),
|
|
44
56
|
};
|
|
45
57
|
}
|
|
46
58
|
|