pal-code 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 (282) hide show
  1. pal/__init__.py +29 -0
  2. pal/__main__.py +3 -0
  3. pal/access_manager/__init__.py +16 -0
  4. pal/access_manager/components/__init__.py +14 -0
  5. pal/access_manager/components/access_control.py +41 -0
  6. pal/access_manager/components/base.py +39 -0
  7. pal/access_manager/components/intervention.py +31 -0
  8. pal/access_manager/components/privilege_registry.py +30 -0
  9. pal/access_manager/pipeline.py +68 -0
  10. pal/access_manager/utils/__init__.py +2 -0
  11. pal/agent_constraints/__init__.py +3 -0
  12. pal/agent_constraints/components/AGENT_OUTPUT.md +13 -0
  13. pal/agent_constraints/components/__init__.py +3 -0
  14. pal/agent_constraints/components/constraints.py +22 -0
  15. pal/agent_constraints/pipeline.py +22 -0
  16. pal/agent_constraints/utils/__init__.py +2 -0
  17. pal/agents/__init__.py +12 -0
  18. pal/agents/coding.py +14 -0
  19. pal/agents/docs.py +15 -0
  20. pal/chat/__init__.py +4 -0
  21. pal/chat/demo.py +231 -0
  22. pal/chat/providers.py +23 -0
  23. pal/chat/service.py +591 -0
  24. pal/chat/services/__init__.py +26 -0
  25. pal/chat/services/base.py +142 -0
  26. pal/chat/services/data.py +142 -0
  27. pal/chat/services/media.py +123 -0
  28. pal/chat/services/web.py +60 -0
  29. pal/chat/sessions.py +108 -0
  30. pal/chat/tools.py +446 -0
  31. pal/chat/uploads.py +81 -0
  32. pal/cli.py +502 -0
  33. pal/compression/__init__.py +12 -0
  34. pal/compression/components/__init__.py +64 -0
  35. pal/compression/components/adverbs.py +20 -0
  36. pal/compression/components/hedges.py +54 -0
  37. pal/compression/components/interjections.py +20 -0
  38. pal/compression/components/minifier.py +56 -0
  39. pal/compression/components/output_compressor.py +13 -0
  40. pal/compression/components/pleonasms.py +54 -0
  41. pal/compression/components/punctuations.py +36 -0
  42. pal/compression/components/stopwords.py +62 -0
  43. pal/compression/history.py +346 -0
  44. pal/compression/pipeline.py +58 -0
  45. pal/compression/utils/__init__.py +30 -0
  46. pal/compression/utils/negations.py +18 -0
  47. pal/compression/utils/nltk_setup.py +20 -0
  48. pal/compression/utils/pronouns.py +27 -0
  49. pal/compression/utils/proselint_setup.py +9 -0
  50. pal/compression/utils/spacy_setup.py +22 -0
  51. pal/config/default.yaml +163 -0
  52. pal/config.py +504 -0
  53. pal/context_manager/__init__.py +7 -0
  54. pal/context_manager/chat_context.py +117 -0
  55. pal/context_manager/components/__init__.py +5 -0
  56. pal/context_manager/components/assembler.py +75 -0
  57. pal/context_manager/components/context_cache.py +45 -0
  58. pal/context_manager/components/kv_bridge.py +16 -0
  59. pal/context_manager/components/pager.py +48 -0
  60. pal/context_manager/components/simple_context_manager.py +56 -0
  61. pal/context_manager/components/turns.py +88 -0
  62. pal/context_manager/pipeline.py +27 -0
  63. pal/context_manager/utils/__init__.py +18 -0
  64. pal/context_manager/utils/hashing.py +5 -0
  65. pal/context_manager/utils/relevance.py +59 -0
  66. pal/context_manager/utils/storage.py +37 -0
  67. pal/documents/__init__.py +4 -0
  68. pal/documents/components/__init__.py +0 -0
  69. pal/documents/components/chunker.py +79 -0
  70. pal/documents/components/index.py +155 -0
  71. pal/documents/utils/__init__.py +1 -0
  72. pal/errors.py +40 -0
  73. pal/input_converters/__init__.py +8 -0
  74. pal/input_converters/components/__init__.py +0 -0
  75. pal/input_converters/components/base.py +38 -0
  76. pal/input_converters/components/pandoc_converter.py +87 -0
  77. pal/input_converters/components/pdf_converter.py +46 -0
  78. pal/input_converters/components/pptx_converter.py +52 -0
  79. pal/input_converters/components/xlsx_converter.py +38 -0
  80. pal/input_converters/pipeline.py +72 -0
  81. pal/input_converters/utils/__init__.py +1 -0
  82. pal/input_converters/workspace.py +56 -0
  83. pal/input_savers/INPUT_RULES.md +2 -0
  84. pal/input_savers/__init__.py +5 -0
  85. pal/input_savers/components/__init__.py +0 -0
  86. pal/input_savers/components/base.py +40 -0
  87. pal/input_savers/components/methods.py +41 -0
  88. pal/input_savers/components/verifier.py +39 -0
  89. pal/input_savers/pipeline.py +154 -0
  90. pal/input_savers/utils/__init__.py +0 -0
  91. pal/input_savers/utils/guards.py +94 -0
  92. pal/knowledge_graph/__init__.py +10 -0
  93. pal/knowledge_graph/components/__init__.py +0 -0
  94. pal/knowledge_graph/components/base.py +50 -0
  95. pal/knowledge_graph/components/builder.py +54 -0
  96. pal/knowledge_graph/components/retriever.py +100 -0
  97. pal/knowledge_graph/components/workspace.py +116 -0
  98. pal/knowledge_graph/pipeline.py +215 -0
  99. pal/knowledge_graph/utils/__init__.py +1 -0
  100. pal/latency_tracker/__init__.py +3 -0
  101. pal/latency_tracker/pipeline.py +86 -0
  102. pal/llm_scheduler/__init__.py +11 -0
  103. pal/llm_scheduler/api.py +137 -0
  104. pal/llm_scheduler/code.py +28 -0
  105. pal/llm_scheduler/core.py +92 -0
  106. pal/llm_scheduler/local.py +215 -0
  107. pal/llm_scheduler/pipeline.py +42 -0
  108. pal/llm_scheduler/types.py +92 -0
  109. pal/llm_wrapper/__init__.py +34 -0
  110. pal/llm_wrapper/adapter.py +316 -0
  111. pal/llm_wrapper/components/__init__.py +23 -0
  112. pal/llm_wrapper/components/adapter_types.py +90 -0
  113. pal/llm_wrapper/components/base.py +195 -0
  114. pal/llm_wrapper/components/features.py +104 -0
  115. pal/llm_wrapper/components/litellm_wrapper.py +215 -0
  116. pal/llm_wrapper/components/openrouter_wrapper.py +114 -0
  117. pal/llm_wrapper/modes.py +73 -0
  118. pal/llm_wrapper/pipeline.py +61 -0
  119. pal/llm_wrapper/utils/__init__.py +4 -0
  120. pal/llm_wrapper/utils/registry.py +49 -0
  121. pal/llm_wrapper/utils/token_counting.py +10 -0
  122. pal/local_llm/__init__.py +5 -0
  123. pal/local_llm/components/__init__.py +0 -0
  124. pal/local_llm/components/huggingface_local.py +217 -0
  125. pal/local_llm/components/local_llm.py +60 -0
  126. pal/logging_setup.py +18 -0
  127. pal/memory/__init__.py +7 -0
  128. pal/memory/components/__init__.py +6 -0
  129. pal/memory/components/lru_k.py +56 -0
  130. pal/memory/components/manager.py +98 -0
  131. pal/memory/components/note.py +54 -0
  132. pal/memory/components/storage_manager.py +60 -0
  133. pal/memory/pipeline.py +122 -0
  134. pal/memory/utils/__init__.py +3 -0
  135. pal/memory/utils/keyword_overlap.py +3 -0
  136. pal/memory_tracker/__init__.py +3 -0
  137. pal/memory_tracker/components/__init__.py +0 -0
  138. pal/memory_tracker/components/process.py +56 -0
  139. pal/memory_tracker/pipeline.py +44 -0
  140. pal/metrics/__init__.py +4 -0
  141. pal/metrics/components/__init__.py +4 -0
  142. pal/metrics/components/collector.py +140 -0
  143. pal/metrics/components/exporter.py +28 -0
  144. pal/metrics/pipeline.py +49 -0
  145. pal/metrics/utils/__init__.py +2 -0
  146. pal/notes_manager/__init__.py +5 -0
  147. pal/notes_manager/components/__init__.py +0 -0
  148. pal/notes_manager/components/privacy.py +14 -0
  149. pal/notes_manager/components/recall.py +31 -0
  150. pal/notes_manager/components/tidy.py +28 -0
  151. pal/notes_manager/pipeline.py +138 -0
  152. pal/orchestrator/__init__.py +26 -0
  153. pal/orchestrator/components/__init__.py +26 -0
  154. pal/orchestrator/components/agent.py +105 -0
  155. pal/orchestrator/components/cgroup_manager.py +209 -0
  156. pal/orchestrator/components/context_interrupt.py +220 -0
  157. pal/orchestrator/components/limits.py +9 -0
  158. pal/orchestrator/components/scheduler.py +69 -0
  159. pal/orchestrator/components/syscall_scheduler.py +200 -0
  160. pal/orchestrator/pipeline.py +228 -0
  161. pal/orchestrator/utils/__init__.py +4 -0
  162. pal/orchestrator/utils/device.py +164 -0
  163. pal/orchestrator/utils/protocol.py +32 -0
  164. pal/os_kernel/__init__.py +5 -0
  165. pal/os_kernel/filesystem.py +80 -0
  166. pal/os_kernel/network.py +146 -0
  167. pal/os_kernel/pipeline.py +175 -0
  168. pal/os_kernel/sysinfo.py +36 -0
  169. pal/paths.py +59 -0
  170. pal/pipeline.py +177 -0
  171. pal/py.typed +0 -0
  172. pal/redact.py +19 -0
  173. pal/sdk/__init__.py +33 -0
  174. pal/sdk/adapters.py +43 -0
  175. pal/sdk/agent.py +99 -0
  176. pal/sdk/components/__init__.py +14 -0
  177. pal/sdk/components/autogen_adapter.py +65 -0
  178. pal/sdk/components/autogen_client.py +98 -0
  179. pal/sdk/components/framework_adapter_base.py +69 -0
  180. pal/sdk/components/metagpt_adapter.py +46 -0
  181. pal/sdk/components/open_interpreter_adapter.py +47 -0
  182. pal/sdk/components/tool_catalog.py +44 -0
  183. pal/sdk/os_api.py +84 -0
  184. pal/sdk/patterns.py +129 -0
  185. pal/sdk/pipeline.py +240 -0
  186. pal/sdk/queries.py +68 -0
  187. pal/sdk/savers.py +88 -0
  188. pal/sdk/utils/__init__.py +2 -0
  189. pal/storage_manager/__init__.py +17 -0
  190. pal/storage_manager/components/__init__.py +19 -0
  191. pal/storage_manager/components/base.py +50 -0
  192. pal/storage_manager/components/local_file_backend.py +84 -0
  193. pal/storage_manager/components/storage_queue.py +91 -0
  194. pal/storage_manager/components/vector_store_backend.py +122 -0
  195. pal/storage_manager/pipeline.py +153 -0
  196. pal/storage_manager/utils/__init__.py +4 -0
  197. pal/storage_manager/utils/memory_adapter.py +60 -0
  198. pal/storage_manager/utils/sdk.py +93 -0
  199. pal/studio/__init__.py +0 -0
  200. pal/studio/builder.py +166 -0
  201. pal/studio/catalog.py +90 -0
  202. pal/studio/checks.py +129 -0
  203. pal/studio/clients.py +6 -0
  204. pal/studio/editor.py +137 -0
  205. pal/studio/files.py +6 -0
  206. pal/studio/models.py +6 -0
  207. pal/studio/plan.py +149 -0
  208. pal/studio/planner.py +205 -0
  209. pal/studio/policy.py +69 -0
  210. pal/studio/routes.py +254 -0
  211. pal/studio/runs.py +210 -0
  212. pal/task_router/__init__.py +11 -0
  213. pal/task_router/components/__init__.py +0 -0
  214. pal/task_router/components/answerer.py +37 -0
  215. pal/task_router/components/base.py +34 -0
  216. pal/task_router/components/classifier.py +40 -0
  217. pal/task_router/components/gates.py +23 -0
  218. pal/task_router/pipeline.py +61 -0
  219. pal/task_router/utils/__init__.py +1 -0
  220. pal/terminal/__init__.py +1 -0
  221. pal/terminal/backend.py +141 -0
  222. pal/terminal/bench.py +173 -0
  223. pal/terminal/cli.py +826 -0
  224. pal/terminal/clients.py +170 -0
  225. pal/terminal/commands.py +401 -0
  226. pal/terminal/decide.py +242 -0
  227. pal/terminal/files.py +55 -0
  228. pal/terminal/flow_cli.py +439 -0
  229. pal/terminal/hardware.py +199 -0
  230. pal/terminal/history.py +88 -0
  231. pal/terminal/keys.py +83 -0
  232. pal/terminal/local_models.py +261 -0
  233. pal/terminal/logo.py +117 -0
  234. pal/terminal/models.py +99 -0
  235. pal/terminal/policy.py +54 -0
  236. pal/terminal/project.py +413 -0
  237. pal/terminal/review.py +65 -0
  238. pal/terminal/security.py +239 -0
  239. pal/terminal/session.py +503 -0
  240. pal/terminal/setup.py +354 -0
  241. pal/terminal/syntax.py +98 -0
  242. pal/terminal/theme.py +37 -0
  243. pal/terminal/ui.py +647 -0
  244. pal/terminal/voice.py +137 -0
  245. pal/terminal/workflow.py +624 -0
  246. pal/token_tracker/__init__.py +3 -0
  247. pal/token_tracker/pipeline.py +54 -0
  248. pal/tokens.py +21 -0
  249. pal/tool_catalog.py +107 -0
  250. pal/tool_manager/__init__.py +17 -0
  251. pal/tool_manager/builtin.py +82 -0
  252. pal/tool_manager/components/__init__.py +19 -0
  253. pal/tool_manager/components/base.py +53 -0
  254. pal/tool_manager/components/conflict_resolver.py +27 -0
  255. pal/tool_manager/components/param_validator.py +41 -0
  256. pal/tool_manager/components/tool_call_queue.py +111 -0
  257. pal/tool_manager/components/tool_registry.py +60 -0
  258. pal/tool_manager/pipeline.py +80 -0
  259. pal/tool_manager/utils/__init__.py +3 -0
  260. pal/web/__init__.py +3 -0
  261. pal/web/app.py +276 -0
  262. pal/web/static/editor.js +219 -0
  263. pal/web/static/fonts/LICENSE-Inter.txt +3 -0
  264. pal/web/static/fonts/inter-400.woff2 +0 -0
  265. pal/web/static/fonts/inter-500.woff2 +0 -0
  266. pal/web/static/fonts/inter-600.woff2 +0 -0
  267. pal/web/static/fonts/inter-700.woff2 +0 -0
  268. pal/web/static/inter.css +5 -0
  269. pal/web/static/pal-logo-256.png +0 -0
  270. pal/web/static/pal-logo-32.png +0 -0
  271. pal/web/static/pal-logo-64.png +0 -0
  272. pal/web/static/studio.css +306 -0
  273. pal/web/static/studio.js +533 -0
  274. pal/web/static/voice.js +76 -0
  275. pal/web/templates/index.html +368 -0
  276. pal/web/templates/studio.html +120 -0
  277. pal_code-0.1.0.dist-info/METADATA +379 -0
  278. pal_code-0.1.0.dist-info/RECORD +282 -0
  279. pal_code-0.1.0.dist-info/WHEEL +5 -0
  280. pal_code-0.1.0.dist-info/entry_points.txt +2 -0
  281. pal_code-0.1.0.dist-info/licenses/LICENSE +21 -0
  282. pal_code-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,65 @@
1
+ """
2
+ Adapter for AutoGen (Wu et al., 2023). AutoGen's ConversableAgent
3
+ ultimately calls out to an OpenAIWrapper instance's .create() method,
4
+ which builds the actual chat-completion request. This adapter
5
+ monkeypatches OpenAIWrapper.create to call the AgentSDK's chat()
6
+ instead, and wraps the reply back into the minimal ChatCompletion-like
7
+ shape (.choices[0].message.content) AutoGen's own code reads from --
8
+ so callers inside AutoGen don't notice the difference.
9
+
10
+ Targets the AutoGen 0.2 API (`autogen.oai.client.OpenAIWrapper`, the `pyautogen<0.3` / `ag2<1` line). The
11
+ redesigned AutoGen (autogen-agentchat 0.4+) has no such class; use `PalChatCompletionClient` for that one.
12
+ Text replies only: tool and function calling are not redirected.
13
+ """
14
+ from types import SimpleNamespace
15
+
16
+ from pal.sdk.components.framework_adapter_base import FrameworkAdapter, FrameworkAdapterError
17
+
18
+
19
+ class AutoGenAdapter(FrameworkAdapter):
20
+ framework_name = "autogen"
21
+
22
+ def install(self) -> None:
23
+ try:
24
+ from autogen.oai.client import OpenAIWrapper
25
+ except ImportError as e:
26
+ raise FrameworkAdapterError(
27
+ "AutoGen 0.2 is not installed -- run `pip install \"pyautogen==0.2.35\"` to use this adapter. For AutoGen 0.4+ "
28
+ "use pal.sdk.autogen_model_client(sdk) instead"
29
+ ) from e
30
+
31
+ if not hasattr(OpenAIWrapper, "create"):
32
+ raise FrameworkAdapterError(
33
+ "autogen.oai.client.OpenAIWrapper has no 'create' method -- this adapter "
34
+ "targets the AutoGen API this project was built against; a newer or older "
35
+ "AutoGen version may have changed it"
36
+ )
37
+
38
+ self._check_free(OpenAIWrapper.create)
39
+ self._target = OpenAIWrapper
40
+ self._original = OpenAIWrapper.create
41
+ sdk = self.sdk
42
+
43
+ def _redirected_create(self_wrapper, **kwargs):
44
+ messages = kwargs.get("messages", [])
45
+ call = {k: kwargs[k] for k in ("max_tokens", "temperature") if isinstance(kwargs.get(k), (int, float))}
46
+ if "max_tokens" in call:
47
+ call["max_tokens"] = int(call["max_tokens"])
48
+ resp = sdk.chat_response(messages, **call)
49
+ message = SimpleNamespace(content=resp.text, role="assistant", function_call=None, tool_calls=None)
50
+ choice = SimpleNamespace(message=message, finish_reason="stop", index=0)
51
+ usage = SimpleNamespace(prompt_tokens=resp.prompt_tokens, completion_tokens=resp.completion_tokens,
52
+ total_tokens=resp.prompt_tokens + resp.completion_tokens)
53
+ # AutoGen 0.2 reads these back from every response (extract_text_or_completion_object, usage, cost)
54
+ return SimpleNamespace(
55
+ choices=[choice], model=kwargs.get("model", "pal-code"), usage=usage, cost=0.0, config_id=0,
56
+ pass_filter=None,
57
+ message_retrieval_function=lambda r: [c.message.content for c in r.choices])
58
+
59
+ OpenAIWrapper.create = self._mark(_redirected_create)
60
+ self._installed = True
61
+
62
+ def uninstall(self) -> None:
63
+ if self._installed:
64
+ self._target.create = self._original
65
+ self._installed = False
@@ -0,0 +1,98 @@
1
+ """Pal-backed model client for the current AutoGen (autogen-agentchat 0.4 and later).
2
+
3
+ The redesigned AutoGen takes a `model_client=` object instead of calling a patchable class, so no monkeypatching
4
+ is needed: pass this client to any AutoGen agent and every model call goes through the Pal SDK.
5
+
6
+ sdk = AgentSDK.standalone(WrapperConfig(backend="gemini", model="...", api_key="..."))
7
+ agent = AssistantAgent("helper", model_client=autogen_model_client(sdk))
8
+
9
+ Text only: AutoGen's tool and function calling are not redirected (a clear error is raised if requested).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ from collections.abc import Sequence
15
+ from typing import Any
16
+
17
+ from pal.sdk.components.framework_adapter_base import FrameworkAdapterError
18
+ from pal.tokens import estimate_tokens
19
+
20
+
21
+ def autogen_model_client(sdk, model: str = "pal-code") -> Any:
22
+ try:
23
+ from autogen_core.models import (
24
+ AssistantMessage,
25
+ ChatCompletionClient,
26
+ CreateResult,
27
+ ModelInfo,
28
+ RequestUsage,
29
+ SystemMessage,
30
+ )
31
+ except ImportError as e:
32
+ raise FrameworkAdapterError(
33
+ "autogen-agentchat is not installed -- run `pip install autogen-agentchat` (AutoGen 0.4+) to use this client"
34
+ ) from e
35
+
36
+ def to_text(content: Any) -> str:
37
+ return content if isinstance(content, str) else " ".join(str(getattr(p, "content", p)) for p in content)
38
+
39
+ def convert(messages: Sequence[Any]) -> list[dict]:
40
+ out = []
41
+ for m in messages:
42
+ role = "system" if isinstance(m, SystemMessage) else "assistant" if isinstance(m, AssistantMessage) else "user"
43
+ out.append({"role": role, "content": to_text(getattr(m, "content", ""))})
44
+ return out
45
+
46
+ class PalChatCompletionClient(ChatCompletionClient):
47
+ def __init__(self) -> None:
48
+ self._sdk = sdk
49
+ self._total = RequestUsage(prompt_tokens=0, completion_tokens=0)
50
+ self._last = RequestUsage(prompt_tokens=0, completion_tokens=0)
51
+
52
+ async def create(self, messages, *, tools=(), tool_choice="auto", json_output=None, extra_create_args=None,
53
+ cancellation_token=None):
54
+ if tools:
55
+ raise FrameworkAdapterError("tool calling is not supported through the Pal model client")
56
+ args = dict(extra_create_args or {})
57
+ call = {k: args[k] for k in ("max_tokens", "temperature") if isinstance(args.get(k), (int, float))}
58
+ if "max_tokens" in call:
59
+ call["max_tokens"] = int(call["max_tokens"])
60
+ resp = await asyncio.to_thread(self._sdk.chat_response, convert(messages), **call) # never block the loop
61
+ self._last = RequestUsage(prompt_tokens=resp.prompt_tokens or 0, completion_tokens=resp.completion_tokens or 0)
62
+ self._total = RequestUsage(prompt_tokens=self._total.prompt_tokens + self._last.prompt_tokens,
63
+ completion_tokens=self._total.completion_tokens + self._last.completion_tokens)
64
+ return CreateResult(finish_reason="stop", content=resp.text, usage=self._last,
65
+ cached=bool(resp.meta.get("cache")))
66
+
67
+ async def create_stream(self, messages, *, tools=(), tool_choice="auto", json_output=None,
68
+ extra_create_args=None, cancellation_token=None):
69
+ result = await self.create(messages, tools=tools, tool_choice=tool_choice, json_output=json_output,
70
+ extra_create_args=extra_create_args, cancellation_token=cancellation_token)
71
+ yield str(result.content)
72
+ yield result
73
+
74
+ async def close(self) -> None:
75
+ return None
76
+
77
+ def actual_usage(self):
78
+ return self._total
79
+
80
+ def total_usage(self):
81
+ return self._total
82
+
83
+ def count_tokens(self, messages, *, tools=()) -> int:
84
+ return sum(estimate_tokens(m["content"]) for m in convert(messages))
85
+
86
+ def remaining_tokens(self, messages, *, tools=()) -> int:
87
+ return max(0, 100_000 - self.count_tokens(messages))
88
+
89
+ @property
90
+ def capabilities(self):
91
+ return self.model_info
92
+
93
+ @property
94
+ def model_info(self):
95
+ return ModelInfo(vision=False, function_calling=False, json_output=False, family=model,
96
+ structured_output=False)
97
+
98
+ return PalChatCompletionClient()
@@ -0,0 +1,69 @@
1
+ """
2
+ Agent framework adapters (AIOS section 3.9): let agents built with an
3
+ existing framework (Autogen, Open Interpreter, MetaGPT, ...) run inside
4
+ pal without any modification to the agent's own code. Each
5
+ adapter locates that framework's core LLM-completion function and
6
+ monkeypatches it to redirect through an AgentSDK's chat() instead -- so
7
+ the framework keeps its own agent-creation API, but every actual model
8
+ call goes through pal's llm_wrapper (compression, caching, paging,
9
+ the syscall scheduler, all of it) rather than the framework's own HTTP
10
+ client.
11
+
12
+ These adapters target each framework's documented core completion
13
+ entry point. A framework's internals can and do change between
14
+ releases, so install() raises a clear FrameworkAdapterError (naming the
15
+ missing attribute) rather than failing silently if a targeted attribute
16
+ path isn't found -- an adapter that silently no-ops would leave an
17
+ agent quietly calling the real API instead of pal, which is worse
18
+ than a loud failure that tells the caller exactly what broke.
19
+ """
20
+ from abc import ABC, abstractmethod
21
+
22
+ from pal.sdk.pipeline import AgentSDK
23
+
24
+
25
+ class FrameworkAdapterError(RuntimeError):
26
+ pass
27
+
28
+
29
+ class FrameworkAdapter(ABC):
30
+ framework_name: str = "base"
31
+
32
+ def __init__(self, sdk: AgentSDK):
33
+ self.sdk = sdk
34
+ self._installed = False
35
+
36
+ @abstractmethod
37
+ def install(self) -> None:
38
+ """Monkeypatches the framework's core completion function to
39
+ redirect through self.sdk.chat(). Raises FrameworkAdapterError
40
+ if the framework isn't installed, or if its expected entry
41
+ point isn't found (version mismatch)."""
42
+ ...
43
+
44
+ @abstractmethod
45
+ def uninstall(self) -> None:
46
+ """Restores the framework's original completion function."""
47
+ ...
48
+
49
+ @property
50
+ def installed(self) -> bool:
51
+ return self._installed
52
+
53
+ @staticmethod
54
+ def _check_free(fn) -> None:
55
+ """Refuse to patch a function another adapter already replaced: two SDKs cannot share one framework."""
56
+ if getattr(fn, "_pal_adapter", False):
57
+ raise FrameworkAdapterError("this framework is already adapted by another SDK; uninstall that one first")
58
+
59
+ @staticmethod
60
+ def _mark(fn):
61
+ fn._pal_adapter = True
62
+ return fn
63
+
64
+ def __enter__(self):
65
+ self.install()
66
+ return self
67
+
68
+ def __exit__(self, *exc) -> None:
69
+ self.uninstall()
@@ -0,0 +1,46 @@
1
+ """
2
+ Adapter for MetaGPT (Hong et al., 2023). Every MetaGPT LLM provider
3
+ (OpenAILLM, etc.) subclasses BaseLLM and implements `acompletion_text`
4
+ as the actual async call to the model. This adapter monkeypatches
5
+ BaseLLM.acompletion_text so MetaGPT's roles/actions keep working
6
+ unmodified while the underlying call routes through pal.
7
+ """
8
+ import asyncio
9
+
10
+ from pal.sdk.components.framework_adapter_base import FrameworkAdapter, FrameworkAdapterError
11
+
12
+
13
+ class MetaGPTAdapter(FrameworkAdapter):
14
+ framework_name = "metagpt"
15
+
16
+ def install(self) -> None:
17
+ try:
18
+ from metagpt.provider.base_llm import BaseLLM
19
+ except ImportError as e:
20
+ raise FrameworkAdapterError(
21
+ "metagpt is not installed -- run `pip install metagpt` to use this adapter"
22
+ ) from e
23
+
24
+ if not hasattr(BaseLLM, "acompletion_text"):
25
+ raise FrameworkAdapterError(
26
+ "metagpt.provider.base_llm.BaseLLM has no 'acompletion_text' method -- this "
27
+ "adapter targets the MetaGPT API this project was built against; a newer or "
28
+ "older version may have changed it"
29
+ )
30
+
31
+ self._check_free(BaseLLM.acompletion_text)
32
+ self._target = BaseLLM
33
+ self._original = BaseLLM.acompletion_text
34
+ sdk = self.sdk
35
+
36
+ async def _redirected_acompletion_text(self_llm, messages, stream=False, timeout=None):
37
+ # the SDK call is synchronous: run it in a thread so MetaGPT's event loop is never blocked
38
+ return await asyncio.to_thread(sdk.chat, messages)
39
+
40
+ BaseLLM.acompletion_text = self._mark(_redirected_acompletion_text)
41
+ self._installed = True
42
+
43
+ def uninstall(self) -> None:
44
+ if self._installed:
45
+ self._target.acompletion_text = self._original
46
+ self._installed = False
@@ -0,0 +1,47 @@
1
+ """
2
+ Adapter for Open Interpreter (Lucas, 2024). Open Interpreter's core
3
+ completion call runs through its `interpreter.llm` object's
4
+ `completions` generator (normally built on top of litellm). This
5
+ adapter monkeypatches that object's `completions` attribute so the
6
+ AgentSDK is what actually produces tokens, while Open Interpreter's own
7
+ tool-execution loop keeps running unmodified.
8
+ """
9
+ from pal.sdk.components.framework_adapter_base import FrameworkAdapter, FrameworkAdapterError
10
+
11
+
12
+ class OpenInterpreterAdapter(FrameworkAdapter):
13
+ framework_name = "open_interpreter"
14
+
15
+ def install(self) -> None:
16
+ try:
17
+ import interpreter as oi
18
+ except ImportError as e:
19
+ raise FrameworkAdapterError(
20
+ "open-interpreter is not installed -- run `pip install open-interpreter` to use this adapter"
21
+ ) from e
22
+
23
+ llm = getattr(oi.interpreter, "llm", None)
24
+ if llm is None:
25
+ raise FrameworkAdapterError(
26
+ "interpreter.interpreter.llm was not found -- this adapter targets the "
27
+ "Open Interpreter API this project was built against; a newer or older "
28
+ "version may have changed it"
29
+ )
30
+
31
+ self._check_free(getattr(llm, "completions", None))
32
+ self._target = llm
33
+ self._original = getattr(llm, "completions", None)
34
+ sdk = self.sdk
35
+
36
+ def _redirected_completions(**kwargs):
37
+ messages = kwargs.get("messages", [])
38
+ text = sdk.chat(messages)
39
+ yield {"choices": [{"delta": {"content": text}}]}
40
+
41
+ llm.completions = self._mark(_redirected_completions)
42
+ self._installed = True
43
+
44
+ def uninstall(self) -> None:
45
+ if self._installed:
46
+ self._target.completions = self._original
47
+ self._installed = False
@@ -0,0 +1,44 @@
1
+ """
2
+ A small built-in tool catalog (AIOS section 3.9's "Tool Integration"):
3
+ a minimal, dependency-free set of example tools covering a few
4
+ different input/output modalities (free text, structured expressions,
5
+ plain numbers), registered into a ToolManagerPipeline so a fresh
6
+ AgentSDK has something callable out of the box.
7
+
8
+ This is intentionally small. AIOS's own SDK ships a much larger
9
+ catalog of tools sourced from real external platforms, which needs
10
+ network access and API keys this project doesn't assume are available.
11
+ Real deployments register their own tools alongside (or instead of)
12
+ these via ToolManagerPipeline.register_tool() -- this catalog is a
13
+ starting point, not a ceiling.
14
+ """
15
+ import time
16
+
17
+ from pal.tool_manager.builtin import safe_eval
18
+ from pal.tool_manager.components.base import ToolParam
19
+ from pal.tool_manager.pipeline import ToolManagerPipeline
20
+
21
+
22
+ def _calculator():
23
+ # AST-based evaluator: never eval(), so it cannot execute code and cannot be made to hang.
24
+ return safe_eval
25
+
26
+
27
+ def _current_time():
28
+ def current_time() -> str:
29
+ return time.strftime("%Y-%m-%d %H:%M:%S")
30
+
31
+ return current_time
32
+
33
+
34
+ def _word_count():
35
+ def word_count(text: str) -> int:
36
+ return len(text.split())
37
+
38
+ return word_count
39
+
40
+
41
+ def register_builtin_tools(tool_manager: ToolManagerPipeline) -> None:
42
+ tool_manager.register_tool("calculator", _calculator, params=[ToolParam("expression", str)])
43
+ tool_manager.register_tool("current_time", _current_time, params=[])
44
+ tool_manager.register_tool("word_count", _word_count, params=[ToolParam("text", str)])
pal/sdk/os_api.py ADDED
@@ -0,0 +1,84 @@
1
+ """`sdk.os`: friendly names for the OS-kernel queries. Each call is an OSQuery routed to the OS kernel; a refusal
2
+ raises OSKernelError instead of returning an error value."""
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pal.os_kernel import OSKernelError
8
+ from pal.sdk.queries import OSQuery
9
+
10
+
11
+ class OSAPI:
12
+ def __init__(self, sdk: Any):
13
+ self._sdk = sdk
14
+
15
+ def _call(self, op: str, **params: Any) -> Any:
16
+ resp = self._sdk.send_request(OSQuery(op=op, params=params))
17
+ if not resp.ok:
18
+ raise OSKernelError(resp.error)
19
+ return resp.data
20
+
21
+ def read_file(self, path: str) -> str:
22
+ return self._call("file_read", path=path)
23
+
24
+ def write_file(self, path: str, content: str) -> dict:
25
+ return self._call("file_write", path=path, content=content)
26
+
27
+ def read_bytes(self, path: str) -> bytes:
28
+ """A workspace file as bytes (images, audio, PDFs, ...), within the OS kernel's binary size limit."""
29
+ import base64
30
+ return base64.b64decode(self._call("file_read_b64", path=path)["b64"])
31
+
32
+ def file_to_markdown(self, path: str) -> str:
33
+ """Convert a PDF, Word, PowerPoint, Excel, EPUB, HTML or LaTeX file in the workspace to markdown, save it under converted/,
34
+ and return a description with the beginning of the text."""
35
+ from pal.errors import PalError
36
+ from pal.input_converters.components.base import ConversionError
37
+ from pal.input_converters.workspace import convert_workspace_file, describe
38
+ try:
39
+ r = convert_workspace_file(path, read_bytes=self.read_bytes, write_text=lambda p, t: self.write_file(p, t),
40
+ exists=lambda p: p in self.list_files(p))
41
+ except ConversionError as e:
42
+ raise PalError(str(e)) from e
43
+ return describe(r)
44
+
45
+ def list_files(self, prefix: str = "") -> list[str]:
46
+ return self._call("file_list", prefix=prefix)
47
+
48
+ def delete_file(self, path: str) -> dict:
49
+ return self._call("file_delete", path=path)
50
+
51
+ def http_get(self, url: str) -> dict:
52
+ return self._call("net_get", url=url)
53
+
54
+ def http_post(self, url: str, json: Any = None) -> dict:
55
+ return self._call("net_post", url=url, json=json)
56
+
57
+ def system_info(self) -> dict:
58
+ return self._call("sys_info")
59
+
60
+ def keyboard(self, **kw: Any) -> Any:
61
+ return self._call("keyboard", **kw)
62
+
63
+ def mouse(self, **kw: Any) -> Any:
64
+ return self._call("mouse", **kw)
65
+
66
+
67
+ def register_os_tools(sdk: Any) -> list[str]:
68
+ """Make the OS operations callable as tools (for ReAct and similar agents). The tools run as OSQuery calls, so they
69
+ reach the OS kernel and never a model. Not registered by default: the chat's tools stay read-only."""
70
+ from pal.tool_manager.components.base import ToolParam
71
+
72
+ tm = sdk._tool_manager
73
+ api = sdk.os
74
+ specs = [
75
+ ("read_file", lambda: api.read_file, [ToolParam("path", str)]),
76
+ ("write_file", lambda: api.write_file, [ToolParam("path", str), ToolParam("content", str)]),
77
+ ("list_files", lambda: api.list_files, [ToolParam("prefix", str, required=False, default="")]),
78
+ ("file_to_markdown", lambda: api.file_to_markdown, [ToolParam("path", str)]),
79
+ ("http_get", lambda: api.http_get, [ToolParam("url", str)]),
80
+ ("system_info", lambda: api.system_info, []),
81
+ ]
82
+ for name, loader, params in specs:
83
+ tm.register_tool(name, loader, params=params)
84
+ return [n for n, _, _ in specs]
pal/sdk/patterns.py ADDED
@@ -0,0 +1,129 @@
1
+ """ReAct and Reflexion as native agent patterns.
2
+
3
+ Both are reasoning *patterns* (papers), not libraries with a single call to intercept, so Pal ships them as
4
+ ready-made native agents built only on SDK calls. Subclass or configure them to make your own agent.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import re
10
+ from collections.abc import Callable
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from pal.sdk.agent import AgentResult, NativeAgent
15
+
16
+ _FINAL = re.compile(r"Final Answer:\s*(.*)", re.S | re.I)
17
+ _ACTION = re.compile(r"^\s*Action:\s*([A-Za-z_][\w.-]*)\s*(.*)$", re.M)
18
+ _CUT = re.compile(r"\n\s*Observation:.*", re.S)
19
+
20
+ REACT_FORMAT = (
21
+ "Solve the task step by step. In each reply write 'Thought: <short reasoning>' and then either "
22
+ "'Action: <tool_name> {\"arg\": value}' (JSON arguments) or 'Final Answer: <answer>'. After an Action you "
23
+ "will receive 'Observation: <result>'. Tools: {tools}.")
24
+
25
+
26
+ @dataclass
27
+ class ReActStep:
28
+ thought: str
29
+ tool: str
30
+ args: dict
31
+ observation: str
32
+
33
+
34
+ class ReActAgent(NativeAgent):
35
+ """Reason, act, observe (Yao et al., 2023): the model alternates thoughts with tool calls until it answers."""
36
+
37
+ max_steps = 6
38
+ max_tokens = 512
39
+ tools: list[str] | None = None # tool names to offer; None = every tool the SDK has
40
+
41
+ def run(self, task: str, attachments: list[dict] | None = None, session: str = "agent") -> AgentResult:
42
+ names = self.tools if self.tools is not None else self.sdk.list_tools()
43
+ role = self.spec.role + "\n" if self.spec else ""
44
+ transcript: list[dict] = [{"role": "system", "content": role + REACT_FORMAT.replace("{tools}", ", ".join(names) or "none")},
45
+ {"role": "user", "content": task}]
46
+ steps: list[ReActStep] = []
47
+ for _ in range(self.max_steps):
48
+ text = _CUT.sub("", self.sdk.chat(transcript, max_tokens=self.max_tokens) or "") # ignore invented observations
49
+ final = _FINAL.search(text)
50
+ if final:
51
+ return AgentResult(final.group(1).strip(), steps=steps)
52
+ act = _ACTION.search(text)
53
+ if act is None: # no action and no marker: treat the reply as the answer
54
+ return AgentResult(text.strip(), steps=steps)
55
+ tool, rest = act.group(1), act.group(2).strip()
56
+ try:
57
+ args = json.loads(rest) if rest else {}
58
+ if not isinstance(args, dict):
59
+ raise ValueError("arguments must be a JSON object")
60
+ observation = str(self.sdk.call_tool(tool, **args))
61
+ except Exception as e: # noqa: BLE001 (a bad call becomes an observation the model can react to)
62
+ args, observation = {}, f"error: {e}"
63
+ thought = text.split("Action:")[0].replace("Thought:", "").strip()
64
+ steps.append(ReActStep(thought, tool, args, observation))
65
+ transcript += [{"role": "assistant", "content": text}, {"role": "user", "content": f"Observation: {observation}"}]
66
+ return AgentResult("", ok=False, steps=steps, error=f"no final answer after {self.max_steps} steps")
67
+
68
+
69
+ @dataclass
70
+ class Trial:
71
+ answer: str
72
+ ok: bool
73
+ feedback: str
74
+ reflection: str = ""
75
+
76
+
77
+ Evaluator = Callable[[str, str], "bool | tuple[bool, str]"]
78
+
79
+
80
+ class ReflexionAgent(NativeAgent):
81
+ """Try, evaluate, reflect, retry (Shinn et al., 2023).
82
+
83
+ The actor (any native agent; plain model call by default) answers. An evaluator (your function, or the model
84
+ judging itself) says pass or fail. On failure the model writes a short lesson, which is kept in memory and
85
+ shown to the next attempt.
86
+ """
87
+
88
+ max_trials = 3
89
+ max_tokens = 400
90
+
91
+ def __init__(self, sdk: Any = None, kernel: Any = None, actor: NativeAgent | None = None,
92
+ evaluator: Evaluator | None = None):
93
+ super().__init__(sdk, kernel)
94
+ self.actor = actor
95
+ self.evaluator = evaluator
96
+
97
+ def _act(self, prompt: str) -> str:
98
+ if self.actor is not None:
99
+ return self.actor.run(prompt).answer
100
+ return self.sdk.chat([{"role": "user", "content": prompt}], max_tokens=self.max_tokens)
101
+
102
+ def _evaluate(self, task: str, answer: str) -> tuple[bool, str]:
103
+ if self.evaluator is not None:
104
+ r = self.evaluator(task, answer)
105
+ return (r, "") if isinstance(r, bool) else (bool(r[0]), str(r[1]))
106
+ verdict = self.sdk.chat([
107
+ {"role": "system", "content": "Judge whether the answer correctly and completely solves the task. "
108
+ "Reply 'PASS' or 'FAIL: <reason>'."},
109
+ {"role": "user", "content": f"Task: {task}\nAnswer: {answer}"}], max_tokens=80) or ""
110
+ return verdict.strip().upper().startswith("PASS"), verdict.strip()
111
+
112
+ def run(self, task: str, attachments: list[dict] | None = None, session: str = "agent") -> AgentResult:
113
+ lessons: list[str] = []
114
+ trials: list[Trial] = []
115
+ for _ in range(self.max_trials):
116
+ prompt = task if not lessons else task + "\n\nLessons from earlier attempts:\n- " + "\n- ".join(lessons)
117
+ answer = self._act(prompt)
118
+ ok, feedback = self._evaluate(task, answer)
119
+ trial = Trial(answer, ok, feedback)
120
+ trials.append(trial)
121
+ if ok:
122
+ return AgentResult(answer, steps=trials)
123
+ trial.reflection = (self.sdk.chat([
124
+ {"role": "system", "content": "In one or two sentences say what went wrong and what to do differently."},
125
+ {"role": "user", "content": f"Task: {task}\nAnswer: {answer}\nFeedback: {feedback}"}],
126
+ max_tokens=120) or "").strip()
127
+ lessons.append(trial.reflection)
128
+ self.sdk.remember(trial.reflection, tags=["reflexion"])
129
+ return AgentResult(trials[-1].answer, ok=False, steps=trials, error=f"not solved in {self.max_trials} trials")