vibecore 0.5.0__py3-none-any.whl → 0.6.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.

Potentially problematic release.


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

vibecore/flow.py CHANGED
@@ -4,7 +4,7 @@ import datetime
4
4
  import sys
5
5
  import threading
6
6
  from collections.abc import Callable, Coroutine
7
- from typing import Any, Generic, Protocol, TypeAlias, overload
7
+ from typing import Any, Concatenate, Generic, TypeAlias, cast, overload
8
8
 
9
9
  from agents import (
10
10
  Agent,
@@ -12,7 +12,6 @@ from agents import (
12
12
  RunHooks,
13
13
  Runner,
14
14
  Session,
15
- TContext,
16
15
  TResponseInputItem,
17
16
  )
18
17
  from agents.result import RunResultBase
@@ -20,7 +19,7 @@ from agents.run import DEFAULT_MAX_TURNS
20
19
  from textual.pilot import Pilot
21
20
  from typing_extensions import TypeVar
22
21
 
23
- from vibecore.context import VibecoreContext
22
+ from vibecore.context import AppAwareContext
24
23
  from vibecore.main import AppIsExiting, VibecoreApp
25
24
  from vibecore.session import JSONLSession
26
25
  from vibecore.settings import settings
@@ -28,35 +27,39 @@ from vibecore.widgets.core import MyTextArea
28
27
  from vibecore.widgets.messages import SystemMessage
29
28
 
30
29
 
31
- class UserInputFunc(Protocol):
32
- """Protocol for user input function with optional prompt parameter."""
30
+ class NoUserInputLeft(Exception):
31
+ """Raised when no more user inputs are available in static runner."""
33
32
 
34
- async def __call__(self, prompt: str = "") -> str:
35
- """Get user input with optional prompt message.
36
-
37
- Args:
38
- prompt: Optional prompt to display before getting input.
39
-
40
- Returns:
41
- The user's input string.
42
- """
43
- ...
33
+ pass
44
34
 
45
35
 
36
+ TContext = TypeVar("TContext", default=None)
46
37
  TWorkflowReturn = TypeVar("TWorkflowReturn", default=RunResultBase)
47
- DecoratedCallable: TypeAlias = Callable[..., Coroutine[Any, Any, TWorkflowReturn]]
48
38
 
49
39
 
50
- class VibecoreRunnerBase(Generic[TWorkflowReturn]):
51
- def __init__(self, vibecore: "Vibecore[TWorkflowReturn]") -> None:
40
+ class VibecoreRunner(Generic[TContext, TWorkflowReturn]):
41
+ def __init__(
42
+ self,
43
+ vibecore: "Vibecore[TContext, TWorkflowReturn]",
44
+ context: TContext | None = None,
45
+ session: Session | None = None,
46
+ ) -> None:
52
47
  self.vibecore = vibecore
48
+ self.context = context
49
+
50
+ if session is None:
51
+ session_id = f"chat-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}"
52
+ self._session = JSONLSession(
53
+ session_id=session_id,
54
+ project_path=None, # Will use current working directory
55
+ base_dir=settings.session.base_dir,
56
+ )
57
+ else:
58
+ self._session = session
53
59
 
54
60
  @property
55
61
  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.")
62
+ return self._session
60
63
 
61
64
  async def print(self, message: str) -> None:
62
65
  print(message, file=sys.stderr)
@@ -86,46 +89,41 @@ class VibecoreRunnerBase(Generic[TWorkflowReturn]):
86
89
  return result
87
90
 
88
91
 
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)
92
+ class VibecoreCliRunner(VibecoreRunner[TContext, TWorkflowReturn]):
93
+ def __init__(
94
+ self,
95
+ vibecore: "Vibecore[TContext, TWorkflowReturn]",
96
+ context: TContext | None = None,
97
+ session: Session | None = None,
98
+ ) -> None:
99
+ super().__init__(vibecore, context=context, session=session)
108
100
 
109
- async def user_input(self, prompt: str = "") -> str:
101
+ async def _user_input(self, prompt: str = "") -> str:
110
102
  return input(prompt)
111
103
 
112
104
  async def run(self) -> TWorkflowReturn:
113
105
  assert self.vibecore.workflow_logic is not None, (
114
106
  "Workflow logic not defined. Please use the @vibecore.workflow() decorator."
115
107
  )
116
- return await self.vibecore.workflow_logic()
108
+ result = None
109
+ while user_message := await self._user_input():
110
+ result = await self.vibecore.workflow_logic(self, user_message)
111
+
112
+ assert result, "No result available after inputs exhausted."
113
+ return result
117
114
 
118
115
 
119
- class VibecoreStaticRunner(VibecoreSimpleRunner[TWorkflowReturn]):
120
- def __init__(self, vibecore: "Vibecore[TWorkflowReturn]") -> None:
121
- super().__init__(vibecore)
116
+ class VibecoreStaticRunner(VibecoreRunner[TContext, TWorkflowReturn]):
117
+ def __init__(
118
+ self,
119
+ vibecore: "Vibecore[TContext, TWorkflowReturn]",
120
+ context: TContext | None = None,
121
+ session: Session | None = None,
122
+ ) -> None:
123
+ super().__init__(vibecore, context=context, session=session)
122
124
  self.inputs: list[str] = []
123
125
  self.prints: list[str] = []
124
126
 
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
127
  async def print(self, message: str) -> None:
130
128
  # Capture printed messages instead of displaying them
131
129
  self.prints.append(message)
@@ -136,21 +134,29 @@ class VibecoreStaticRunner(VibecoreSimpleRunner[TWorkflowReturn]):
136
134
  assert self.vibecore.workflow_logic is not None, (
137
135
  "Workflow logic not defined. Please use the @vibecore.workflow() decorator."
138
136
  )
139
- self.inputs.extend(inputs)
140
- return await self.vibecore.workflow_logic()
137
+ result = None
138
+ for user_message in inputs:
139
+ result = await self.vibecore.workflow_logic(self, user_message)
141
140
 
141
+ assert result, "No result available after inputs exhausted."
142
+ return result
142
143
 
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
144
 
149
- @property
150
- def session(self) -> Session:
151
- return self.app.session
145
+ class VibecoreTextualRunner(VibecoreRunner[AppAwareContext, TWorkflowReturn]):
146
+ def __init__(
147
+ self,
148
+ vibecore: "Vibecore[AppAwareContext, TWorkflowReturn]",
149
+ context: AppAwareContext | None = None,
150
+ session: Session | None = None,
151
+ ) -> None:
152
+ super().__init__(vibecore, context=context, session=session)
153
+ self.app = VibecoreApp(
154
+ self,
155
+ show_welcome=False,
156
+ )
157
+ self.app_ready_event = asyncio.Event()
152
158
 
153
- async def user_input(self, prompt: str = "") -> str:
159
+ async def _user_input(self, prompt: str = "") -> str:
154
160
  if prompt:
155
161
  await self.print(prompt)
156
162
  self.app.query_one(MyTextArea).disabled = False
@@ -165,12 +171,12 @@ class VibecoreTextualRunner(VibecoreRunnerBase[TWorkflowReturn]):
165
171
 
166
172
  async def run_agent(
167
173
  self,
168
- starting_agent: Agent[TContext],
174
+ starting_agent: Agent[AppAwareContext],
169
175
  input: str | list[TResponseInputItem],
170
176
  *,
171
- context: TContext | None = None,
177
+ context: AppAwareContext | None = None,
172
178
  max_turns: int = DEFAULT_MAX_TURNS,
173
- hooks: RunHooks[TContext] | None = None,
179
+ hooks: RunHooks[AppAwareContext] | None = None,
174
180
  run_config: RunConfig | None = None,
175
181
  previous_response_id: str | None = None,
176
182
  session: Session | None = None,
@@ -200,7 +206,6 @@ class VibecoreTextualRunner(VibecoreRunnerBase[TWorkflowReturn]):
200
206
  Args:
201
207
  app: App to run.
202
208
  """
203
-
204
209
  with self.app._context():
205
210
  try:
206
211
  self.app._loop = asyncio.get_running_loop()
@@ -216,15 +221,19 @@ class VibecoreTextualRunner(VibecoreRunnerBase[TWorkflowReturn]):
216
221
  assert self.vibecore.workflow_logic is not None, (
217
222
  "Workflow logic not defined. Please use the @vibecore.workflow() decorator."
218
223
  )
219
- try:
220
- return await self.vibecore.workflow_logic()
221
- except AppIsExiting:
222
- raise
224
+ while True:
225
+ user_message = await self._user_input()
226
+ result = await self.vibecore.workflow_logic(self, user_message)
227
+ assert result, "No result available after inputs exhausted."
228
+ return result
223
229
 
224
- async def run(self, shutdown: bool = False) -> TWorkflowReturn:
225
- self.app = VibecoreApp(self.vibecore.context, self.vibecore.starting_agent, show_welcome=False)
230
+ async def run(self, inputs: list[str] | None = None, shutdown: bool = False) -> TWorkflowReturn:
231
+ if inputs:
232
+ self.app.message_queue.extend(inputs)
226
233
  app_task = asyncio.create_task(self._run_app(), name=f"run_app({self.app})")
227
234
  await self.app_ready_event.wait()
235
+
236
+ await self.app.load_session_history(self.session)
228
237
  pilot = Pilot(self.app)
229
238
  logic_task: asyncio.Task[TWorkflowReturn] | None = None
230
239
 
@@ -249,34 +258,29 @@ class VibecoreTextualRunner(VibecoreRunnerBase[TWorkflowReturn]):
249
258
  self.app.exit()
250
259
  await app_task
251
260
  else:
261
+ await self.print("Workflow complete. Press Ctrl-Q to exit.")
252
262
  # Enable text input so users can interact freely
253
263
  self.app.query_one(MyTextArea).disabled = False
254
264
  # Wait until app is exited
255
265
  await app_task
256
266
  return result
257
-
258
- raise RuntimeError("Unexpected state: both tasks completed")
267
+ raise AssertionError(f"Unexpected state: done={done}, pending={pending}")
259
268
 
260
269
 
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
270
+ WorkflowLogic: TypeAlias = Callable[
271
+ Concatenate[VibecoreRunner[TContext, TWorkflowReturn], str, ...],
272
+ Coroutine[Any, Any, TWorkflowReturn],
273
+ ]
272
274
 
273
- async def user_input(self, prompt: str = "") -> str:
274
- return await self.runner.user_input(prompt)
275
275
 
276
- async def print(self, message: str) -> None:
277
- return await self.runner.print(message)
276
+ class Vibecore(Generic[TContext, TWorkflowReturn]):
277
+ def __init__(self, disable_user_input: bool = True) -> None:
278
+ self.workflow_logic: WorkflowLogic[TContext, TWorkflowReturn] | None = None
279
+ self.disable_user_input = disable_user_input
278
280
 
279
- def workflow(self) -> Callable[[DecoratedCallable[TWorkflowReturn]], DecoratedCallable[TWorkflowReturn]]:
281
+ def workflow(
282
+ self,
283
+ ) -> Callable[[WorkflowLogic[TContext, TWorkflowReturn]], WorkflowLogic[TContext, TWorkflowReturn]]:
280
284
  """Decorator to define the workflow logic for the app.
281
285
 
282
286
  Returns:
@@ -284,62 +288,80 @@ class Vibecore(Generic[TWorkflowReturn]):
284
288
  """
285
289
 
286
290
  def decorator(
287
- func: DecoratedCallable[TWorkflowReturn],
288
- ) -> DecoratedCallable[TWorkflowReturn]:
291
+ func: WorkflowLogic[TContext, TWorkflowReturn],
292
+ ) -> WorkflowLogic[TContext, TWorkflowReturn]:
289
293
  self.workflow_logic = func
290
294
  return func
291
295
 
292
296
  return decorator
293
297
 
294
- async def run_agent(
298
+ @overload
299
+ async def run_textual(
295
300
  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,
301
+ inputs: str | None = None,
302
+ context: AppAwareContext | None = None,
304
303
  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
- )
304
+ shutdown: bool = False,
305
+ ) -> TWorkflowReturn: ...
306
+ @overload
307
+ async def run_textual(
308
+ self,
309
+ inputs: list[str] | None = None,
310
+ context: AppAwareContext | None = None,
311
+ session: Session | None = None,
312
+ shutdown: bool = False,
313
+ ) -> TWorkflowReturn: ...
314
+
315
+ async def run_textual(
316
+ self,
317
+ inputs: str | list[str] | None = None,
318
+ context: AppAwareContext | None = None,
319
+ session: Session | None = None,
320
+ shutdown: bool = False,
321
+ ) -> TWorkflowReturn:
322
+ if isinstance(inputs, str):
323
+ inputs = [inputs]
316
324
 
317
- async def run_textual(self, shutdown: bool = False) -> TWorkflowReturn:
318
325
  if self.workflow_logic is None:
319
326
  raise ValueError("Workflow logic not defined. Please use the @vibecore.workflow() decorator.")
320
327
 
321
- self.runner = VibecoreTextualRunner(self)
322
- return await self.runner.run(shutdown=shutdown)
328
+ assert isinstance(context, AppAwareContext) or context is None, (
329
+ "Textual runner requires AppAwareContext or None."
330
+ )
331
+ # Type checker needs help: after the assertion, we know context is AppAwareContext | None
332
+ # and this Vibecore instance can be treated as Vibecore[AppAwareContext, TWorkflowReturn]
333
+ runner = VibecoreTextualRunner(
334
+ cast("Vibecore[AppAwareContext, TWorkflowReturn]", self),
335
+ context=context,
336
+ session=session,
337
+ )
338
+ return await runner.run(inputs=inputs, shutdown=shutdown)
323
339
 
324
- async def run_cli(self) -> TWorkflowReturn:
340
+ async def run_cli(self, context: TContext | None = None, session: Session | None = None) -> TWorkflowReturn:
325
341
  if self.workflow_logic is None:
326
342
  raise ValueError("Workflow logic not defined. Please use the @vibecore.workflow() decorator.")
327
343
 
328
- self.runner = VibecoreCliRunner(self)
329
- return await self.runner.run()
344
+ runner = VibecoreCliRunner(self, context=context, session=session)
345
+ return await runner.run()
330
346
 
331
347
  @overload
332
- async def run(self, inputs: str) -> TWorkflowReturn: ...
348
+ async def run(
349
+ self, inputs: str, context: TContext | None = None, session: Session | None = None
350
+ ) -> TWorkflowReturn: ...
333
351
 
334
352
  @overload
335
- async def run(self, inputs: list[str]) -> TWorkflowReturn: ...
353
+ async def run(
354
+ self, inputs: list[str], context: TContext | None = None, session: Session | None = None
355
+ ) -> TWorkflowReturn: ...
336
356
 
337
- async def run(self, inputs: str | list[str]) -> TWorkflowReturn:
357
+ async def run(
358
+ self, inputs: str | list[str], context: TContext | None = None, session: Session | None = None
359
+ ) -> TWorkflowReturn:
338
360
  if isinstance(inputs, str):
339
361
  inputs = [inputs]
340
362
 
341
363
  if self.workflow_logic is None:
342
364
  raise ValueError("Workflow logic not defined. Please use the @vibecore.workflow() decorator.")
343
365
 
344
- self.runner = VibecoreStaticRunner(self)
345
- return await self.runner.run(inputs=inputs)
366
+ runner = VibecoreStaticRunner(self, context=context, session=session)
367
+ return await runner.run(inputs=inputs)
@@ -4,8 +4,6 @@ import json
4
4
  from typing import Protocol
5
5
 
6
6
  from agents import (
7
- Agent,
8
- AgentUpdatedStreamEvent,
9
7
  MessageOutputItem,
10
8
  RawResponsesStreamEvent,
11
9
  RunItemStreamEvent,
@@ -43,10 +41,6 @@ class MessageHandler(Protocol):
43
41
  """Add a message to the widget's message list."""
44
42
  ...
45
43
 
46
- async def handle_agent_update(self, new_agent: Agent) -> None:
47
- """Handle agent updates."""
48
- ...
49
-
50
44
  async def handle_agent_error(self, error: Exception) -> None:
51
45
  """Handle errors during streaming."""
52
46
  ...
@@ -205,10 +199,6 @@ class AgentStreamHandler:
205
199
  case MessageOutputItem():
206
200
  await self.handle_message_complete()
207
201
 
208
- case AgentUpdatedStreamEvent(new_agent=new_agent):
209
- # log(f"AgentUpdatedStreamEvent new_agent: {new_agent.name}")
210
- await self.message_handler.handle_agent_update(new_agent)
211
-
212
202
  async def handle_task_tool_event(self, tool_name: str, tool_call_id: str, event: StreamEvent) -> None:
213
203
  """Handle streaming events from task tool sub-agents.
214
204