somnia 0.1.0__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.
Files changed (63) hide show
  1. openagent/__init__.py +5 -0
  2. openagent/__main__.py +10 -0
  3. openagent/cli/__init__.py +1 -0
  4. openagent/cli/commands.py +202 -0
  5. openagent/cli/main.py +103 -0
  6. openagent/cli/prompting.py +474 -0
  7. openagent/cli/repl.py +996 -0
  8. openagent/collaboration/__init__.py +1 -0
  9. openagent/collaboration/bus.py +40 -0
  10. openagent/collaboration/protocols.py +67 -0
  11. openagent/config/__init__.py +1 -0
  12. openagent/config/models.py +97 -0
  13. openagent/config/settings.py +417 -0
  14. openagent/mcp/__init__.py +1 -0
  15. openagent/mcp/client.py +67 -0
  16. openagent/mcp/registry.py +98 -0
  17. openagent/mcp/transport_http.py +138 -0
  18. openagent/mcp/transport_stdio.py +120 -0
  19. openagent/providers/__init__.py +1 -0
  20. openagent/providers/anthropic_provider.py +120 -0
  21. openagent/providers/base.py +53 -0
  22. openagent/providers/openai_provider.py +295 -0
  23. openagent/runtime/__init__.py +1 -0
  24. openagent/runtime/agent.py +826 -0
  25. openagent/runtime/compact.py +265 -0
  26. openagent/runtime/events.py +12 -0
  27. openagent/runtime/execution_mode.py +191 -0
  28. openagent/runtime/interrupts.py +5 -0
  29. openagent/runtime/messages.py +281 -0
  30. openagent/runtime/permissions.py +179 -0
  31. openagent/runtime/session.py +81 -0
  32. openagent/runtime/subagent_runner.py +159 -0
  33. openagent/runtime/system_prompt.py +99 -0
  34. openagent/runtime/teammate.py +615 -0
  35. openagent/runtime/tool_events.py +402 -0
  36. openagent/skills/__init__.py +1 -0
  37. openagent/skills/loader.py +114 -0
  38. openagent/storage/__init__.py +1 -0
  39. openagent/storage/common.py +58 -0
  40. openagent/storage/inbox.py +27 -0
  41. openagent/storage/jobs.py +39 -0
  42. openagent/storage/sessions.py +74 -0
  43. openagent/storage/tasks.py +172 -0
  44. openagent/storage/team.py +40 -0
  45. openagent/storage/tool_logs.py +62 -0
  46. openagent/storage/transcripts.py +34 -0
  47. openagent/tools/__init__.py +1 -0
  48. openagent/tools/background.py +113 -0
  49. openagent/tools/filesystem.py +428 -0
  50. openagent/tools/mcp.py +5 -0
  51. openagent/tools/process.py +75 -0
  52. openagent/tools/registry.py +48 -0
  53. openagent/tools/shell.py +118 -0
  54. openagent/tools/subagent.py +29 -0
  55. openagent/tools/tasks.py +129 -0
  56. openagent/tools/team.py +160 -0
  57. openagent/tools/todo.py +124 -0
  58. somnia-0.1.0.dist-info/METADATA +580 -0
  59. somnia-0.1.0.dist-info/RECORD +63 -0
  60. somnia-0.1.0.dist-info/WHEEL +5 -0
  61. somnia-0.1.0.dist-info/entry_points.txt +2 -0
  62. somnia-0.1.0.dist-info/licenses/LICENSE +21 -0
  63. somnia-0.1.0.dist-info/top_level.txt +1 -0
openagent/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """OpenAgent package."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
openagent/__main__.py ADDED
@@ -0,0 +1,10 @@
1
+ """somnia 命令行入口模块.
2
+
3
+ 这个模块允许通过 `python -m openagent` 或 `somnia` 运行。
4
+ """
5
+
6
+ from openagent.cli.main import main
7
+
8
+
9
+ if __name__ == "__main__":
10
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ """CLI entrypoints for OpenAgent."""
@@ -0,0 +1,202 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from dataclasses import dataclass
6
+
7
+ from openagent.runtime.agent import OpenAgentRuntime
8
+ from openagent.runtime.messages import MarkdownStreamRenderer, render_markdown_text, render_message_content, render_text_content
9
+ from openagent.cli.prompting import choose_session_interactively, format_session_timestamp
10
+
11
+
12
+ ASSISTANT_BULLET = "\u25cf"
13
+ USER_BULLET = "\u276f"
14
+
15
+
16
+ def _prefix_first_line(text: str, prefix: str) -> str:
17
+ if not text:
18
+ return prefix.rstrip()
19
+ lines = text.splitlines()
20
+ if not lines:
21
+ return prefix.rstrip()
22
+ lines[0] = f"{prefix}{lines[0]}"
23
+ return "\n".join(lines)
24
+
25
+
26
+ def _assistant_prefix(*, ansi: bool) -> str:
27
+ if ansi:
28
+ return "\x1b[37m\u25cf\x1b[0m "
29
+ return f"{ASSISTANT_BULLET} "
30
+
31
+
32
+ def _user_prefix(*, ansi: bool) -> str:
33
+ if ansi:
34
+ return "\x1b[38;5;45m\u276f\x1b[0m "
35
+ return f"{USER_BULLET} "
36
+
37
+
38
+ def print_user_message(text: str, *, ansi: bool | None = None) -> None:
39
+ ansi_enabled = sys.stdout.isatty() if ansi is None else ansi
40
+ lines = text.splitlines() or [""]
41
+ first = f"{_user_prefix(ansi=ansi_enabled)}{lines[0]}"
42
+ remainder = [f" {line}" if line else " " for line in lines[1:]]
43
+ print()
44
+ print(first)
45
+ for line in remainder:
46
+ print(line)
47
+ print()
48
+
49
+
50
+ class ConsoleStreamer:
51
+ def __init__(
52
+ self,
53
+ start_on_new_line: bool = False,
54
+ line_buffered: bool = False,
55
+ on_first_output=None,
56
+ ) -> None:
57
+ self.has_output = False
58
+ self.start_on_new_line = start_on_new_line
59
+ self.line_buffered = line_buffered
60
+ self.on_first_output = on_first_output
61
+ self._renderer: MarkdownStreamRenderer | None = None
62
+ self._started_printing = False
63
+
64
+ def __call__(self, text: str) -> None:
65
+ if not text:
66
+ return
67
+ if self._renderer is None:
68
+ self._renderer = MarkdownStreamRenderer(ansi=sys.stdout.isatty())
69
+ if not self.has_output and self.on_first_output is not None:
70
+ self.on_first_output()
71
+ self._print_rendered(self._renderer.feed(text))
72
+ self.has_output = True
73
+
74
+ def finish(self) -> None:
75
+ if not self.has_output:
76
+ return
77
+ if self._renderer is None:
78
+ return
79
+ self._print_rendered(self._renderer.finish())
80
+
81
+ def _print_rendered(self, rendered: str) -> None:
82
+ if not rendered:
83
+ return
84
+ if self.start_on_new_line and not self._started_printing:
85
+ print()
86
+ if not self._started_printing:
87
+ rendered = _prefix_first_line(rendered, _assistant_prefix(ansi=sys.stdout.isatty()))
88
+ print(rendered, end="" if rendered.endswith("\n") else "\n", flush=True)
89
+ self._started_printing = True
90
+
91
+
92
+ @dataclass(slots=True)
93
+ class SessionChoice:
94
+ session_id: str
95
+ label: str
96
+
97
+
98
+ def _has_visible_exchange(session) -> bool:
99
+ has_user = False
100
+ has_assistant = False
101
+ for message in session.messages:
102
+ role = message.get("role")
103
+ content = message.get("content")
104
+ if role == "user" and isinstance(content, str):
105
+ if content.startswith("<background-results>") or content.startswith("<inbox>"):
106
+ continue
107
+ if content.strip():
108
+ has_user = True
109
+ elif role == "assistant":
110
+ text = render_text_content(content).strip()
111
+ if text:
112
+ has_assistant = True
113
+ if has_user and has_assistant:
114
+ return True
115
+ return False
116
+
117
+
118
+ def _session_preview(session) -> str:
119
+ for message in reversed(session.messages):
120
+ role = message.get("role")
121
+ content = message.get("content")
122
+ if role == "assistant":
123
+ text = render_message_content(content, ansi=False).strip()
124
+ elif role == "user" and isinstance(content, str):
125
+ if content.startswith("<background-results>") or content.startswith("<inbox>"):
126
+ continue
127
+ text = content.strip()
128
+ else:
129
+ continue
130
+ if text:
131
+ return " ".join(text.split())[:80]
132
+ return "[no visible messages]"
133
+
134
+
135
+ def _build_session_choices(runtime: OpenAgentRuntime) -> list[SessionChoice]:
136
+ choices: list[SessionChoice] = []
137
+ for session in runtime.list_sessions():
138
+ if not _has_visible_exchange(session):
139
+ continue
140
+ stamp = format_session_timestamp(session.updated_at or session.created_at)
141
+ preview = _session_preview(session)
142
+ label = f"{session.id} | {stamp} | {preview}"
143
+ choices.append(SessionChoice(session_id=session.id, label=label))
144
+ return choices
145
+
146
+
147
+ def _select_session(runtime: OpenAgentRuntime):
148
+ choices = _build_session_choices(runtime)
149
+ if not choices:
150
+ print("No saved sessions. Starting a new chat.")
151
+ return runtime.create_session(), False
152
+
153
+ selected_id = choose_session_interactively([(item.session_id, item.label) for item in choices])
154
+ if not selected_id:
155
+ print("Session selection cancelled. Starting a new chat.")
156
+ return runtime.create_session(), False
157
+ return runtime.load_session(selected_id), True
158
+
159
+
160
+ def cmd_chat(runtime: OpenAgentRuntime, resume: bool = False) -> int:
161
+ from openagent.cli.repl import run_repl
162
+
163
+ session, resumed = _select_session(runtime) if resume else (runtime.create_session(), False)
164
+ return run_repl(runtime, session, resumed=resumed)
165
+
166
+
167
+ def cmd_run(runtime: OpenAgentRuntime, prompt: str) -> int:
168
+ session = runtime.create_session()
169
+ streamer = ConsoleStreamer()
170
+ result = runtime.run_turn(session, prompt, text_callback=streamer)
171
+ if streamer.has_output:
172
+ streamer.finish()
173
+ elif result:
174
+ print(_prefix_first_line(render_markdown_text(result, ansi=sys.stdout.isatty()), _assistant_prefix(ansi=sys.stdout.isatty())))
175
+ return 0
176
+
177
+
178
+ def cmd_tasks_list(runtime: OpenAgentRuntime) -> int:
179
+ tasks = runtime.task_store.list_all()
180
+ if not tasks:
181
+ print("No tasks.")
182
+ else:
183
+ for task in tasks:
184
+ print(json.dumps(task, ensure_ascii=False, indent=2))
185
+ return 0
186
+
187
+
188
+ def cmd_tasks_get(runtime: OpenAgentRuntime, task_id: int) -> int:
189
+ print(json.dumps(runtime.task_store.get(task_id), ensure_ascii=False, indent=2))
190
+ return 0
191
+
192
+
193
+ def cmd_compact(runtime: OpenAgentRuntime) -> int:
194
+ session = runtime.latest_session()
195
+ runtime.compact_session(session)
196
+ print(f"Compacted session {session.id}")
197
+ return 0
198
+
199
+
200
+ def cmd_doctor(runtime: OpenAgentRuntime) -> int:
201
+ print(runtime.doctor())
202
+ return 0
openagent/cli/main.py ADDED
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from openagent.config.settings import load_settings
6
+ from openagent.runtime.agent import OpenAgentRuntime
7
+
8
+
9
+ def _add_provider_overrides(parser: argparse.ArgumentParser) -> None:
10
+ parser.add_argument(
11
+ "--provider",
12
+ default=argparse.SUPPRESS,
13
+ help="Override the configured provider for this invocation.",
14
+ )
15
+ parser.add_argument(
16
+ "--model",
17
+ default=argparse.SUPPRESS,
18
+ help="Override the configured model for this invocation.",
19
+ )
20
+
21
+
22
+ def build_parser() -> argparse.ArgumentParser:
23
+ parser = argparse.ArgumentParser(prog="openagent")
24
+ parser.add_argument("--workspace", default=".", help="Workspace root for the agent.")
25
+ parser.add_argument(
26
+ "-r",
27
+ "-resume",
28
+ "--resume",
29
+ dest="resume",
30
+ action="store_true",
31
+ help="Open the interactive session picker and resume a saved chat.",
32
+ )
33
+ _add_provider_overrides(parser)
34
+ subparsers = parser.add_subparsers(dest="command")
35
+
36
+ chat_parser = subparsers.add_parser("chat", help="Start interactive chat mode.")
37
+ chat_parser.add_argument(
38
+ "-r",
39
+ "-resume",
40
+ "--resume",
41
+ dest="resume",
42
+ action="store_true",
43
+ help="Open the interactive session picker and resume a saved chat.",
44
+ )
45
+ _add_provider_overrides(chat_parser)
46
+
47
+ run_parser = subparsers.add_parser("run", help="Run a single prompt.")
48
+ run_parser.add_argument("prompt", help="Prompt to execute.")
49
+ _add_provider_overrides(run_parser)
50
+
51
+ tasks_parser = subparsers.add_parser("tasks", help="Inspect persistent tasks.")
52
+ _add_provider_overrides(tasks_parser)
53
+ tasks_subparsers = tasks_parser.add_subparsers(dest="tasks_command", required=True)
54
+ tasks_subparsers.add_parser("list", help="List tasks.")
55
+ get_parser = tasks_subparsers.add_parser("get", help="Get a task by ID.")
56
+ get_parser.add_argument("task_id", type=int)
57
+
58
+ compact_parser = subparsers.add_parser("compact", help="Compact the latest session.")
59
+ _add_provider_overrides(compact_parser)
60
+ doctor_parser = subparsers.add_parser("doctor", help="Validate runtime configuration.")
61
+ _add_provider_overrides(doctor_parser)
62
+ return parser
63
+
64
+
65
+ def main(argv: list[str] | None = None) -> int:
66
+ parser = build_parser()
67
+ args = parser.parse_args(argv)
68
+ settings = load_settings(
69
+ args.workspace,
70
+ provider_override=getattr(args, "provider", None),
71
+ model_override=getattr(args, "model", None),
72
+ )
73
+ runtime = OpenAgentRuntime(settings)
74
+ try:
75
+ from openagent.cli.commands import (
76
+ cmd_chat,
77
+ cmd_compact,
78
+ cmd_doctor,
79
+ cmd_run,
80
+ cmd_tasks_get,
81
+ cmd_tasks_list,
82
+ )
83
+
84
+ if args.command in {None, "chat"}:
85
+ return cmd_chat(runtime, resume=getattr(args, "resume", False))
86
+ if args.command == "run":
87
+ return cmd_run(runtime, args.prompt)
88
+ if args.command == "tasks" and args.tasks_command == "list":
89
+ return cmd_tasks_list(runtime)
90
+ if args.command == "tasks" and args.tasks_command == "get":
91
+ return cmd_tasks_get(runtime, args.task_id)
92
+ if args.command == "compact":
93
+ return cmd_compact(runtime)
94
+ if args.command == "doctor":
95
+ return cmd_doctor(runtime)
96
+ parser.error("Unsupported command")
97
+ finally:
98
+ runtime.close()
99
+ return 1
100
+
101
+
102
+ if __name__ == "__main__":
103
+ raise SystemExit(main())