@amical/aiagent-sdk 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/index.d.mts +476 -0
  2. package/dist/index.d.ts +476 -0
  3. package/dist/index.js +968 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +951 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +7 -1
  8. package/.agents/skills/Viral_Writer/SKILL.md +0 -239
  9. package/.cline_memory.json +0 -58
  10. package/.node-version +0 -1
  11. package/Agents.md +0 -95
  12. package/src/agent/Agent.ts +0 -566
  13. package/src/agent/index.ts +0 -1
  14. package/src/index.ts +0 -15
  15. package/src/llm/NodeApiHandler.ts +0 -117
  16. package/src/llm/WebApiHandler.ts +0 -118
  17. package/src/llm/index.ts +0 -47
  18. package/src/mcp/NodeMcpManager.ts +0 -158
  19. package/src/mcp/WebMcpManager.ts +0 -30
  20. package/src/mcp/index.ts +0 -56
  21. package/src/memory/NodeMemoryManager.ts +0 -82
  22. package/src/memory/WebMemoryManager.ts +0 -89
  23. package/src/memory/index.ts +0 -36
  24. package/src/parser/diff.ts +0 -855
  25. package/src/parser/index.ts +0 -111
  26. package/src/parser/parse-assistant-message.ts +0 -240
  27. package/src/prompt/NodePromptManager.ts +0 -276
  28. package/src/prompt/WebPromptManager.ts +0 -277
  29. package/src/prompt/index.ts +0 -28
  30. package/src/runtime/nodeRuntime.ts +0 -112
  31. package/src/runtime/web.test.ts +0 -95
  32. package/src/runtime/webRuntime.ts +0 -119
  33. package/src/runtime.ts +0 -46
  34. package/src/shared/tools.ts +0 -270
  35. package/test-node-agent.ts +0 -113
  36. package/test-runtime.mjs +0 -36
  37. package/test.md +0 -33
  38. package/tsconfig.json +0 -28
  39. package/tsup.config.ts +0 -21
  40. package/uapi.ts +0 -3
  41. package/webFetch.ts +0 -49
  42. package/webSearch.ts +0 -19
@@ -1,117 +0,0 @@
1
- import { IApiHandler, ApiStreamChunk, ApiProviderInfo } from './index';
2
- import { AgentMessage } from '../memory/index';
3
- import { streamText, ModelMessage } from 'ai';
4
- import { createOpenAI } from '@ai-sdk/openai';
5
- import { createDifyProvider } from 'dify-ai-provider';
6
-
7
- export interface NodeApiHandlerConfig {
8
- providerId?: string; // 标识,例如 'openai', 'deepseek', 'dify'
9
- modelId?: string;
10
- apiKey: string;
11
- baseURL?: string;
12
- headers?: Record<string, string>;
13
- fetch?: typeof fetch; // 允许注入自定义的 fetch 拦截器
14
- }
15
-
16
- /**
17
- * Node 端的 LLM API 处理器 (已通过 Vercel AI SDK 重构)
18
- * 支持灵活对接 OpenAI 及各类兼容 OpenAI 规范的平台 (DeepSeek, Dify 等)
19
- */
20
- export class NodeApiHandler implements IApiHandler {
21
- private config: NodeApiHandlerConfig;
22
- private conversationId?: string;
23
-
24
- constructor(config: NodeApiHandlerConfig) {
25
- this.config = config;
26
- }
27
-
28
- getModelInfo(): ApiProviderInfo {
29
- return {
30
- providerId: this.config.providerId || 'openai-compatible',
31
- modelId: this.config.modelId,
32
- };
33
- }
34
-
35
- async *createMessage(systemPrompt: string, messages: AgentMessage[]): AsyncGenerator<ApiStreamChunk, void, unknown> {
36
- let model;
37
-
38
- if (this.config.providerId === 'dify') {
39
- const customDify = createDifyProvider({
40
- baseURL: this.config.baseURL,
41
- headers: this.config.headers,
42
- fetch: this.config.fetch,
43
- });
44
- model = customDify(this.config.modelId || '', {
45
- apiKey: this.config.apiKey,
46
- responseMode: 'streaming',
47
- });
48
- } else {
49
- const customOpenAI = createOpenAI({
50
- baseURL: this.config.baseURL,
51
- apiKey: this.config.apiKey,
52
- headers: this.config.headers,
53
- fetch: this.config.fetch,
54
- });
55
- model = customOpenAI(this.config.modelId || '');
56
- }
57
-
58
- try {
59
- const streamOptions: any = {
60
- model,
61
- headers: {
62
- ...this.config.headers,
63
- },
64
- allowSystemInMessages: true,
65
- };
66
-
67
- if (this.config.providerId === 'dify' && this.conversationId) {
68
- // Dify 自带记忆系统,使用 prompt 模式
69
- // 如果是第一次对话(messages 为空或只有一个 system 消息),需要带上 systemPrompt + 用户的需求
70
- // 如果不是第一次,则只传递当前最新的用户输入,Dify 会自动处理历史记录
71
- streamOptions.headers['chat-id'] = this.conversationId; // Dify 的 conversationId
72
- }
73
-
74
- streamOptions.messages = [
75
- { role: 'system', content: systemPrompt },
76
- ...(messages.map((m) => ({
77
- role: m.role === 'system' ? 'system' : m.role === 'user' ? 'user' : 'assistant',
78
- content: m.content
79
- })) as ModelMessage[]),
80
- ];
81
-
82
- // console.log('streamOptions.messages====', streamOptions.messages);
83
-
84
- const { fullStream } = streamText(streamOptions);
85
-
86
- for await (const part of fullStream) {
87
- if ((this.config.providerId === 'dify' && !this.conversationId && (part as any)?.providerMetadata?.difyWorkflowData?.conversationId) || (part as any)?.providerMetadata?.dify?.conversationId) {
88
- this.conversationId = (part as any).providerMetadata?.difyWorkflowData?.conversationId || (part as any).providerMetadata?.dify?.conversationId;
89
- }
90
- if (part.type === 'text-delta') {
91
- yield {
92
- type: 'text',
93
- text: (part as any).textDelta || part.text || '',
94
- };
95
- } else if (part.type === 'finish') {
96
- yield {
97
- type: 'usage',
98
- usage: {
99
- inputTokens: (part as any).usage?.promptTokens || (part as any).totalUsage?.promptTokens || (part as any).totalUsage?.inputTokens || 0,
100
- outputTokens: (part as any).usage?.completionTokens || (part as any).totalUsage?.completionTokens || (part as any).totalUsage?.outputTokens || 0,
101
- },
102
- };
103
- } else if (part.type === 'error') {
104
- yield {
105
- type: 'error',
106
- error: part.error as Error,
107
- };
108
- }
109
- }
110
- } catch (err: any) {
111
- yield {
112
- type: 'error',
113
- error: err,
114
- };
115
- }
116
- }
117
- }
@@ -1,118 +0,0 @@
1
- import { IApiHandler, ApiStreamChunk, ApiProviderInfo } from './index';
2
- import { AgentMessage } from '../memory/index';
3
- import { streamText, ModelMessage } from 'ai';
4
- import { createOpenAI } from '@ai-sdk/openai';
5
- import { createDifyProvider } from 'dify-ai-provider';
6
-
7
- export interface WebApiHandlerConfig {
8
- providerId?: string; // 标识,例如 'openai', 'deepseek', 'dify'
9
- modelId?: string;
10
- apiKey: string;
11
- baseURL?: string;
12
- headers?: Record<string, string>;
13
- fetch?: typeof fetch; // 允许注入自定义的 fetch 拦截器
14
- }
15
-
16
- /**
17
- * Web 端的 LLM API 处理器 (已通过 Vercel AI SDK 重构)
18
- * 支持灵活对接 OpenAI 及各类兼容 OpenAI 规范的平台 (DeepSeek, Dify 等)
19
- * 由于 Vercel AI SDK 底层统一使用标准的 Web Fetch API,Web 端的逻辑可以做到与 Node 端完全一致。
20
- */
21
- export class WebApiHandler implements IApiHandler {
22
- private config: WebApiHandlerConfig;
23
- private conversationId?: string;
24
-
25
- constructor(config: WebApiHandlerConfig) {
26
- this.config = config;
27
- }
28
-
29
- getModelInfo(): ApiProviderInfo {
30
- return {
31
- providerId: this.config.providerId || 'openai-compatible',
32
- modelId: this.config.modelId,
33
- };
34
- }
35
-
36
- async *createMessage(systemPrompt: string, messages: AgentMessage[]): AsyncGenerator<ApiStreamChunk, void, unknown> {
37
- let model;
38
-
39
- if (this.config.providerId === 'dify') {
40
- const customDify = createDifyProvider({
41
- baseURL: this.config.baseURL,
42
- headers: this.config.headers,
43
- fetch: this.config.fetch,
44
- });
45
- model = customDify(this.config.modelId || '', {
46
- apiKey: this.config.apiKey,
47
- responseMode: 'streaming',
48
- });
49
- } else {
50
- const customOpenAI = createOpenAI({
51
- baseURL: this.config.baseURL,
52
- apiKey: this.config.apiKey,
53
- headers: this.config.headers,
54
- fetch: this.config.fetch,
55
- });
56
- model = customOpenAI(this.config.modelId || '');
57
- }
58
-
59
- try {
60
- const streamOptions: any = {
61
- model,
62
- headers: {
63
- ...this.config.headers,
64
- },
65
- allowSystemInMessages: true,
66
- };
67
-
68
- if (this.config.providerId === 'dify' && this.conversationId) {
69
- // Dify 自带记忆系统,使用 prompt 模式
70
- // 如果是第一次对话(messages 为空或只有一个 system 消息),需要带上 systemPrompt + 用户的需求
71
- // 如果不是第一次,则只传递当前最新的用户输入,Dify 会自动处理历史记录
72
- streamOptions.headers['chat-id'] = this.conversationId; // Dify 的 conversationId
73
- }
74
-
75
- streamOptions.messages = [
76
- { role: 'system', content: systemPrompt },
77
- ...(messages.map((m) => ({
78
- role: m.role === 'system' ? 'system' : m.role === 'user' ? 'user' : 'assistant',
79
- content: m.content,
80
- })) as ModelMessage[]),
81
- ];
82
-
83
- // console.log('streamOptions.messages====', streamOptions.messages);
84
-
85
- const { fullStream } = streamText(streamOptions);
86
-
87
- for await (const part of fullStream) {
88
- if ((this.config.providerId === 'dify' && !this.conversationId && (part as any)?.providerMetadata?.difyWorkflowData?.conversationId) || (part as any)?.providerMetadata?.dify?.conversationId) {
89
- this.conversationId = (part as any).providerMetadata?.difyWorkflowData?.conversationId || (part as any).providerMetadata?.dify?.conversationId;
90
- }
91
- if (part.type === 'text-delta') {
92
- yield {
93
- type: 'text',
94
- text: (part as any).textDelta || part.text || '',
95
- };
96
- } else if (part.type === 'finish') {
97
- yield {
98
- type: 'usage',
99
- usage: {
100
- inputTokens: (part as any).usage?.promptTokens || (part as any).totalUsage?.promptTokens || (part as any).totalUsage?.inputTokens || 0,
101
- outputTokens: (part as any).usage?.completionTokens || (part as any).totalUsage?.completionTokens || (part as any).totalUsage?.outputTokens || 0,
102
- },
103
- };
104
- } else if (part.type === 'error') {
105
- yield {
106
- type: 'error',
107
- error: part.error as Error,
108
- };
109
- }
110
- }
111
- } catch (err: any) {
112
- yield {
113
- type: 'error',
114
- error: err,
115
- };
116
- }
117
- }
118
- }
package/src/llm/index.ts DELETED
@@ -1,47 +0,0 @@
1
- import { AgentMessage } from "../memory/index";
2
-
3
- /**
4
- * 统一的 API 响应流数据块
5
- */
6
- export interface ApiStreamChunk {
7
- type: "text" | "usage" | "error";
8
- text?: string;
9
- usage?: {
10
- inputTokens: number;
11
- outputTokens: number;
12
- };
13
- error?: Error;
14
- }
15
-
16
- /**
17
- * 通用的 LLM 提供者信息
18
- */
19
- export interface ApiProviderInfo {
20
- providerId: string;
21
- modelId?: string;
22
- }
23
-
24
- /**
25
- * 抽象的 LLM API 处理器接口。
26
- * 宿主环境(VSCode/Web/Node CLI)可以选择注入 AnthropicSDK、OpenAISDK 或是 Fetch 调用实现。
27
- */
28
- export interface IApiHandler {
29
- /**
30
- * 发起一次到大模型的请求,返回一个可异步迭代的流。
31
- */
32
- createMessage(
33
- systemPrompt: string,
34
- messages: AgentMessage[]
35
- ): AsyncGenerator<ApiStreamChunk, void, unknown>;
36
-
37
- /**
38
- * 获取当前配置的模型信息
39
- */
40
- getModelInfo(): ApiProviderInfo;
41
- }
42
-
43
- /**
44
- * 生成大模型 API 处理器的工厂接口
45
- */
46
- export * from "./NodeApiHandler";
47
- export * from "./WebApiHandler";
@@ -1,158 +0,0 @@
1
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
- import { IMcpManager, McpServerInfo, McpTool, McpToolCallResult } from "./index";
4
-
5
- export interface StdioServerConfig {
6
- command: string;
7
- args?: string[];
8
- env?: Record<string, string>;
9
- }
10
-
11
- export interface NodeMcpManagerConfig {
12
- servers: Record<string, StdioServerConfig>;
13
- }
14
-
15
- export class NodeMcpManager implements IMcpManager {
16
- private config: NodeMcpManagerConfig;
17
- private clients: Map<string, Client> = new Map();
18
- private transports: Map<string, StdioClientTransport> = new Map();
19
- private serverStatuses: Map<string, McpServerInfo> = new Map();
20
-
21
- constructor(config: NodeMcpManagerConfig) {
22
- this.config = config;
23
- }
24
-
25
- /**
26
- * 初始化并连接所有的 MCP Servers
27
- */
28
- public async init(): Promise<void> {
29
- const connectPromises = Object.entries(this.config.servers).map(async ([name, serverConfig]) => {
30
- this.serverStatuses.set(name, { name, status: "connecting" });
31
-
32
- try {
33
- const transport = new StdioClientTransport({
34
- command: serverConfig.command,
35
- args: serverConfig.args,
36
- env: { ...process.env, ...serverConfig.env } as Record<string, string>,
37
- });
38
-
39
- const client = new Client(
40
- {
41
- name: "cline-agent-sdk",
42
- version: "0.1.0",
43
- },
44
- {
45
- capabilities: {},
46
- }
47
- );
48
-
49
- await client.connect(transport);
50
-
51
- this.clients.set(name, client);
52
- this.transports.set(name, transport);
53
- this.serverStatuses.set(name, { name, status: "connected" });
54
- } catch (err: any) {
55
- this.serverStatuses.set(name, { name, status: "disconnected", error: err.message });
56
- }
57
- });
58
-
59
- await Promise.all(connectPromises);
60
- }
61
-
62
- public getServers(): McpServerInfo[] {
63
- return Array.from(this.serverStatuses.values());
64
- }
65
-
66
- public async getTools(): Promise<McpTool[]> {
67
- const allTools: McpTool[] = [];
68
-
69
- for (const [serverName, client] of this.clients.entries()) {
70
- try {
71
- const response = await client.listTools();
72
- if (response && response.tools) {
73
- for (const tool of response.tools) {
74
- allTools.push({
75
- name: `${serverName}__${tool.name}`,
76
- description: tool.description,
77
- inputSchema: tool.inputSchema,
78
- });
79
- }
80
- }
81
- } catch (err: any) {
82
- console.error(`Failed to get tools from MCP server [${serverName}]: ${err.message}`);
83
- }
84
- }
85
-
86
- return allTools;
87
- }
88
-
89
- public async callTool(serverName: string, toolName: string, args: any): Promise<McpToolCallResult> {
90
- const client = this.clients.get(serverName);
91
- if (!client) {
92
- return {
93
- content: [{ type: "text", text: `MCP Server [${serverName}] is not connected.` }],
94
- isError: true,
95
- };
96
- }
97
-
98
- // 如果 Agent 传回来的 toolName 包含了 serverName__ 前缀,自动剥离它
99
- const prefix = `${serverName}__`;
100
- const actualToolName = toolName.startsWith(prefix) ? toolName.slice(prefix.length) : toolName;
101
-
102
- try {
103
- const response = await client.callTool({
104
- name: actualToolName,
105
- arguments: args,
106
- });
107
-
108
- return {
109
- content: response.content as any,
110
- isError: !!response.isError,
111
- };
112
- } catch (err: any) {
113
- return {
114
- content: [{ type: "text", text: `Error calling tool ${toolName} on server ${serverName}: ${err.message}` }],
115
- isError: true,
116
- };
117
- }
118
- }
119
-
120
- public async readResource(serverName: string, uri: string): Promise<any> {
121
- const client = this.clients.get(serverName);
122
- if (!client) {
123
- throw new Error(`MCP Server [${serverName}] is not connected.`);
124
- }
125
-
126
- try {
127
- const response = await client.readResource({ uri });
128
- return response.contents;
129
- } catch (err: any) {
130
- throw new Error(`Error reading resource ${uri} from server ${serverName}: ${err.message}`);
131
- }
132
- }
133
-
134
- /**
135
- * 断开并清理所有的 MCP Servers
136
- */
137
- public async destroy(): Promise<void> {
138
- for (const [name, client] of this.clients.entries()) {
139
- try {
140
- await client.close();
141
- } catch (e) {
142
- // ignore
143
- }
144
- }
145
-
146
- for (const [name, transport] of this.transports.entries()) {
147
- try {
148
- await transport.close();
149
- } catch (e) {
150
- // ignore
151
- }
152
- }
153
-
154
- this.clients.clear();
155
- this.transports.clear();
156
- this.serverStatuses.clear();
157
- }
158
- }
@@ -1,30 +0,0 @@
1
- import { IMcpManager, McpServerInfo, McpTool, McpToolCallResult } from "./index";
2
-
3
- export class WebMcpManager implements IMcpManager {
4
- constructor() {
5
- // 浏览器环境中,直接使用官方的 StdioClientTransport 不可行(依赖 Node 的 child_process),
6
- // 尽管有 SSEClientTransport,但其内部依赖了 Node 端的 eventsource 等包。
7
- // 在纯 Web 环境下,通常需要通过 WebSocket 或 SSE 桥接到后端的代理服务。
8
- // 因此这里暂时作为一个空实现,留待后续结合具体的 Web Backend 代理来完善。
9
- console.warn("WebMcpManager is initialized as an empty stub. MCP relies on Node.js processes or a proxy backend.");
10
- }
11
-
12
- public getServers(): McpServerInfo[] {
13
- return [];
14
- }
15
-
16
- public async getTools(): Promise<McpTool[]> {
17
- return [];
18
- }
19
-
20
- public async callTool(serverName: string, toolName: string, args: any): Promise<McpToolCallResult> {
21
- return {
22
- content: [{ type: "text", text: `MCP is not supported in the pure Web environment.` }],
23
- isError: true,
24
- };
25
- }
26
-
27
- public async readResource(serverName: string, uri: string): Promise<any> {
28
- throw new Error("MCP readResource is not supported in the pure Web environment.");
29
- }
30
- }
package/src/mcp/index.ts DELETED
@@ -1,56 +0,0 @@
1
- /**
2
- * MCP Server 描述信息
3
- */
4
- export interface McpServerInfo {
5
- name: string;
6
- status: "connected" | "disconnected" | "connecting";
7
- error?: string;
8
- }
9
-
10
- /**
11
- * MCP 工具定义
12
- */
13
- export interface McpTool {
14
- name: string;
15
- description?: string;
16
- inputSchema: any; // JSON Schema
17
- }
18
-
19
- /**
20
- * MCP 工具调用结果
21
- */
22
- export interface McpToolCallResult {
23
- content: Array<{ type: string; text?: string; data?: any }>;
24
- isError?: boolean;
25
- }
26
-
27
- /**
28
- * 抽象的 MCP 管理器接口。
29
- * 宿主环境(VSCode/Node)使用基于 StdIO 的 MCP SDK 实现,Web 环境可能使用 SSE 或其他代理机制实现。
30
- * 通过注入 IMcpManager,Agent 可以无缝使用 MCP 提供的拓展工具。
31
- */
32
- export interface IMcpManager {
33
- /**
34
- * 获取所有连接的 MCP 服务器状态
35
- */
36
- getServers(): McpServerInfo[];
37
-
38
- /**
39
- * 获取所有可用的 MCP 工具
40
- */
41
- getTools(): Promise<McpTool[]>;
42
-
43
- /**
44
- * 调用特定的 MCP 工具
45
- * @param serverName 提供该工具的服务器名称
46
- * @param toolName 工具名称
47
- * @param args 工具参数
48
- */
49
- callTool(serverName: string, toolName: string, args: any): Promise<McpToolCallResult>;
50
-
51
- /**
52
- * 读取 MCP 资源
53
- */
54
- readResource?(serverName: string, uri: string): Promise<any>;
55
- }
56
-
@@ -1,82 +0,0 @@
1
- import { IMemoryManager, AgentMessage } from "./index";
2
- import * as fs from "fs/promises";
3
- import * as path from "path";
4
-
5
- /**
6
- * Node 端 MemoryManager 实现
7
- * 将对话历史持久化到本地文件系统(JSON 格式)。
8
- */
9
- export class NodeMemoryManager implements IMemoryManager {
10
- private filePath: string;
11
- private maxMessages: number;
12
-
13
- /**
14
- * @param dbPath 存储历史记录的本地文件路径(例如:'./.cline_memory.json')
15
- * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 150)
16
- */
17
- constructor(dbPath: string, maxMessages: number = 150) {
18
- this.filePath = path.resolve(dbPath);
19
- this.maxMessages = maxMessages;
20
- }
21
-
22
- private async ensureFile(): Promise<void> {
23
- try {
24
- await fs.access(this.filePath);
25
- } catch {
26
- // 文件不存在则初始化为空数组
27
- const dir = path.dirname(this.filePath);
28
- await fs.mkdir(dir, { recursive: true });
29
- await fs.writeFile(this.filePath, "[]", "utf-8");
30
- }
31
- }
32
-
33
- private async readMessages(): Promise<AgentMessage[]> {
34
- await this.ensureFile();
35
- const data = await fs.readFile(this.filePath, "utf-8");
36
- try {
37
- return JSON.parse(data) as AgentMessage[];
38
- } catch {
39
- return [];
40
- }
41
- }
42
-
43
- private async writeMessages(messages: AgentMessage[]): Promise<void> {
44
- await this.ensureFile();
45
- await fs.writeFile(this.filePath, JSON.stringify(messages, null, 2), "utf-8");
46
- }
47
-
48
- async getHistory(): Promise<AgentMessage[]> {
49
- return this.readMessages();
50
- }
51
-
52
- async appendMessage(message: AgentMessage): Promise<void> {
53
- const messages = await this.readMessages();
54
- messages.push(message);
55
- await this.writeMessages(messages);
56
- await this.condenseHistory();
57
- }
58
-
59
- async condenseHistory(): Promise<void> {
60
- const messages = await this.readMessages();
61
- if (messages.length > this.maxMessages) {
62
- // 简单截断:保留最早的系统消息(如果有)和最近的 N 条消息
63
- const firstMessage = messages[0];
64
- const isFirstSystem = firstMessage && firstMessage.role === "system";
65
-
66
- const sliceCount = messages.length - this.maxMessages;
67
- let condensed: AgentMessage[];
68
-
69
- if (isFirstSystem) {
70
- condensed = [firstMessage, ...messages.slice(sliceCount + 1)];
71
- } else {
72
- condensed = messages.slice(sliceCount);
73
- }
74
-
75
- await this.writeMessages(condensed);
76
- }
77
- }
78
-
79
- async clear(): Promise<void> {
80
- await this.writeMessages([]);
81
- }
82
- }
@@ -1,89 +0,0 @@
1
- import { IMemoryManager, AgentMessage } from "./index";
2
-
3
- /**
4
- * Web 端 MemoryManager 实现
5
- * 使用 IndexedDB (通过封装) 或 LocalStorage / SessionStorage 来持久化对话历史。
6
- * 此处提供一个基于 SessionStorage / 内存 回退的轻量实现。
7
- */
8
- export class WebMemoryManager implements IMemoryManager {
9
- private storageKey: string;
10
- private maxMessages: number;
11
- private inMemoryFallback: AgentMessage[] = [];
12
-
13
- /**
14
- * @param storageKey SessionStorage 中的键名
15
- * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 150)
16
- */
17
- constructor(storageKey: string = "cline_agent_memory", maxMessages: number = 150) {
18
- this.storageKey = storageKey;
19
- this.maxMessages = maxMessages;
20
- }
21
-
22
- private isStorageAvailable(): boolean {
23
- try {
24
- return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
25
- } catch {
26
- return false;
27
- }
28
- }
29
-
30
- private readMessages(): AgentMessage[] {
31
- if (this.isStorageAvailable()) {
32
- const data = window.sessionStorage.getItem(this.storageKey);
33
- if (data) {
34
- try {
35
- return JSON.parse(data) as AgentMessage[];
36
- } catch {
37
- return [];
38
- }
39
- }
40
- return [];
41
- }
42
- return this.inMemoryFallback;
43
- }
44
-
45
- private writeMessages(messages: AgentMessage[]): void {
46
- if (this.isStorageAvailable()) {
47
- window.sessionStorage.setItem(this.storageKey, JSON.stringify(messages));
48
- } else {
49
- this.inMemoryFallback = messages;
50
- }
51
- }
52
-
53
- async getHistory(): Promise<AgentMessage[]> {
54
- return Promise.resolve(this.readMessages());
55
- }
56
-
57
- async appendMessage(message: AgentMessage): Promise<void> {
58
- const messages = this.readMessages();
59
- messages.push(message);
60
- this.writeMessages(messages);
61
- await this.condenseHistory();
62
- }
63
-
64
- async condenseHistory(): Promise<void> {
65
- const messages = this.readMessages();
66
- if (messages.length > this.maxMessages) {
67
- // 简单截断:保留最早的系统消息(如果有)和最近的 N 条消息
68
- const firstMessage = messages[0];
69
- const isFirstSystem = firstMessage && firstMessage.role === "system";
70
-
71
- const sliceCount = messages.length - this.maxMessages;
72
- let condensed: AgentMessage[];
73
-
74
- if (isFirstSystem) {
75
- condensed = [firstMessage, ...messages.slice(sliceCount + 1)];
76
- } else {
77
- condensed = messages.slice(sliceCount);
78
- }
79
-
80
- this.writeMessages(condensed);
81
- }
82
- return Promise.resolve();
83
- }
84
-
85
- async clear(): Promise<void> {
86
- this.writeMessages([]);
87
- return Promise.resolve();
88
- }
89
- }