js-agent-core 1.0.1

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 (168) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +29 -0
  3. package/dist/core/AgentDashboard.d.ts +33 -0
  4. package/dist/core/AgentDashboard.d.ts.map +1 -0
  5. package/dist/core/AgentDashboard.js +477 -0
  6. package/dist/core/AgentDashboard.js.map +1 -0
  7. package/dist/core/AutoVectorStore.d.ts +23 -0
  8. package/dist/core/AutoVectorStore.d.ts.map +1 -0
  9. package/dist/core/AutoVectorStore.js +55 -0
  10. package/dist/core/AutoVectorStore.js.map +1 -0
  11. package/dist/core/BaseAgent.d.ts +70 -0
  12. package/dist/core/BaseAgent.d.ts.map +1 -0
  13. package/dist/core/BaseAgent.js +583 -0
  14. package/dist/core/BaseAgent.js.map +1 -0
  15. package/dist/core/EventEmitter.d.ts +8 -0
  16. package/dist/core/EventEmitter.d.ts.map +1 -0
  17. package/dist/core/EventEmitter.js +32 -0
  18. package/dist/core/EventEmitter.js.map +1 -0
  19. package/dist/core/LocalEmbedder.d.ts +25 -0
  20. package/dist/core/LocalEmbedder.d.ts.map +1 -0
  21. package/dist/core/LocalEmbedder.js +62 -0
  22. package/dist/core/LocalEmbedder.js.map +1 -0
  23. package/dist/core/LongTermMemory.d.ts +30 -0
  24. package/dist/core/LongTermMemory.d.ts.map +1 -0
  25. package/dist/core/LongTermMemory.js +123 -0
  26. package/dist/core/LongTermMemory.js.map +1 -0
  27. package/dist/core/MemoryVectorStore.d.ts +17 -0
  28. package/dist/core/MemoryVectorStore.d.ts.map +1 -0
  29. package/dist/core/MemoryVectorStore.js +44 -0
  30. package/dist/core/MemoryVectorStore.js.map +1 -0
  31. package/dist/core/OpenAIProvider.d.ts +21 -0
  32. package/dist/core/OpenAIProvider.d.ts.map +1 -0
  33. package/dist/core/OpenAIProvider.js +254 -0
  34. package/dist/core/OpenAIProvider.js.map +1 -0
  35. package/dist/core/SimpleMemory.d.ts +12 -0
  36. package/dist/core/SimpleMemory.d.ts.map +1 -0
  37. package/dist/core/SimpleMemory.js +27 -0
  38. package/dist/core/SimpleMemory.js.map +1 -0
  39. package/dist/core/StructuredPlanner.d.ts +13 -0
  40. package/dist/core/StructuredPlanner.d.ts.map +1 -0
  41. package/dist/core/StructuredPlanner.js +156 -0
  42. package/dist/core/StructuredPlanner.js.map +1 -0
  43. package/dist/core/ToolRegistry.d.ts +18 -0
  44. package/dist/core/ToolRegistry.d.ts.map +1 -0
  45. package/dist/core/ToolRegistry.js +74 -0
  46. package/dist/core/ToolRegistry.js.map +1 -0
  47. package/dist/core/index.d.ts +14 -0
  48. package/dist/core/index.d.ts.map +1 -0
  49. package/dist/core/index.js +14 -0
  50. package/dist/core/index.js.map +1 -0
  51. package/dist/core/logging/CompositeLogger.d.ts +8 -0
  52. package/dist/core/logging/CompositeLogger.d.ts.map +1 -0
  53. package/dist/core/logging/CompositeLogger.js +23 -0
  54. package/dist/core/logging/CompositeLogger.js.map +1 -0
  55. package/dist/core/logging/NodeFsLogger.d.ts +12 -0
  56. package/dist/core/logging/NodeFsLogger.d.ts.map +1 -0
  57. package/dist/core/logging/NodeFsLogger.js +46 -0
  58. package/dist/core/logging/NodeFsLogger.js.map +1 -0
  59. package/dist/core/logging/WebIndexedDbLogger.d.ts +15 -0
  60. package/dist/core/logging/WebIndexedDbLogger.d.ts.map +1 -0
  61. package/dist/core/logging/WebIndexedDbLogger.js +65 -0
  62. package/dist/core/logging/WebIndexedDbLogger.js.map +1 -0
  63. package/dist/core/logging/index.d.ts +11 -0
  64. package/dist/core/logging/index.d.ts.map +1 -0
  65. package/dist/core/logging/index.js +33 -0
  66. package/dist/core/logging/index.js.map +1 -0
  67. package/dist/core/persistence/NodeFsSkillStore.d.ts +17 -0
  68. package/dist/core/persistence/NodeFsSkillStore.d.ts.map +1 -0
  69. package/dist/core/persistence/NodeFsSkillStore.js +124 -0
  70. package/dist/core/persistence/NodeFsSkillStore.js.map +1 -0
  71. package/dist/core/persistence/NodeFsVectorStore.d.ts +25 -0
  72. package/dist/core/persistence/NodeFsVectorStore.d.ts.map +1 -0
  73. package/dist/core/persistence/NodeFsVectorStore.js +74 -0
  74. package/dist/core/persistence/NodeFsVectorStore.js.map +1 -0
  75. package/dist/core/persistence/SkillLoader.d.ts +26 -0
  76. package/dist/core/persistence/SkillLoader.d.ts.map +1 -0
  77. package/dist/core/persistence/SkillLoader.js +144 -0
  78. package/dist/core/persistence/SkillLoader.js.map +1 -0
  79. package/dist/core/persistence/WebIndexedDbSkillStore.d.ts +21 -0
  80. package/dist/core/persistence/WebIndexedDbSkillStore.d.ts.map +1 -0
  81. package/dist/core/persistence/WebIndexedDbSkillStore.js +119 -0
  82. package/dist/core/persistence/WebIndexedDbSkillStore.js.map +1 -0
  83. package/dist/core/persistence/WebIndexedDbVectorStore.d.ts +30 -0
  84. package/dist/core/persistence/WebIndexedDbVectorStore.d.ts.map +1 -0
  85. package/dist/core/persistence/WebIndexedDbVectorStore.js +87 -0
  86. package/dist/core/persistence/WebIndexedDbVectorStore.js.map +1 -0
  87. package/dist/core/persistence/index.d.ts +6 -0
  88. package/dist/core/persistence/index.d.ts.map +1 -0
  89. package/dist/core/persistence/index.js +21 -0
  90. package/dist/core/persistence/index.js.map +1 -0
  91. package/dist/index.d.ts +3 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +3 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/interfaces/IAgentEvent.d.ts +27 -0
  96. package/dist/interfaces/IAgentEvent.d.ts.map +1 -0
  97. package/dist/interfaces/IAgentEvent.js +2 -0
  98. package/dist/interfaces/IAgentEvent.js.map +1 -0
  99. package/dist/interfaces/IAgentState.d.ts +22 -0
  100. package/dist/interfaces/IAgentState.d.ts.map +1 -0
  101. package/dist/interfaces/IAgentState.js +2 -0
  102. package/dist/interfaces/IAgentState.js.map +1 -0
  103. package/dist/interfaces/ILLMProvider.d.ts +43 -0
  104. package/dist/interfaces/ILLMProvider.d.ts.map +1 -0
  105. package/dist/interfaces/ILLMProvider.js +2 -0
  106. package/dist/interfaces/ILLMProvider.js.map +1 -0
  107. package/dist/interfaces/ILogger.d.ts +15 -0
  108. package/dist/interfaces/ILogger.d.ts.map +1 -0
  109. package/dist/interfaces/ILogger.js +2 -0
  110. package/dist/interfaces/ILogger.js.map +1 -0
  111. package/dist/interfaces/IMemory.d.ts +32 -0
  112. package/dist/interfaces/IMemory.d.ts.map +1 -0
  113. package/dist/interfaces/IMemory.js +2 -0
  114. package/dist/interfaces/IMemory.js.map +1 -0
  115. package/dist/interfaces/IPlanner.d.ts +20 -0
  116. package/dist/interfaces/IPlanner.d.ts.map +1 -0
  117. package/dist/interfaces/IPlanner.js +2 -0
  118. package/dist/interfaces/IPlanner.js.map +1 -0
  119. package/dist/interfaces/ISkill.d.ts +9 -0
  120. package/dist/interfaces/ISkill.d.ts.map +1 -0
  121. package/dist/interfaces/ISkill.js +2 -0
  122. package/dist/interfaces/ISkill.js.map +1 -0
  123. package/dist/interfaces/ISkillStore.d.ts +53 -0
  124. package/dist/interfaces/ISkillStore.d.ts.map +1 -0
  125. package/dist/interfaces/ISkillStore.js +2 -0
  126. package/dist/interfaces/ISkillStore.js.map +1 -0
  127. package/dist/interfaces/ITool.d.ts +32 -0
  128. package/dist/interfaces/ITool.d.ts.map +1 -0
  129. package/dist/interfaces/ITool.js +2 -0
  130. package/dist/interfaces/ITool.js.map +1 -0
  131. package/dist/interfaces/index.d.ts +10 -0
  132. package/dist/interfaces/index.d.ts.map +1 -0
  133. package/dist/interfaces/index.js +10 -0
  134. package/dist/interfaces/index.js.map +1 -0
  135. package/package.json +47 -0
  136. package/src/core/AgentDashboard.ts +533 -0
  137. package/src/core/AutoVectorStore.ts +60 -0
  138. package/src/core/BaseAgent.ts +676 -0
  139. package/src/core/EventEmitter.ts +35 -0
  140. package/src/core/LocalEmbedder.ts +68 -0
  141. package/src/core/LongTermMemory.ts +146 -0
  142. package/src/core/MemoryVectorStore.ts +54 -0
  143. package/src/core/OpenAIProvider.ts +274 -0
  144. package/src/core/SimpleMemory.ts +31 -0
  145. package/src/core/StructuredPlanner.ts +165 -0
  146. package/src/core/ToolRegistry.ts +89 -0
  147. package/src/core/index.ts +16 -0
  148. package/src/core/logging/CompositeLogger.ts +26 -0
  149. package/src/core/logging/NodeFsLogger.ts +53 -0
  150. package/src/core/logging/WebIndexedDbLogger.ts +76 -0
  151. package/src/core/logging/index.ts +35 -0
  152. package/src/core/persistence/NodeFsSkillStore.ts +138 -0
  153. package/src/core/persistence/NodeFsVectorStore.ts +86 -0
  154. package/src/core/persistence/SkillLoader.ts +153 -0
  155. package/src/core/persistence/WebIndexedDbSkillStore.ts +139 -0
  156. package/src/core/persistence/WebIndexedDbVectorStore.ts +106 -0
  157. package/src/core/persistence/index.ts +22 -0
  158. package/src/index.ts +2 -0
  159. package/src/interfaces/IAgentEvent.ts +46 -0
  160. package/src/interfaces/IAgentState.ts +22 -0
  161. package/src/interfaces/ILLMProvider.ts +47 -0
  162. package/src/interfaces/ILogger.ts +16 -0
  163. package/src/interfaces/IMemory.ts +29 -0
  164. package/src/interfaces/IPlanner.ts +22 -0
  165. package/src/interfaces/ISkill.ts +9 -0
  166. package/src/interfaces/ISkillStore.ts +60 -0
  167. package/src/interfaces/ITool.ts +38 -0
  168. package/src/interfaces/index.ts +10 -0
@@ -0,0 +1,165 @@
1
+ import { IPlanner, TaskList, ITool, ILLMProvider, LLMMessage, LLMResponse } from '../interfaces/index.js';
2
+
3
+ export class StructuredPlanner implements IPlanner {
4
+ private provider: ILLMProvider;
5
+ private model: string;
6
+ private stream: boolean;
7
+
8
+ constructor(provider: ILLMProvider, model: string, stream: boolean = true) {
9
+ this.provider = provider;
10
+ this.model = model;
11
+ this.stream = stream;
12
+ }
13
+
14
+ async plan(input: string, tools: ITool[], context?: string, onChunk?: (chunk: string) => void): Promise<TaskList> {
15
+ const systemPrompt = this.getPlanningSystemPrompt(tools);
16
+ const messages: LLMMessage[] = [
17
+ { role: 'system', content: systemPrompt },
18
+ { role: 'user', content: `User Input: ${input}\nContext: ${context || 'None'}` }
19
+ ];
20
+
21
+ let response: LLMResponse;
22
+ let fullContent = '';
23
+
24
+ if (this.provider.stream && this.stream) {
25
+ response = await this.provider.stream(messages, {
26
+ model: this.model,
27
+ temperature: 0,
28
+ stream: true,
29
+ intent: 'plan',
30
+ }, (chunk) => {
31
+ if (chunk.content) {
32
+ fullContent += chunk.content;
33
+ if (onChunk) onChunk(chunk.content);
34
+ }
35
+ });
36
+ } else {
37
+ response = await this.provider.generate(messages, {
38
+ model: this.model,
39
+ temperature: 0,
40
+ stream: false,
41
+ intent: 'plan',
42
+ });
43
+ fullContent = response.content;
44
+ }
45
+
46
+ try {
47
+ const taskList = JSON.parse(this.cleanJsonResponse(fullContent));
48
+ return this.validateAndNormalizeTaskList(taskList);
49
+ } catch (error: any) {
50
+ console.error('Failed to parse planning response:', fullContent);
51
+ throw new Error(`Planning failed: ${error instanceof Error ? error.message : String(error)}`);
52
+ }
53
+ }
54
+
55
+ async replan(input: string, tools: ITool[], currentTaskList: TaskList, errorContext: string, onChunk?: (chunk: string) => void): Promise<TaskList> {
56
+ const systemPrompt = this.getPlanningSystemPrompt(tools);
57
+ const messages: LLMMessage[] = [
58
+ { role: 'system', content: systemPrompt },
59
+ {
60
+ role: 'user',
61
+ content: `
62
+ Original Input: ${input}
63
+ Current Task List: ${JSON.stringify(currentTaskList)}
64
+ Execution Error/Feedback: ${errorContext}
65
+ Please provide a revised task list to solve the original input considering the error.`
66
+ }
67
+ ];
68
+
69
+ let response: LLMResponse;
70
+ let fullContent = '';
71
+ if (this.provider.stream && this.stream) {
72
+ response = await this.provider.stream(messages, {
73
+ model: this.model,
74
+ temperature: 0,
75
+ stream: true,
76
+ intent: 'replan',
77
+ }, (chunk) => {
78
+ if (chunk.content) {
79
+ fullContent += chunk.content;
80
+ if (onChunk) onChunk(chunk.content);
81
+ }
82
+ });
83
+ } else {
84
+ response = await this.provider.generate(messages, {
85
+ model: this.model,
86
+ temperature: 0,
87
+ stream: false,
88
+ intent: 'replan',
89
+ });
90
+ fullContent = response.content;
91
+ }
92
+
93
+ try {
94
+ const taskList = JSON.parse(this.cleanJsonResponse(fullContent));
95
+ return this.validateAndNormalizeTaskList(taskList);
96
+ } catch (error: any) {
97
+ console.error('Failed to parse planning response:', fullContent);
98
+ throw new Error(`Planning failed: ${error instanceof Error ? error.message : String(error)}`);
99
+ }
100
+ }
101
+
102
+ private getPlanningSystemPrompt(tools: ITool[], context?: string): string {
103
+ const toolDescriptions = tools.map(t => `${t.name}: ${t.description}`).join('\n');
104
+ return `You are a structured planner for an AI agent.
105
+ Your goal is to break down a complex user request into a sequence of goal-oriented tasks.
106
+
107
+ AVAILABLE TOOLS (for your awareness of what's possible, but DO NOT assign tools to tasks):
108
+ ${toolDescriptions}
109
+
110
+ ${context ? `CONTEXT:\n${context}\n` : ''}
111
+
112
+ PLANNING RULES:
113
+ 1. Break down the request into discrete tasks.
114
+ 2. For each task, clearly describe its 'goal'.
115
+ 3. For each task, define strict 'criteria' (Definition of Done) - how will the execution engine know this task is completed? The criteria must be objective and verifiable.
116
+ 4. Define dependencies between tasks (e.g., task 2 depends on task 1's result).
117
+ 5. If the user's request is a simple greeting or doesn't require any tasks, set 'isCompleted' to true and provide a direct response in the 'response' field.
118
+ 6. Provide your plan in JSON format.
119
+
120
+ Thinking Process:
121
+ You should ALWAYS wrap your internal thinking process in <think> tags before the JSON output.
122
+ Example:
123
+ <think>The user just said hello. This doesn't require any tools. I will provide a direct greeting response.</think>
124
+ {"tasks": [], "isCompleted": true, "response": "Hello! How can I help you today?"}
125
+
126
+ JSON STRUCTURE:
127
+ {
128
+ "tasks": [
129
+ { "id": "1", "goal": "...", "criteria": "...", "dependsOn": [] },
130
+ ...
131
+ ],
132
+ "isCompleted": false,
133
+ "response": "optional response string"
134
+ }`;
135
+ }
136
+
137
+ private cleanJsonResponse(content: string): string {
138
+ // 1. Remove <think> tags and their content
139
+ let cleaned = content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
140
+
141
+ // 2. Try to extract content from markdown code blocks (handling any language tag like json, javascript, etc.)
142
+ const codeBlockRegex = /```(?:[a-zA-Z]*)\s*([\s\S]*?)\s*```/;
143
+ const match = cleaned.match(codeBlockRegex);
144
+ if (match) {
145
+ cleaned = match[1].trim();
146
+ }
147
+
148
+ // 3. Fallback: extract the JSON object by finding the first '{' and last '}'
149
+ // This is useful if the model includes text before/after the JSON or if markdown extraction is messy.
150
+ const start = cleaned.indexOf('{');
151
+ const end = cleaned.lastIndexOf('}');
152
+ if (start !== -1 && end !== -1 && end > start) {
153
+ cleaned = cleaned.substring(start, end + 1);
154
+ }
155
+
156
+ return cleaned.trim();
157
+ }
158
+
159
+ private validateAndNormalizeTaskList(data: any): TaskList {
160
+ if (!data.tasks || !Array.isArray(data.tasks)) {
161
+ throw new Error('Invalid task list format: missing tasks array');
162
+ }
163
+ return data;
164
+ }
165
+ }
@@ -0,0 +1,89 @@
1
+ import { ITool, ISkill, ToolResult } from '../interfaces/index.js';
2
+ import { ZodError } from 'zod';
3
+
4
+ export class ToolRegistry {
5
+ private tools: Map<string, ITool> = new Map();
6
+
7
+ registerTool(tool: ITool): void {
8
+ if (this.tools.has(tool.name)) {
9
+ throw new Error(`Tool with name '${tool.name}' is already registered.`);
10
+ }
11
+ this.tools.set(tool.name, tool);
12
+ }
13
+
14
+ registerSkill(skill: ISkill): void {
15
+ skill.tools.forEach(tool => {
16
+ this.registerTool(tool);
17
+ });
18
+ }
19
+
20
+ getTool(name: string): ITool | undefined {
21
+ return this.tools.get(name);
22
+ }
23
+
24
+ getAllTools(): ITool[] {
25
+ return Array.from(this.tools.values());
26
+ }
27
+
28
+ hasTool(name: string): boolean {
29
+ return this.tools.has(name);
30
+ }
31
+
32
+ /**
33
+ * 校验并执行工具
34
+ * @param name 工具名称
35
+ * @param argsRaw 原始参数字符串或对象
36
+ * @param context 执行上下文
37
+ * @param onProgress 进度反馈回调
38
+ */
39
+ async validateAndExecute(
40
+ name: string,
41
+ argsRaw: string | object,
42
+ context?: any,
43
+ onProgress?: (percent: number, message: string) => void
44
+ ): Promise<ToolResult> {
45
+ const tool = this.getTool(name);
46
+ if (!tool) {
47
+ return {
48
+ success: false,
49
+ error: `Tool '${name}' not found.`
50
+ };
51
+ }
52
+
53
+ let args: any;
54
+ try {
55
+ args = typeof argsRaw === 'string' ? JSON.parse(argsRaw) : argsRaw;
56
+ } catch (e) {
57
+ return {
58
+ success: false,
59
+ error: `Invalid JSON format in tool arguments: ${tool.name}. Please ensure you provide a valid JSON object.`
60
+ };
61
+ }
62
+
63
+ try {
64
+ // 使用 zod 进行校验
65
+ const validatedArgs = tool.parameters.parse(args);
66
+ return await tool.execute(validatedArgs, context, onProgress);
67
+ } catch (error) {
68
+ if (error instanceof ZodError) {
69
+ // 格式化 zod 错误,提供更智能的修复建议
70
+ const issues = error.issues.map(issue => {
71
+ const path = issue.path.join('.');
72
+ return `- [${path}]: ${issue.message} (Expected ${issue.code})`;
73
+ }).join('\n');
74
+
75
+ const suggestion = `The parameters for tool '${name}' are invalid.\n\nIssues found:\n${issues}\n\nPlease correct these parameters and try again. Ensure the arguments match the required schema: ${JSON.stringify((tool.parameters as any)._def.typeName || 'Object Schema')}`;
76
+
77
+ return {
78
+ success: false,
79
+ error: suggestion
80
+ };
81
+ }
82
+
83
+ return {
84
+ success: false,
85
+ error: `Validation failed for tool '${name}': ${(error as Error).message}`
86
+ };
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,16 @@
1
+ export * from './BaseAgent.js';
2
+ export * from './EventEmitter.js';
3
+ export * from './ToolRegistry.js';
4
+ export * from './OpenAIProvider.js';
5
+ export * from './SimpleMemory.js';
6
+ export * from './LongTermMemory.js';
7
+ export * from './LocalEmbedder.js';
8
+ export * from './MemoryVectorStore.js';
9
+ export * from './AutoVectorStore.js';
10
+ export * from './StructuredPlanner.js';
11
+ export * from './AgentDashboard.js';
12
+ export * from './persistence/index.js';
13
+ export * from './persistence/NodeFsSkillStore.js';
14
+
15
+
16
+
@@ -0,0 +1,26 @@
1
+ import { ILogger, LogLevel, LogContext } from '../../interfaces/index.js';
2
+
3
+ export class CompositeLogger implements ILogger {
4
+ private loggers: ILogger[];
5
+
6
+ constructor(loggers: ILogger[]) {
7
+ this.loggers = loggers.filter(Boolean);
8
+ }
9
+
10
+ log(level: LogLevel, message: string, context?: LogContext): void {
11
+ for (const logger of this.loggers) {
12
+ try {
13
+ const result = logger.log(level, message, context);
14
+ if (result instanceof Promise) {
15
+ result.catch(err => console.error('CompositeLogger item failed:', err));
16
+ }
17
+ } catch (err) {
18
+ console.error('CompositeLogger item failed synchronously:', err);
19
+ }
20
+ }
21
+ }
22
+
23
+ addLogger(logger: ILogger) {
24
+ this.loggers.push(logger);
25
+ }
26
+ }
@@ -0,0 +1,53 @@
1
+ import { ILogger, LogLevel, LogContext } from '../../interfaces/index.js';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ export class NodeFsLogger implements ILogger {
6
+ private logDir: string;
7
+ private currentFile: string;
8
+
9
+ constructor(options: { logDir?: string } = {}) {
10
+ this.logDir = options.logDir || path.join(process.cwd(), 'logs');
11
+ this.currentFile = path.join(this.logDir, `agent-${new Date().toISOString().split('T')[0]}.log`);
12
+ this.init();
13
+ }
14
+
15
+ private init() {
16
+ if (!fs.existsSync(this.logDir)) {
17
+ fs.mkdirSync(this.logDir, { recursive: true });
18
+ }
19
+ }
20
+
21
+ log(level: LogLevel, message: string, context?: LogContext): void {
22
+ const timestamp = new Date().toISOString();
23
+ const logEntry = {
24
+ timestamp,
25
+ level,
26
+ message,
27
+ ...context
28
+ };
29
+
30
+ const logLine = JSON.stringify(logEntry) + '\n';
31
+
32
+ // 使用同步追加以简化,但在高并发场景下建议使用流
33
+ try {
34
+ fs.appendFileSync(this.currentFile, logLine);
35
+ } catch (err) {
36
+ console.error('Failed to write to log file:', err);
37
+ }
38
+
39
+ // 同时输出到控制台以便开发调试
40
+ const color = this.getColor(level);
41
+ console.log(`${color}[${timestamp}] [${level.toUpperCase()}] ${message}\x1b[0m`, context || '');
42
+ }
43
+
44
+ private getColor(level: LogLevel): string {
45
+ switch (level) {
46
+ case 'debug': return '\x1b[90m'; // Gray
47
+ case 'info': return '\x1b[32m'; // Green
48
+ case 'warn': return '\x1b[33m'; // Yellow
49
+ case 'error': return '\x1b[31m'; // Red
50
+ default: return '\x1b[0m';
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,76 @@
1
+ import { ILogger, LogLevel, LogContext } from '../../interfaces/index.js';
2
+
3
+ export class WebIndexedDbLogger implements ILogger {
4
+ private dbName: string;
5
+ private storeName: string;
6
+ private db: IDBDatabase | null = null;
7
+ private initPromise: Promise<void> | null = null;
8
+
9
+ constructor(options: { dbName?: string; storeName?: string } = {}) {
10
+ this.dbName = options.dbName || 'js-agent-logs';
11
+ this.storeName = options.storeName || 'logs';
12
+ this.init();
13
+ }
14
+
15
+ private init(): Promise<void> {
16
+ if (this.initPromise) return this.initPromise;
17
+
18
+ this.initPromise = new Promise((resolve, reject) => {
19
+ const request = indexedDB.open(this.dbName, 1);
20
+
21
+ request.onupgradeneeded = (event) => {
22
+ const db = (event.target as IDBOpenDBRequest).result;
23
+ if (!db.objectStoreNames.contains(this.storeName)) {
24
+ const store = db.createObjectStore(this.storeName, { autoIncrement: true });
25
+ store.createIndex('timestamp', 'timestamp', { unique: false });
26
+ store.createIndex('level', 'level', { unique: false });
27
+ store.createIndex('traceId', 'traceId', { unique: false });
28
+ store.createIndex('agentId', 'agentId', { unique: false });
29
+ }
30
+ };
31
+
32
+ request.onsuccess = (event) => {
33
+ this.db = (event.target as IDBOpenDBRequest).result;
34
+ resolve();
35
+ };
36
+
37
+ request.onerror = (event) => {
38
+ console.error('IndexedDB error:', (event.target as IDBOpenDBRequest).error);
39
+ reject((event.target as IDBOpenDBRequest).error);
40
+ };
41
+ });
42
+
43
+ return this.initPromise;
44
+ }
45
+
46
+ async log(level: LogLevel, message: string, context?: LogContext): Promise<void> {
47
+ await this.init();
48
+ if (!this.db) return;
49
+
50
+ const timestamp = new Date().toISOString();
51
+ const logEntry = {
52
+ timestamp,
53
+ level,
54
+ message,
55
+ ...context
56
+ };
57
+
58
+ const transaction = this.db.transaction([this.storeName], 'readwrite');
59
+ const store = transaction.objectStore(this.storeName);
60
+ store.add(logEntry);
61
+
62
+ // 浏览器环境下通常也需要 Console 输出
63
+ const color = this.getColor(level);
64
+ console.log(`${color}[${timestamp}] [${level.toUpperCase()}] ${message}\x1b[0m`, context || '');
65
+ }
66
+
67
+ private getColor(level: LogLevel): string {
68
+ switch (level) {
69
+ case 'debug': return '\x1b[90m'; // Gray
70
+ case 'info': return '\x1b[32m'; // Green
71
+ case 'warn': return '\x1b[33m'; // Yellow
72
+ case 'error': return '\x1b[31m'; // Red
73
+ default: return '\x1b[0m';
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,35 @@
1
+ import { ILogger } from '../../interfaces/index.js';
2
+
3
+ export class LoggerFactory {
4
+ /**
5
+ * 根据当前运行环境创建默认的持久化日志记录器
6
+ */
7
+ static async createDefaultLogger(): Promise<ILogger | null> {
8
+ // 1. 探测 Node.js 环境
9
+ if (typeof process !== 'undefined' && process.versions?.node) {
10
+ try {
11
+ const { NodeFsLogger } = await import('./NodeFsLogger.js');
12
+ return new NodeFsLogger();
13
+ } catch (e) {
14
+ console.warn('Failed to initialize NodeFsLogger, falling back to console:', e);
15
+ }
16
+ }
17
+
18
+ // 2. 探测浏览器环境 (支持 IndexedDB)
19
+ if (typeof window !== 'undefined' && typeof indexedDB !== 'undefined') {
20
+ try {
21
+ const { WebIndexedDbLogger } = await import('./WebIndexedDbLogger.js');
22
+ return new WebIndexedDbLogger();
23
+ } catch (e) {
24
+ console.warn('Failed to initialize WebIndexedDbLogger, falling back to console:', e);
25
+ }
26
+ }
27
+
28
+ // 3. 回退逻辑:如果没有任何持久化环境(如某些极简 Sandbox),返回 null,BaseAgent 会默认使用 console
29
+ return null;
30
+ }
31
+ }
32
+
33
+ export * from './NodeFsLogger.js';
34
+ export * from './WebIndexedDbLogger.js';
35
+ export * from './CompositeLogger.js';
@@ -0,0 +1,138 @@
1
+ import { ISkillStore, SkillFile, SkillMetadata } from '../../interfaces/index.js';
2
+
3
+ /**
4
+ * 后端实现:基于文件系统的目录隔离存储
5
+ */
6
+ export class NodeFsSkillStore implements ISkillStore {
7
+ constructor(private baseDir: string = '') {
8
+ if (!this.baseDir) {
9
+ // 延迟加载 path 以避免浏览器报错
10
+ this.initDefaultDir();
11
+ }
12
+ }
13
+
14
+ private async initDefaultDir() {
15
+ if (typeof process !== 'undefined' && process.versions?.node) {
16
+ const path = await import('path');
17
+ this.baseDir = path.join(process.cwd(), 'storage', 'skills');
18
+ }
19
+ }
20
+
21
+ private async ensureDir(dir: string): Promise<void> {
22
+ const fs = await import('fs/promises');
23
+ try {
24
+ await fs.access(dir);
25
+ } catch {
26
+ await fs.mkdir(dir, { recursive: true });
27
+ }
28
+ }
29
+
30
+ private async getSkillDir(skillName: string): Promise<string> {
31
+ const path = await import('path');
32
+ return path.join(this.baseDir, skillName);
33
+ }
34
+
35
+ async saveSkill(metadata: SkillMetadata, files: SkillFile[]): Promise<void> {
36
+ const fs = await import('fs/promises');
37
+ const path = await import('path');
38
+
39
+ const skillDir = await this.getSkillDir(metadata.name);
40
+ await this.ensureDir(skillDir);
41
+
42
+ // 1. 保存元数据
43
+ const metaPath = path.join(skillDir, 'metadata.json');
44
+ await fs.writeFile(metaPath, JSON.stringify(metadata, null, 2), 'utf-8');
45
+
46
+ // 2. 保存所有文件
47
+ for (const file of files) {
48
+ const filePath = path.join(skillDir, file.path);
49
+ const fileDir = path.dirname(filePath);
50
+ await this.ensureDir(fileDir);
51
+
52
+ if (typeof file.content === 'string') {
53
+ await fs.writeFile(filePath, file.content, 'utf-8');
54
+ } else {
55
+ // Uint8Array is directly supported by fs.writeFile in Node.js
56
+ await fs.writeFile(filePath, file.content);
57
+ }
58
+ }
59
+ }
60
+
61
+ async getSkillFiles(skillName: string): Promise<SkillFile[]> {
62
+ const fs = await import('fs/promises');
63
+ const path = await import('path');
64
+
65
+ const skillDir = await this.getSkillDir(skillName);
66
+ const files: SkillFile[] = [];
67
+
68
+ const readDirRecursive = async (currentDir: string, relativePath: string = '') => {
69
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
70
+
71
+ for (const entry of entries) {
72
+ const fullPath = path.join(currentDir, entry.name);
73
+ const relPath = path.join(relativePath, entry.name);
74
+
75
+ if (entry.name === 'metadata.json' && relativePath === '') continue; // 跳过元数据文件
76
+
77
+ if (entry.isDirectory()) {
78
+ await readDirRecursive(fullPath, relPath);
79
+ } else {
80
+ const buffer = await fs.readFile(fullPath);
81
+ // Convert Buffer to Uint8Array to satisfy the interface
82
+ const content = new Uint8Array(buffer);
83
+ files.push({
84
+ path: relPath,
85
+ content: content
86
+ });
87
+ }
88
+ }
89
+ };
90
+
91
+ try {
92
+ await readDirRecursive(skillDir);
93
+ } catch (e) {
94
+ console.error(`Failed to read files for skill ${skillName}:`, e);
95
+ }
96
+
97
+ return files;
98
+ }
99
+
100
+ async getSkillMetadata(skillName: string): Promise<SkillMetadata | null> {
101
+ const fs = await import('fs/promises');
102
+ const path = await import('path');
103
+
104
+ const metaPath = path.join(await this.getSkillDir(skillName), 'metadata.json');
105
+ try {
106
+ const content = await fs.readFile(metaPath, 'utf-8');
107
+ return JSON.parse(content);
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+
113
+ async listSkills(): Promise<SkillMetadata[]> {
114
+ const fs = await import('fs/promises');
115
+ await this.ensureDir(this.baseDir);
116
+ const skillNames = await fs.readdir(this.baseDir);
117
+ const results: SkillMetadata[] = [];
118
+
119
+ for (const name of skillNames) {
120
+ const meta = await this.getSkillMetadata(name);
121
+ if (meta) {
122
+ results.push(meta);
123
+ }
124
+ }
125
+
126
+ return results;
127
+ }
128
+
129
+ async deleteSkill(skillName: string): Promise<void> {
130
+ const fs = await import('fs/promises');
131
+ const skillDir = await this.getSkillDir(skillName);
132
+ try {
133
+ await fs.rm(skillDir, { recursive: true, force: true });
134
+ } catch (e) {
135
+ console.error(`Failed to delete skill ${skillName}:`, e);
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,86 @@
1
+ import { IVectorStore } from '../../interfaces/index.js';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+
5
+ export interface NodeFsVectorStoreOptions {
6
+ storagePath?: string;
7
+ }
8
+
9
+ export class NodeFsVectorStore implements IVectorStore {
10
+ private items: Array<{ text: string; embedding: number[]; metadata?: Record<string, any> }> = [];
11
+ private storagePath: string;
12
+
13
+ constructor(options: NodeFsVectorStoreOptions = {}) {
14
+ this.storagePath = options.storagePath || path.join(process.cwd(), '.agent_data', 'vector_store.json');
15
+ }
16
+
17
+ private async ensureDirectory(): Promise<void> {
18
+ const dir = path.dirname(this.storagePath);
19
+ try {
20
+ await fs.access(dir);
21
+ } catch {
22
+ await fs.mkdir(dir, { recursive: true });
23
+ }
24
+ }
25
+
26
+ private async load(): Promise<void> {
27
+ try {
28
+ const data = await fs.readFile(this.storagePath, 'utf-8');
29
+ this.items = JSON.parse(data);
30
+ } catch {
31
+ this.items = [];
32
+ }
33
+ }
34
+
35
+ private async save(): Promise<void> {
36
+ await this.ensureDirectory();
37
+ await fs.writeFile(this.storagePath, JSON.stringify(this.items, null, 2), 'utf-8');
38
+ }
39
+
40
+ async add(text: string, embedding: number[], metadata?: Record<string, any>): Promise<void> {
41
+ await this.load();
42
+ this.items.push({ text, embedding, metadata });
43
+ await this.save();
44
+ }
45
+
46
+ async search(queryEmbedding: number[], topK: number = 3): Promise<Array<{ text: string; score: number; metadata?: Record<string, any> }>> {
47
+ await this.load();
48
+ const scoredItems = this.items.map(item => ({
49
+ ...item,
50
+ score: this.cosineSimilarity(queryEmbedding, item.embedding)
51
+ }));
52
+
53
+ return scoredItems
54
+ .sort((a, b) => b.score - a.score)
55
+ .slice(0, topK)
56
+ .map(({ text, score, metadata }) => ({ text, score, metadata }));
57
+ }
58
+
59
+ async searchByKeyword(keyword: string, topK: number = 3): Promise<Array<{ text: string; score: number; metadata?: Record<string, any> }>> {
60
+ await this.load();
61
+ const lowKeyword = keyword.toLowerCase();
62
+
63
+ return this.items
64
+ .filter(item => item.text.toLowerCase().includes(lowKeyword))
65
+ .map(item => ({
66
+ text: item.text,
67
+ score: 1.0,
68
+ metadata: item.metadata
69
+ }))
70
+ .slice(0, topK);
71
+ }
72
+
73
+ private cosineSimilarity(vecA: number[], vecB: number[]): number {
74
+ if (vecA.length !== vecB.length) return 0;
75
+ let dotProduct = 0;
76
+ let normA = 0;
77
+ let normB = 0;
78
+ for (let i = 0; i < vecA.length; i++) {
79
+ dotProduct += vecA[i] * vecB[i];
80
+ normA += vecA[i] * vecA[i];
81
+ normB += vecB[i] * vecB[i];
82
+ }
83
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
84
+ return magnitude === 0 ? 0 : dotProduct / magnitude;
85
+ }
86
+ }