prism-agent 0.2.1__py3-none-any.whl

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.
prism/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """
2
+ PRISM Agent - 统一 AI Agent CLI
3
+ """
4
+
5
+ __version__ = "0.1.0"
prism/acp/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """PRISM ACP"""
2
+
3
+ from prism.acp.client import ACPClient
4
+
5
+ __all__ = ['ACPClient']
prism/acp/client.py ADDED
@@ -0,0 +1,63 @@
1
+ """
2
+ PRISM Agent - ACP (Agent Communication Protocol) 客户端
3
+ 提供与 ACP 兼容 agent 通信的能力
4
+ """
5
+
6
+ import json
7
+ import subprocess
8
+ from typing import Any, Dict, Optional
9
+
10
+
11
+ class ACPClient:
12
+ """
13
+ ACP 客户端
14
+ 通过 stdio 与 ACP 兼容 agent 通信
15
+ """
16
+
17
+ def __init__(self, command: str, args: Optional[list] = None):
18
+ self.command = command
19
+ self.args = args or []
20
+ self.process: Optional[subprocess.Popen] = None
21
+
22
+ def start(self) -> Dict[str, Any]:
23
+ try:
24
+ self.process = subprocess.Popen(
25
+ [self.command] + self.args,
26
+ stdin=subprocess.PIPE,
27
+ stdout=subprocess.PIPE,
28
+ stderr=subprocess.PIPE,
29
+ text=True,
30
+ bufsize=1,
31
+ )
32
+ return {'success': True}
33
+ except Exception as e:
34
+ return {'success': False, 'error': str(e)}
35
+
36
+ def send(self, payload: Dict[str, Any]) -> Dict[str, Any]:
37
+ if not self.process:
38
+ return {'success': False, 'error': 'ACP client not started'}
39
+
40
+ try:
41
+ request = json.dumps({'jsonrpc': '2.0', 'id': 1, **payload}) + '\n'
42
+ self.process.stdin.write(request)
43
+ self.process.stdin.flush()
44
+ response_line = self.process.stdout.readline()
45
+ if not response_line:
46
+ return {'success': False, 'error': 'No response from ACP agent'}
47
+ response = json.loads(response_line)
48
+ if 'result' in response:
49
+ return {'success': True, 'result': response['result']}
50
+ if 'error' in response:
51
+ return {'success': False, 'error': response['error'].get('message', 'Unknown error')}
52
+ return {'success': False, 'error': 'Invalid ACP response'}
53
+ except Exception as e:
54
+ return {'success': False, 'error': str(e)}
55
+
56
+ def close(self):
57
+ if self.process:
58
+ try:
59
+ self.process.terminate()
60
+ self.process.wait(timeout=5)
61
+ except Exception:
62
+ pass
63
+ self.process = None
prism/agent.py ADDED
@@ -0,0 +1,144 @@
1
+ """
2
+ PRISM Agent - 核心Agent循环
3
+ 整合 Hermes 的上下文压缩 + Codex 的工具调用 + OpenClaw 的浏览器控制
4
+ """
5
+
6
+ import json
7
+ import os
8
+ import logging
9
+ from typing import List, Dict, Any, Optional, Callable
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+
13
+ from prism.providers.manager import provider_pool
14
+ from prism.tools.registry import registry
15
+
16
+ logger = logging.getLogger("prism.agent")
17
+
18
+
19
+ @dataclass
20
+ class Message:
21
+ """消息结构"""
22
+ role: str # system | user | assistant | tool
23
+ content: str
24
+ timestamp: datetime = field(default_factory=datetime.now)
25
+ metadata: Dict[str, Any] = field(default_factory=dict)
26
+
27
+
28
+ @dataclass
29
+ class ToolCall:
30
+ """工具调用"""
31
+ id: str
32
+ name: str
33
+ arguments: Dict[str, Any]
34
+ result: Optional[Dict[str, Any]] = None
35
+
36
+
37
+ class Agent:
38
+ """
39
+ PRISM Agent 核心
40
+ 整合:
41
+ - Hermes 的 Skills 系统
42
+ - Codex 的代码执行能力
43
+ - OpenClaw 的浏览器自动化
44
+ - 统一的模型调用接口
45
+ """
46
+
47
+ def __init__(self, system_prompt: Optional[str] = None):
48
+ self.messages: List[Message] = []
49
+ self.tool_calls: List[ToolCall] = []
50
+ self.system_prompt = system_prompt or self._default_system_prompt()
51
+ self.max_turns = 150
52
+ self.tools_enabled = True
53
+
54
+ # 初始化系统消息
55
+ self.messages.append(Message(
56
+ role="system",
57
+ content=self.system_prompt,
58
+ ))
59
+
60
+ def _default_system_prompt(self) -> str:
61
+ """默认系统提示词"""
62
+ return """你是 PRISM Agent,一个强大的 AI 助手。
63
+
64
+ 你可以:
65
+ 1. 读写文件、执行命令
66
+ 2. 搜索网页、控制浏览器
67
+ 3. 执行 Python 代码
68
+ 4. 管理定时任务
69
+ 5. 发送消息到 Telegram/Discord/飞书
70
+
71
+ 原则:
72
+ - 先理解用户需求,再执行
73
+ - 复杂任务拆成步骤
74
+ - 遇到问题主动报告,不要隐瞒
75
+ - 安全第一,危险操作先确认
76
+ """
77
+
78
+ def chat(self, user_message: str) -> str:
79
+ """
80
+ 发送消息并获取回复
81
+ 整合了:
82
+ - Hermes 的上下文管理
83
+ - Codex 的函数调用
84
+ - OpenClaw 的工具执行
85
+ """
86
+ # 添加用户消息
87
+ self.messages.append(Message(role="user", content=user_message))
88
+
89
+ # 构建 API 消息格式
90
+ api_messages = [
91
+ {"role": m.role, "content": m.content}
92
+ for m in self.messages
93
+ ]
94
+
95
+ # 调用模型
96
+ result = provider_pool.chat(api_messages)
97
+
98
+ if not result.get('success'):
99
+ logger.warning("chat failed: %s", result.get('error'))
100
+ return f"Error: {result.get('error', 'Unknown error')}"
101
+
102
+ assistant_content = result.get('content', '')
103
+ logger.info("chat success model=%s", result.get('model'))
104
+
105
+ # 添加助手回复
106
+ self.messages.append(Message(role="assistant", content=assistant_content))
107
+
108
+ return assistant_content
109
+
110
+ def execute_tool(self, tool_name: str, **kwargs) -> Dict[str, Any]:
111
+ """
112
+ 执行工具
113
+ 整合了 Codex 的终端执行 + OpenClaw 的浏览器控制
114
+ """
115
+ if not self.tools_enabled:
116
+ return {'success': False, 'error': 'Tools disabled'}
117
+
118
+ logger.info("execute tool=%s args=%s", tool_name, kwargs)
119
+ result = registry.execute(tool_name, **kwargs)
120
+ logger.info("execute tool=%s result=%s", tool_name, result.get('success'))
121
+
122
+ # 记录工具调用
123
+ self.tool_calls.append(ToolCall(
124
+ id=f"call_{len(self.tool_calls)}",
125
+ name=tool_name,
126
+ arguments=kwargs,
127
+ result=result,
128
+ ))
129
+
130
+ return result
131
+
132
+ def list_tools(self) -> List[Dict[str, str]]:
133
+ """列出可用工具"""
134
+ return registry.list_tools()
135
+
136
+ def clear_history(self):
137
+ """清空对话历史"""
138
+ self.messages = [self.messages[0]] # 保留系统消息
139
+ self.tool_calls = []
140
+
141
+
142
+ def create_agent(system_prompt: Optional[str] = None) -> Agent:
143
+ """创建 Agent 实例"""
144
+ return Agent(system_prompt=system_prompt)
prism/cli.py ADDED
@@ -0,0 +1,419 @@
1
+ """
2
+ PRISM Agent - CLI 入口
3
+ 整合 Hermes/Codex/OpenClaw 的命令行体验
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ from pathlib import Path
9
+
10
+ # 添加项目根目录到路径
11
+ sys.path.insert(0, str(Path(__file__).parent.parent))
12
+
13
+ from typing import Optional
14
+
15
+ import click
16
+ import logging
17
+ from rich.console import Console
18
+ from rich.panel import Panel
19
+ from rich.markdown import Markdown
20
+ from rich.syntax import Syntax
21
+ from rich.prompt import Prompt
22
+
23
+ from prism.config import config as prism_config
24
+ from prism.config import ConfigError
25
+ from prism.agent import create_agent
26
+
27
+ console = Console()
28
+
29
+ # 统一日志配置
30
+ LOG_DIR = Path.home() / ".prism" / "logs"
31
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
32
+ LOG_FILE = LOG_DIR / "prism.log"
33
+
34
+ logger = logging.getLogger("prism")
35
+ logger.setLevel(logging.DEBUG)
36
+ formatter = logging.Formatter(
37
+ "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
38
+ datefmt="%Y-%m-%d %H:%M:%S",
39
+ )
40
+
41
+ # 文件日志,带轮转
42
+ from logging.handlers import RotatingFileHandler
43
+ fh = RotatingFileHandler(LOG_FILE, encoding="utf-8", maxBytes=5 * 1024 * 1024, backupCount=5)
44
+ fh.setLevel(logging.DEBUG)
45
+ fh.setFormatter(formatter)
46
+ logger.addHandler(fh)
47
+
48
+ # 控制台日志只显示 WARNING+
49
+ ch = logging.StreamHandler()
50
+ ch.setLevel(logging.WARNING)
51
+ ch.setFormatter(formatter)
52
+ logger.addHandler(ch)
53
+
54
+
55
+ @click.group()
56
+ @click.version_option(version="0.2.1", prog_name="PRISM")
57
+ def cli():
58
+ """
59
+ PRISM Agent - 统一 AI Agent CLI
60
+
61
+ 整合 Hermes/Codex/OpenClaw 优势的新一代 AI Agent
62
+ """
63
+ # 配置校验延后到实际需要时执行,避免影响 help / version 等命令
64
+
65
+
66
+ @cli.group()
67
+ def gateway():
68
+ """Gateway 控制命令"""
69
+ pass
70
+
71
+
72
+ @gateway.command()
73
+ @click.option('--platform', '-p', help='平台名称')
74
+ @click.option('--token', '-t', help='Bot Token')
75
+ @click.option('--app-id', help='飞书 App ID')
76
+ @click.option('--app-secret', help='飞书 App Secret')
77
+ @click.option('--encrypt-key', help='飞书 Encrypt Key')
78
+ @click.option('--verification-token', help='飞书 Verification Token')
79
+ def start(platform: Optional[str], token: Optional[str], app_id: Optional[str],
80
+ app_secret: Optional[str], encrypt_key: Optional[str], verification_token: Optional[str]):
81
+ """启动 Gateway 服务"""
82
+ from prism.gateway import gateway as gw
83
+
84
+ if not platform:
85
+ platforms = gw.list_platforms()
86
+ if platforms:
87
+ click.echo(f"已配置平台: {', '.join(platforms)}")
88
+ else:
89
+ click.echo("未配置任何平台,请用 --platform 指定")
90
+ return
91
+
92
+ # 保存配置到 config
93
+ config_path = Path.home() / ".prism" / "config.yaml"
94
+ config_path.parent.mkdir(parents=True, exist_ok=True)
95
+
96
+ import yaml
97
+ if config_path.exists():
98
+ with open(config_path, 'r', encoding='utf-8') as f:
99
+ cfg = yaml.safe_load(f) or {}
100
+ else:
101
+ cfg = {}
102
+
103
+ if 'gateway' not in cfg:
104
+ cfg['gateway'] = {}
105
+ cfg['gateway'][platform] = {
106
+ 'token': token or '',
107
+ 'app_id': app_id or '',
108
+ 'app_secret': app_secret or '',
109
+ 'encrypt_key': encrypt_key or '',
110
+ 'verification_token': verification_token or '',
111
+ }
112
+
113
+ with open(config_path, 'w', encoding='utf-8') as f:
114
+ yaml.dump(cfg, f, allow_unicode=True, default_flow_style=False)
115
+
116
+ click.echo(f"Gateway 配置已保存到 {config_path}")
117
+ click.echo(f"平台: {platform}")
118
+
119
+ # 显示启动说明
120
+ if platform == 'feishu':
121
+ click.echo("\n飞书 Gateway 启动说明:")
122
+ click.echo("1. 在飞书开放平台创建应用")
123
+ click.echo("2. 开启机器人能力")
124
+ click.echo("3. 配置事件订阅 URL")
125
+ click.echo("4. 使用 prism gateway start --platform feishu 启动")
126
+ elif platform == 'telegram':
127
+ click.echo("\nTelegram Gateway 启动说明:")
128
+ click.echo("1. 与 @BotFather 对话创建 Bot")
129
+ click.echo("2. 获取 Bot Token")
130
+ click.echo("3. 使用 prism gateway start --platform telegram --token <TOKEN> 启动")
131
+
132
+
133
+ @gateway.command()
134
+ @click.argument('platform')
135
+ def stop(platform: str):
136
+ """停止 Gateway 服务"""
137
+ click.echo(f"停止 {platform} Gateway...")
138
+ from prism.gateway import gateway as gw
139
+ click.echo("Gateway 已停止")
140
+
141
+
142
+ @gateway.command()
143
+ def status():
144
+ """查看 Gateway 状态"""
145
+ from prism.gateway import gateway as gw
146
+ platforms = gw.list_platforms()
147
+ if platforms:
148
+ click.echo(f"运行中平台: {', '.join(platforms)}")
149
+ else:
150
+ click.echo("未运行任何 Gateway")
151
+
152
+
153
+ @cli.group()
154
+ def skill():
155
+ """Skills 管理命令"""
156
+ pass
157
+
158
+
159
+ @skill.command()
160
+ def list():
161
+ """列出所有已安装的 Skills"""
162
+ from prism.skills import skills
163
+ skill_list = skills.list_skills()
164
+
165
+ console = Console()
166
+ console.print("\n[bold cyan]已安装 Skills:[/bold cyan]")
167
+ for s in skill_list:
168
+ status = "[green]✓[/green]" if s.get('enabled', True) else "[red]✗[/red]"
169
+ console.print(f" {status} [green]{s['name']}[/green]: {s['description']}")
170
+ console.print()
171
+
172
+
173
+ @skill.command()
174
+ @click.argument('name')
175
+ def install(name: str):
176
+ """安装一个 Skill"""
177
+ from prism.skills import skills
178
+ result = skills.install_skill(name)
179
+ if result.get('success'):
180
+ console.print(f"[green]✓[/green] 已安装 skill: {name}")
181
+ else:
182
+ console.print(f"[red]✗[/red] 安装失败: {result.get('error', '未知错误')}")
183
+
184
+
185
+ @skill.command()
186
+ @click.argument('name')
187
+ def remove(name: str):
188
+ """移除一个 Skill"""
189
+ from prism.skills import skills
190
+ result = skills.uninstall_skill(name)
191
+ if result.get('success'):
192
+ console.print(f"[green]✓[/green] 已移除 skill: {name}")
193
+ else:
194
+ console.print(f"[red]✗[/red] 移除失败: {result.get('error', '未知错误')}")
195
+
196
+
197
+ @cli.group()
198
+ def browser():
199
+ """浏览器控制命令"""
200
+ pass
201
+
202
+
203
+ @browser.command()
204
+ @click.argument('url')
205
+ @click.option('--headless/--no-headless', default=True, help='无头模式')
206
+ def open(url: str, headless: bool):
207
+ """打开网页"""
208
+ from prism.tools.browser import browser as browser_api
209
+ result = browser_api.navigate(url, headless=headless)
210
+ if result.get('success'):
211
+ console.print(f"[green]✓[/green] 已打开: {url}")
212
+ console.print(f"标题: {result.get('title', 'N/A')}")
213
+ console.print(f"状态码: {result.get('status', 'N/A')}")
214
+ else:
215
+ console.print(f"[red]✗[/red] 打开失败: {result.get('error')}")
216
+
217
+
218
+ @browser.command()
219
+ def close():
220
+ """关闭浏览器"""
221
+ from prism.tools.browser import browser as browser_api
222
+ result = browser_api.disconnect()
223
+ if result.get('success'):
224
+ console.print(f"[green]✓[/green] {result.get('message')}")
225
+ else:
226
+ console.print(f"[red]✗[/red] {result.get('error')}")
227
+
228
+
229
+ @cli.group()
230
+ def config():
231
+ """配置管理命令"""
232
+ pass
233
+
234
+
235
+ @config.command()
236
+ @click.argument('key')
237
+ @click.argument('value')
238
+ def set(key: str, value: str):
239
+ """设置配置项"""
240
+ prism_config.set(key, value)
241
+ console.print(f"[green]✓[/green] 已设置 {key} = {value}")
242
+
243
+
244
+ @config.command()
245
+ @click.argument('key', required=False)
246
+ def get(key: Optional[str]):
247
+ """查看配置项"""
248
+ if key:
249
+ value = prism_config.get(key)
250
+ console.print(f"{key} = {value}")
251
+ else:
252
+ all_config = prism_config.show()
253
+ console.print_json(data=all_config)
254
+
255
+
256
+ @cli.command()
257
+ @click.option('--model', '-m', help='模型名称')
258
+ @click.option('--provider', '-p', help='提供商')
259
+ def chat(model: Optional[str], provider: Optional[str]):
260
+
261
+ # 启动前校验配置
262
+ try:
263
+ prism_config.validate()
264
+ except ConfigError as e:
265
+ console.print(f"[red]配置错误:{e}[/red]")
266
+ return
267
+ console.print(Panel.fit(
268
+ "[bold cyan]PRISM Agent[/bold cyan] [dim]v0.1.0[/dim]\n"
269
+ "整合 Hermes + Codex + OpenClaw 能力\n"
270
+ "输入 /help 查看命令,/exit 退出",
271
+ border_style="cyan"
272
+ ))
273
+
274
+ # 创建 Agent
275
+ agent = create_agent()
276
+
277
+ # 显示当前模型
278
+ current_model = model or config.get('model.default', 'gpt-4o')
279
+ console.print(f"[dim]当前模型: {current_model}[/dim]\n")
280
+
281
+ # 对话循环
282
+ while True:
283
+ try:
284
+ # 获取用户输入
285
+ user_input = Prompt.ask("[bold green]你[/bold green]")
286
+
287
+ if not user_input.strip():
288
+ continue
289
+
290
+ # 处理特殊命令
291
+ if user_input.startswith('/'):
292
+ cmd = user_input.lower()
293
+ if cmd in ['/exit', '/quit']:
294
+ console.print("[yellow]再见![/yellow]")
295
+ break
296
+ elif cmd == '/help':
297
+ show_help()
298
+ continue
299
+ elif cmd == '/tools':
300
+ show_tools(agent)
301
+ continue
302
+ elif cmd == '/model':
303
+ show_model()
304
+ continue
305
+ elif cmd == '/clear':
306
+ agent.clear_history()
307
+ console.print("[green]历史已清空[/green]")
308
+ continue
309
+ else:
310
+ console.print(f"[red]未知命令: {cmd}[/red]")
311
+ continue
312
+
313
+ # 发送消息给 Agent
314
+ with console.status("[bold cyan]思考中...", spinner="dots"):
315
+ response = agent.chat(user_input)
316
+
317
+ # 显示回复
318
+ console.print(Markdown(response))
319
+ console.print()
320
+
321
+ except KeyboardInterrupt:
322
+ console.print("\n[yellow]再见![/yellow]")
323
+ break
324
+ except Exception as e:
325
+ console.print(f"[red]错误: {e}[/red]")
326
+
327
+
328
+ @cli.command()
329
+ @click.argument('message')
330
+ @click.option('--model', '-m', help='模型名称')
331
+ def ask(message: str, model: Optional[str]):
332
+ """单次提问"""
333
+ try:
334
+ prism_config.validate()
335
+ except ConfigError as e:
336
+ console.print(f"[red]配置错误:{e}[/red]")
337
+ return
338
+ agent = create_agent()
339
+ response = agent.chat(message)
340
+ console.print(Markdown(response))
341
+
342
+
343
+ @cli.command()
344
+ def tools():
345
+ """列出所有可用工具"""
346
+ try:
347
+ prism_config.validate()
348
+ except ConfigError as e:
349
+ console.print(f"[red]配置错误:{e}[/red]")
350
+ return
351
+ agent = create_agent()
352
+ show_tools(agent)
353
+
354
+
355
+ @cli.command()
356
+ def model():
357
+ """显示当前模型配置"""
358
+ try:
359
+ prism_config.validate()
360
+ except ConfigError as e:
361
+ console.print(f"[red]配置错误:{e}[/red]")
362
+ return
363
+ show_model()
364
+
365
+
366
+ @cli.command()
367
+ def version():
368
+ """显示版本信息"""
369
+ console.print("PRISM Agent v0.1.0")
370
+ console.print("整合 Hermes + Codex + OpenClaw 能力")
371
+
372
+
373
+ def show_help():
374
+ """显示帮助信息"""
375
+ help_text = """
376
+ **可用命令:**
377
+ - `/help` - 显示帮助
378
+ - `/exit` - 退出
379
+ - `/tools` - 列出所有工具
380
+ - `/model` - 显示当前模型
381
+ - `/clear` - 清空对话历史
382
+
383
+ **示例:**
384
+ - "帮我写一个Python脚本"
385
+ - "读取文件 README.md"
386
+ - "执行命令 ls -la"
387
+ - "搜索网页 Python教程"
388
+ """
389
+ console.print(Markdown(help_text))
390
+
391
+
392
+ def show_tools(agent):
393
+ """显示可用工具"""
394
+ tools = agent.list_tools()
395
+ console.print("\n[bold cyan]可用工具:[/bold cyan]")
396
+ for tool in tools:
397
+ console.print(f" • [green]{tool['name']}[/green]: {tool['description']}")
398
+ console.print()
399
+
400
+
401
+ def show_model():
402
+ """显示模型配置"""
403
+ model = config.get('model.default', 'gpt-4o')
404
+ provider = config.get('model.provider', 'openai')
405
+ base_url = config.get('model.base_url', '')
406
+
407
+ console.print(f"\n[bold cyan]当前模型配置:[/bold cyan]")
408
+ console.print(f" 模型: [green]{model}[/green]")
409
+ console.print(f" 提供商: [green]{provider}[/green]")
410
+ console.print(f" API地址: [green]{base_url}[/green]\n")
411
+
412
+
413
+ def main():
414
+ """主入口"""
415
+ cli()
416
+
417
+
418
+ if __name__ == '__main__':
419
+ main()