vibecore 0.4.2__py3-none-any.whl → 0.5.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.

Potentially problematic release.


This version of vibecore might be problematic. Click here for more details.

vibecore/flow.py CHANGED
@@ -1,13 +1,29 @@
1
1
  import asyncio
2
+ import contextlib
3
+ import datetime
4
+ import sys
2
5
  import threading
3
6
  from collections.abc import Callable, Coroutine
4
- from typing import Protocol
7
+ from typing import Any, Generic, Protocol, TypeAlias, overload
5
8
 
6
- from agents import Agent
9
+ from agents import (
10
+ Agent,
11
+ RunConfig,
12
+ RunHooks,
13
+ Runner,
14
+ Session,
15
+ TContext,
16
+ TResponseInputItem,
17
+ )
18
+ from agents.result import RunResultBase
19
+ from agents.run import DEFAULT_MAX_TURNS
7
20
  from textual.pilot import Pilot
21
+ from typing_extensions import TypeVar
8
22
 
9
23
  from vibecore.context import VibecoreContext
10
24
  from vibecore.main import AppIsExiting, VibecoreApp
25
+ from vibecore.session import JSONLSession
26
+ from vibecore.settings import settings
11
27
  from vibecore.widgets.core import MyTextArea
12
28
  from vibecore.widgets.messages import SystemMessage
13
29
 
@@ -27,79 +43,303 @@ class UserInputFunc(Protocol):
27
43
  ...
28
44
 
29
45
 
30
- async def flow(
31
- agent: Agent,
32
- logic: Callable[[VibecoreApp, VibecoreContext, UserInputFunc], Coroutine],
33
- headless: bool = False,
34
- shutdown: bool = False,
35
- disable_user_input: bool = True,
36
- ):
37
- ctx = VibecoreContext()
38
- app = VibecoreApp(ctx, agent, show_welcome=False)
46
+ TWorkflowReturn = TypeVar("TWorkflowReturn", default=RunResultBase)
47
+ DecoratedCallable: TypeAlias = Callable[..., Coroutine[Any, Any, TWorkflowReturn]]
39
48
 
40
- app_ready_event = asyncio.Event()
41
49
 
42
- def on_app_ready() -> None:
50
+ class VibecoreRunnerBase(Generic[TWorkflowReturn]):
51
+ def __init__(self, vibecore: "Vibecore[TWorkflowReturn]") -> None:
52
+ self.vibecore = vibecore
53
+
54
+ @property
55
+ def session(self) -> Session:
56
+ raise NotImplementedError("session property implemented.")
57
+
58
+ async def user_input(self, prompt: str = "") -> str:
59
+ raise NotImplementedError("user_input method not implemented.")
60
+
61
+ async def print(self, message: str) -> None:
62
+ print(message, file=sys.stderr)
63
+
64
+ async def run_agent(
65
+ self,
66
+ starting_agent: Agent[TContext],
67
+ input: str | list[TResponseInputItem],
68
+ *,
69
+ context: TContext | None = None,
70
+ max_turns: int = DEFAULT_MAX_TURNS,
71
+ hooks: RunHooks[TContext] | None = None,
72
+ run_config: RunConfig | None = None,
73
+ previous_response_id: str | None = None,
74
+ session: Session | None = None,
75
+ ) -> RunResultBase:
76
+ result = await Runner.run(
77
+ starting_agent=starting_agent,
78
+ input=input, # Pass string directly when using session
79
+ context=context,
80
+ max_turns=max_turns,
81
+ hooks=hooks,
82
+ run_config=run_config,
83
+ previous_response_id=previous_response_id,
84
+ session=session,
85
+ )
86
+ return result
87
+
88
+
89
+ class VibecoreSimpleRunner(VibecoreRunnerBase[TWorkflowReturn]):
90
+ def __init__(self, vibecore: "Vibecore[TWorkflowReturn]") -> None:
91
+ super().__init__(vibecore)
92
+
93
+ session_id = f"chat-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}"
94
+ self._session = JSONLSession(
95
+ session_id=session_id,
96
+ project_path=None, # Will use current working directory
97
+ base_dir=settings.session.base_dir,
98
+ )
99
+
100
+ @property
101
+ def session(self) -> Session:
102
+ return self._session
103
+
104
+
105
+ class VibecoreCliRunner(VibecoreSimpleRunner[TWorkflowReturn]):
106
+ def __init__(self, vibecore: "Vibecore[TWorkflowReturn]") -> None:
107
+ super().__init__(vibecore)
108
+
109
+ async def user_input(self, prompt: str = "") -> str:
110
+ return input(prompt)
111
+
112
+ async def run(self) -> TWorkflowReturn:
113
+ assert self.vibecore.workflow_logic is not None, (
114
+ "Workflow logic not defined. Please use the @vibecore.workflow() decorator."
115
+ )
116
+ return await self.vibecore.workflow_logic()
117
+
118
+
119
+ class VibecoreStaticRunner(VibecoreSimpleRunner[TWorkflowReturn]):
120
+ def __init__(self, vibecore: "Vibecore[TWorkflowReturn]") -> None:
121
+ super().__init__(vibecore)
122
+ self.inputs: list[str] = []
123
+ self.prints: list[str] = []
124
+
125
+ async def user_input(self, prompt: str = "") -> str:
126
+ assert self.inputs, "No more user inputs available."
127
+ return self.inputs.pop()
128
+
129
+ async def print(self, message: str) -> None:
130
+ # Capture printed messages instead of displaying them
131
+ self.prints.append(message)
132
+
133
+ async def run(self, inputs: list[str] | None = None) -> TWorkflowReturn:
134
+ if inputs is None:
135
+ inputs = []
136
+ assert self.vibecore.workflow_logic is not None, (
137
+ "Workflow logic not defined. Please use the @vibecore.workflow() decorator."
138
+ )
139
+ self.inputs.extend(inputs)
140
+ return await self.vibecore.workflow_logic()
141
+
142
+
143
+ class VibecoreTextualRunner(VibecoreRunnerBase[TWorkflowReturn]):
144
+ def __init__(self, vibecore: "Vibecore[TWorkflowReturn]") -> None:
145
+ super().__init__(vibecore)
146
+ self.app = VibecoreApp(self.vibecore.context, self.vibecore.starting_agent, show_welcome=False)
147
+ self.app_ready_event = asyncio.Event()
148
+
149
+ @property
150
+ def session(self) -> Session:
151
+ return self.app.session
152
+
153
+ async def user_input(self, prompt: str = "") -> str:
154
+ if prompt:
155
+ await self.print(prompt)
156
+ self.app.query_one(MyTextArea).disabled = False
157
+ self.app.query_one(MyTextArea).focus()
158
+ user_input = await self.app.wait_for_user_input()
159
+ if self.vibecore.disable_user_input:
160
+ self.app.query_one(MyTextArea).disabled = True
161
+ return user_input
162
+
163
+ async def print(self, message: str) -> None:
164
+ await self.app.add_message(SystemMessage(message))
165
+
166
+ async def run_agent(
167
+ self,
168
+ starting_agent: Agent[TContext],
169
+ input: str | list[TResponseInputItem],
170
+ *,
171
+ context: TContext | None = None,
172
+ max_turns: int = DEFAULT_MAX_TURNS,
173
+ hooks: RunHooks[TContext] | None = None,
174
+ run_config: RunConfig | None = None,
175
+ previous_response_id: str | None = None,
176
+ session: Session | None = None,
177
+ ) -> RunResultBase:
178
+ result = Runner.run_streamed(
179
+ starting_agent=starting_agent,
180
+ input=input, # Pass string directly when using session
181
+ context=context,
182
+ max_turns=max_turns,
183
+ hooks=hooks,
184
+ run_config=run_config,
185
+ previous_response_id=previous_response_id,
186
+ session=session,
187
+ )
188
+
189
+ self.app.current_worker = self.app.handle_streamed_response(result)
190
+ await self.app.current_worker.wait()
191
+ return result
192
+
193
+ def on_app_ready(self) -> None:
43
194
  """Called when app is ready to process events."""
44
- app_ready_event.set()
195
+ self.app_ready_event.set()
45
196
 
46
- async def run_app(app: VibecoreApp) -> None:
197
+ async def _run_app(self) -> None:
47
198
  """Run the apps message loop.
48
199
 
49
200
  Args:
50
201
  app: App to run.
51
202
  """
52
203
 
53
- with app._context():
204
+ with self.app._context():
54
205
  try:
55
- app._loop = asyncio.get_running_loop()
56
- app._thread_id = threading.get_ident()
57
- await app._process_messages(
58
- ready_callback=on_app_ready,
59
- headless=headless,
206
+ self.app._loop = asyncio.get_running_loop()
207
+ self.app._thread_id = threading.get_ident()
208
+ await self.app._process_messages(
209
+ ready_callback=self.on_app_ready,
210
+ headless=False,
60
211
  )
61
212
  finally:
62
- app_ready_event.set()
213
+ self.app_ready_event.set()
63
214
 
64
- async def user_input(prompt: str = "") -> str:
65
- if prompt:
66
- await app.add_message(SystemMessage(prompt))
67
- app.query_one(MyTextArea).disabled = False
68
- app.query_one(MyTextArea).focus()
69
- user_input = await app.wait_for_user_input()
70
- if disable_user_input:
71
- app.query_one(MyTextArea).disabled = True
72
- return user_input
73
-
74
- async def run_logic(app: VibecoreApp, ctx: VibecoreContext, user_input: UserInputFunc) -> None:
215
+ async def _run_logic(self) -> TWorkflowReturn:
216
+ assert self.vibecore.workflow_logic is not None, (
217
+ "Workflow logic not defined. Please use the @vibecore.workflow() decorator."
218
+ )
75
219
  try:
76
- await logic(app, ctx, user_input)
220
+ return await self.vibecore.workflow_logic()
77
221
  except AppIsExiting:
78
- return
79
-
80
- app_task = asyncio.create_task(run_app(app), name=f"with_app({app})")
81
- await app_ready_event.wait()
82
- pilot = Pilot(app)
83
- logic_task: asyncio.Task | None = None
84
-
85
- await pilot._wait_for_screen()
86
- if disable_user_input:
87
- app.query_one(MyTextArea).disabled = True
88
- logic_task = asyncio.create_task(run_logic(app, ctx, user_input), name="logic_task")
89
- done, pending = await asyncio.wait([logic_task, app_task], return_when=asyncio.FIRST_COMPLETED)
90
-
91
- # If app has exited and logic is still running, cancel logic
92
- if app_task in done and logic_task in pending:
93
- logic_task.cancel()
94
- # If logic is finished and app is still running
95
- elif logic_task in done and app_task in pending:
96
- if shutdown:
97
- if not headless:
222
+ raise
223
+
224
+ async def run(self, shutdown: bool = False) -> TWorkflowReturn:
225
+ self.app = VibecoreApp(self.vibecore.context, self.vibecore.starting_agent, show_welcome=False)
226
+ app_task = asyncio.create_task(self._run_app(), name=f"run_app({self.app})")
227
+ await self.app_ready_event.wait()
228
+ pilot = Pilot(self.app)
229
+ logic_task: asyncio.Task[TWorkflowReturn] | None = None
230
+
231
+ await pilot._wait_for_screen()
232
+ if self.vibecore.disable_user_input:
233
+ self.app.query_one(MyTextArea).disabled = True
234
+ logic_task = asyncio.create_task(self._run_logic(), name="logic_task")
235
+ done, pending = await asyncio.wait([logic_task, app_task], return_when=asyncio.FIRST_COMPLETED)
236
+
237
+ # If app has exited and logic is still running, cancel logic
238
+ if app_task in done and logic_task in pending:
239
+ logic_task.cancel()
240
+ with contextlib.suppress(asyncio.CancelledError):
241
+ await logic_task
242
+ raise AppIsExiting()
243
+ # If logic is finished and app is still running
244
+ elif logic_task in done and app_task in pending:
245
+ result = logic_task.result()
246
+ if shutdown:
98
247
  await pilot._wait_for_screen()
99
248
  await asyncio.sleep(1.0)
100
- app.exit()
101
- else:
102
- # Enable text input so users can interact freely
103
- app.query_one(MyTextArea).disabled = False
104
- # Wait until app is exited
105
- await app_task
249
+ self.app.exit()
250
+ await app_task
251
+ else:
252
+ # Enable text input so users can interact freely
253
+ self.app.query_one(MyTextArea).disabled = False
254
+ # Wait until app is exited
255
+ await app_task
256
+ return result
257
+
258
+ raise RuntimeError("Unexpected state: both tasks completed")
259
+
260
+
261
+ class Vibecore(Generic[TWorkflowReturn]):
262
+ def __init__(self, starting_agent: Agent[TContext], disable_user_input: bool = True) -> None:
263
+ self.context = VibecoreContext()
264
+ self.workflow_logic: Callable[..., Coroutine[Any, Any, TWorkflowReturn]] | None = None
265
+ self.starting_agent = starting_agent
266
+ self.disable_user_input = disable_user_input
267
+ self.runner: VibecoreRunnerBase[TWorkflowReturn] = VibecoreRunnerBase(self)
268
+
269
+ @property
270
+ def session(self) -> Session:
271
+ return self.runner.session
272
+
273
+ async def user_input(self, prompt: str = "") -> str:
274
+ return await self.runner.user_input(prompt)
275
+
276
+ async def print(self, message: str) -> None:
277
+ return await self.runner.print(message)
278
+
279
+ def workflow(self) -> Callable[[DecoratedCallable[TWorkflowReturn]], DecoratedCallable[TWorkflowReturn]]:
280
+ """Decorator to define the workflow logic for the app.
281
+
282
+ Returns:
283
+ A decorator that wraps the workflow logic function.
284
+ """
285
+
286
+ def decorator(
287
+ func: DecoratedCallable[TWorkflowReturn],
288
+ ) -> DecoratedCallable[TWorkflowReturn]:
289
+ self.workflow_logic = func
290
+ return func
291
+
292
+ return decorator
293
+
294
+ async def run_agent(
295
+ self,
296
+ starting_agent: Agent[TContext],
297
+ input: str | list[TResponseInputItem],
298
+ *,
299
+ context: TContext | None = None,
300
+ max_turns: int = DEFAULT_MAX_TURNS,
301
+ hooks: RunHooks[TContext] | None = None,
302
+ run_config: RunConfig | None = None,
303
+ previous_response_id: str | None = None,
304
+ session: Session | None = None,
305
+ ) -> RunResultBase:
306
+ return await self.runner.run_agent(
307
+ starting_agent=starting_agent,
308
+ input=input,
309
+ context=context,
310
+ max_turns=max_turns,
311
+ hooks=hooks,
312
+ run_config=run_config,
313
+ previous_response_id=previous_response_id,
314
+ session=session,
315
+ )
316
+
317
+ async def run_textual(self, shutdown: bool = False) -> TWorkflowReturn:
318
+ if self.workflow_logic is None:
319
+ raise ValueError("Workflow logic not defined. Please use the @vibecore.workflow() decorator.")
320
+
321
+ self.runner = VibecoreTextualRunner(self)
322
+ return await self.runner.run(shutdown=shutdown)
323
+
324
+ async def run_cli(self) -> TWorkflowReturn:
325
+ if self.workflow_logic is None:
326
+ raise ValueError("Workflow logic not defined. Please use the @vibecore.workflow() decorator.")
327
+
328
+ self.runner = VibecoreCliRunner(self)
329
+ return await self.runner.run()
330
+
331
+ @overload
332
+ async def run(self, inputs: str) -> TWorkflowReturn: ...
333
+
334
+ @overload
335
+ async def run(self, inputs: list[str]) -> TWorkflowReturn: ...
336
+
337
+ async def run(self, inputs: str | list[str]) -> TWorkflowReturn:
338
+ if isinstance(inputs, str):
339
+ inputs = [inputs]
340
+
341
+ if self.workflow_logic is None:
342
+ raise ValueError("Workflow logic not defined. Please use the @vibecore.workflow() decorator.")
343
+
344
+ self.runner = VibecoreStaticRunner(self)
345
+ return await self.runner.run(inputs=inputs)
@@ -5,6 +5,8 @@ import logging
5
5
  from pathlib import Path
6
6
  from typing import TYPE_CHECKING
7
7
 
8
+ from agents import Session
9
+
8
10
  if TYPE_CHECKING:
9
11
  from openai.types.responses import ResponseInputItemParam as TResponseInputItem
10
12
 
@@ -14,7 +16,7 @@ from .path_utils import get_session_file_path
14
16
  logger = logging.getLogger(__name__)
15
17
 
16
18
 
17
- class JSONLSession:
19
+ class JSONLSession(Session):
18
20
  """JSONL-based implementation of the agents.Session protocol.
19
21
 
20
22
  Stores conversation history in JSON Lines format, with one JSON object
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vibecore
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Build your own AI-powered automation tools in the terminal with this extensible agent framework
5
5
  Project-URL: Homepage, https://github.com/serialx/vibecore
6
6
  Project-URL: Repository, https://github.com/serialx/vibecore
@@ -1,7 +1,7 @@
1
1
  vibecore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  vibecore/cli.py,sha256=HU2cjvEDzMWlauoArUItBlTpsVAdgVuKFMLshtaqvUE,7119
3
3
  vibecore/context.py,sha256=SZdWOOBKOPBRVC66Y3NohkWdxR5dbAycCHynuqhmEFo,2577
4
- vibecore/flow.py,sha256=ZaKzMsz4YBvgelVzJOIHnTJzMWTmvkfvudwW_hllq6U,3384
4
+ vibecore/flow.py,sha256=1Tcbnox4UwDHFJ3i0hBGu7TZsBfjLkEsGFuJVhJjmcY,12205
5
5
  vibecore/main.py,sha256=JeUhWOoWZDmvFrufMHe01DiUlTNsovcmpH9w8uudTm8,20127
6
6
  vibecore/main.tcss,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  vibecore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -29,7 +29,7 @@ vibecore/models/anthropic_auth.py,sha256=_hRGbPHTs6IrkSc5mDI7mshdOZYCyqu82HX9WdQ
29
29
  vibecore/prompts/common_system_prompt.txt,sha256=L-YlOfTJSQLtVg6d0r9lcD5FrgOLzoSWjGzVfQjcDBg,4916
30
30
  vibecore/session/__init__.py,sha256=FXbtw8DZVBw8e3qCA2cQharMzozblhwA0yU4U0JRSkM,121
31
31
  vibecore/session/file_lock.py,sha256=vCuDdfaOGDeVpTjJP6yBJx7onIT7JkkAeAuWAtuLJb8,3739
32
- vibecore/session/jsonl_session.py,sha256=vWITGBugf5ZDkI00abwsD_lyi6LwHYXzX9JrwavS9hE,8734
32
+ vibecore/session/jsonl_session.py,sha256=hS03fbgPzKEKdUesdg_8OHCoD1nnMFjHe54K2Psh3SY,8771
33
33
  vibecore/session/loader.py,sha256=vmDwzjtedFEeWhaFa6HbygjL32-bSNXM6KccQC9hyJs,6880
34
34
  vibecore/session/path_utils.py,sha256=_meng4PnOR59ekPWp_WICkt8yVkokt8c6oePZvk3m-4,2544
35
35
  vibecore/tools/__init__.py,sha256=nppfKiflvkQRUotBrj9nFU0veWex1DE_YX1fg67SRlw,37
@@ -81,8 +81,8 @@ vibecore/widgets/messages.tcss,sha256=Dhz6X1Fkj2XN9bVGVH_hBelDF7WXNE6hHMkGQRQy1Q
81
81
  vibecore/widgets/tool_message_factory.py,sha256=yrZorT4HKo5b6rWUc0dgQle7q7cvLyq8JllE772RZS0,5730
82
82
  vibecore/widgets/tool_messages.py,sha256=hJOolN3iLTAjqfotfH1elXqsdDo1r_UHjsyRVH0GAeo,29415
83
83
  vibecore/widgets/tool_messages.tcss,sha256=gdChmHClURqn_sD9GkcOGQcQVYvUUl75mLUYp85sKz8,8442
84
- vibecore-0.4.2.dist-info/METADATA,sha256=jakWk2i8UVQ9fIECmTH6tyDr3DLVR5h6A-svdfuDMlk,19550
85
- vibecore-0.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
86
- vibecore-0.4.2.dist-info/entry_points.txt,sha256=i9mOKvpz07ciV_YYisxNCYZ53_Crjkn9mciiQ3aA6QM,51
87
- vibecore-0.4.2.dist-info/licenses/LICENSE,sha256=KXxxifvrcreHrZ4aOYgP-vA8DRHHueW389KKOeEbtjc,1069
88
- vibecore-0.4.2.dist-info/RECORD,,
84
+ vibecore-0.5.0.dist-info/METADATA,sha256=Ow2rN0Iwmim2skl99Og1fYZEz1VTd_c4GS8crTl6P_0,19550
85
+ vibecore-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
86
+ vibecore-0.5.0.dist-info/entry_points.txt,sha256=i9mOKvpz07ciV_YYisxNCYZ53_Crjkn9mciiQ3aA6QM,51
87
+ vibecore-0.5.0.dist-info/licenses/LICENSE,sha256=KXxxifvrcreHrZ4aOYgP-vA8DRHHueW389KKOeEbtjc,1069
88
+ vibecore-0.5.0.dist-info/RECORD,,