pi-python-core 0.10.1__py3-none-any.whl → 0.10.2__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.
pi_python/__init__.py CHANGED
@@ -64,6 +64,7 @@ from .recovery import (
64
64
  )
65
65
  from .tools import (
66
66
  Tool,
67
+ ToolInvocation,
67
68
  ToolContext,
68
69
  ToolExecutor,
69
70
  ToolResult,
@@ -151,6 +152,7 @@ __all__ = [
151
152
  "is_recoverable_length",
152
153
  "is_retryable_error",
153
154
  "retry_delay",
155
+ "ToolInvocation",
154
156
  "ToolContext",
155
157
  "ToolExecutor",
156
158
  "ToolResult",
pi_python/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.10.1"
1
+ __version__ = "0.10.2"
pi_python/agent.py CHANGED
@@ -9,6 +9,7 @@ import asyncio
9
9
  from copy import copy, deepcopy
10
10
  from dataclasses import dataclass
11
11
  from typing import Any, Callable
12
+ from uuid import uuid4
12
13
 
13
14
  from .cancellation import CancelToken
14
15
  from .errors import (
@@ -29,6 +30,7 @@ from .messages import (
29
30
  TextContent,
30
31
  Message,
31
32
  SystemMessage,
33
+ ToolCall,
32
34
  UserMessage,
33
35
  validate_history,
34
36
  validate_json,
@@ -37,7 +39,7 @@ from .provider import Provider, DefaultProvider, FunctionProvider, has_default_s
37
39
  from .queues import MessageQueues
38
40
  from .run import CALLER_CANCELLED, Run, RunResult, merge
39
41
  from .sync import run_sync
40
- from .tools import Tool
42
+ from .tools import Tool, ToolInvocation
41
43
  from .transcript import current_system_message
42
44
 
43
45
 
@@ -78,9 +80,13 @@ class Agent:
78
80
  execution_mode: str = "parallel",
79
81
  steering_mode: str = "one_at_a_time",
80
82
  follow_up_mode: str = "one_at_a_time",
83
+ include_tool_origin: bool = False,
81
84
  ):
82
85
  if execution_mode not in {"parallel", "sequential"}:
83
86
  raise ConfigurationError("Invalid execution mode")
87
+ if type(include_tool_origin) is not bool:
88
+ raise ConfigurationError("include_tool_origin must be a boolean")
89
+ self.include_tool_origin = include_tool_origin
84
90
  self._queues = MessageQueues(steering_mode, follow_up_mode)
85
91
  if provider is not None and stream_fn is not None:
86
92
  raise ConfigurationError("Pass provider or stream_fn, not both")
@@ -204,6 +210,10 @@ class Agent:
204
210
  if self._running:
205
211
  self._updates.append(update)
206
212
  else:
213
+ # Preserve submission order after an application batch deferred updates.
214
+ for pending in self._updates:
215
+ merge(self._defaults, pending)
216
+ self._updates.clear()
207
217
  merge(self._defaults, update)
208
218
 
209
219
  @staticmethod
@@ -332,6 +342,31 @@ class Agent:
332
342
  raise AgentBusyError("Agent already running")
333
343
  return await self._start(self._input(message))
334
344
 
345
+ async def execute_tools(
346
+ self, invocations: list[ToolInvocation], *, source: str | None = None
347
+ ) -> RunResult:
348
+ """Execute a nonempty batch without a model request or consuming queued input.
349
+
350
+ Shares the model tool path, limits and cancellation ownership. Results are
351
+ recorded as application feedback, never as assistant/tool-result pairs.
352
+ ``source`` is caller-supplied diagnostic metadata, not an identity claim.
353
+ """
354
+ self._usable()
355
+ if self._running:
356
+ raise AgentBusyError("Agent already running")
357
+ validate_history(self._messages)
358
+ if not isinstance(invocations, list) or not invocations:
359
+ raise ConfigurationError("execute_tools requires a nonempty list of ToolInvocation")
360
+ if source is not None and not isinstance(source, str):
361
+ raise ConfigurationError("source must be a string or None")
362
+ calls = []
363
+ for invocation in invocations:
364
+ if not isinstance(invocation, ToolInvocation):
365
+ raise ConfigurationError("Expected ToolInvocation")
366
+ invocation.__post_init__()
367
+ calls.append(ToolCall(str(uuid4()), invocation.name, deepcopy(invocation.arguments)))
368
+ return await self._start([], application_calls=calls, source=source)
369
+
335
370
  def prompt_sync(
336
371
  self, message: str | Message | list[Message], images: list[ImageContent] | None = None
337
372
  ) -> RunResult:
@@ -369,18 +404,32 @@ class Agent:
369
404
  return await self._start([])
370
405
 
371
406
  async def _start(
372
- self, pending: list[Message], skip_initial_steering: bool = False
407
+ self,
408
+ pending: list[Message],
409
+ skip_initial_steering: bool = False,
410
+ *,
411
+ application_calls: list[ToolCall] | None = None,
412
+ source: str | None = None,
373
413
  ) -> RunResult:
374
- if isinstance(self.provider, DefaultProvider) and not has_default_stream():
414
+ if (
415
+ application_calls is None
416
+ and isinstance(self.provider, DefaultProvider)
417
+ and not has_default_stream()
418
+ ):
375
419
  raise ConfigurationError(
376
420
  "No model provider: pass Agent(provider=...) or call set_default_stream_fn(...)"
377
421
  )
422
+ for update in self._updates:
423
+ merge(self._defaults, update)
424
+ self._updates.clear()
378
425
  self._running = True
379
426
  self._idle.clear()
380
427
  self._settled.clear()
381
428
  self._last_error = None
382
429
  self._events.failed = False
383
- run = self._run = Run(self, skip_initial_steering)
430
+ run = self._run = Run(
431
+ self, skip_initial_steering, application_calls=application_calls, source=source
432
+ )
384
433
  run.loop = asyncio.get_running_loop()
385
434
  # Driver starts with a checkpoint so cancellation before scheduling still finalizes.
386
435
  run.driver = asyncio.create_task(self._drive(run, pending))
pi_python/events.py CHANGED
@@ -16,7 +16,7 @@ from .messages import validate_json
16
16
  class Event:
17
17
  type: str
18
18
  run_id: str
19
- turn_id: int
19
+ turn_id: int | None
20
20
  sequence: int
21
21
  call_id: str | None = None
22
22
  data: dict[str, Any] = field(default_factory=dict)
@@ -50,8 +50,7 @@ def decode_event(value: str) -> Event:
50
50
  if (
51
51
  type(e.sequence) is not int
52
52
  or e.sequence < 1
53
- or type(e.turn_id) is not int
54
- or e.turn_id < 0
53
+ or (e.turn_id is not None and (type(e.turn_id) is not int or e.turn_id < 0))
55
54
  ):
56
55
  raise MessageValidationError("Invalid event sequence or turn")
57
56
  if e.call_id is not None and not isinstance(e.call_id, str):
@@ -85,7 +84,7 @@ class EventDispatcher:
85
84
  self,
86
85
  kind: str,
87
86
  run_id: str,
88
- turn_id: int,
87
+ turn_id: int | None,
89
88
  data: dict[str, Any],
90
89
  call_id: str | None,
91
90
  await_owned: Callable,
pi_python/features.py CHANGED
@@ -19,6 +19,7 @@ FEATURES = MCP_FEATURES | frozenset(
19
19
  "task-scope-v1",
20
20
  "loop-portal-v1",
21
21
  "nested-agent-ownership-v1",
22
+ "application-tool-batches-v1",
22
23
  "plugin-requires-v1",
23
24
  }
24
25
  )
pi_python/loop.py CHANGED
@@ -19,6 +19,7 @@ from .messages import (
19
19
  message_from_dict,
20
20
  message_to_dict,
21
21
  validate_history,
22
+ convert_application_tool_batch,
22
23
  )
23
24
  from .models import ModelInfo
24
25
  from .provider import ModelRequest
@@ -82,6 +83,12 @@ async def stream_response(run: Run) -> tuple[AssistantMessage, bool]:
82
83
  messages = await run.hook("transform_context", messages, run.token)
83
84
  if run.hooks.convert_to_llm:
84
85
  messages = await run.hook("convert_to_llm", deepcopy(messages))
86
+ messages = [
87
+ convert_application_tool_batch(m)
88
+ if isinstance(m, CustomMessage) and m.custom_type == "application_tool_batch"
89
+ else m
90
+ for m in messages
91
+ ]
85
92
  if any(isinstance(m, CustomMessage) for m in messages):
86
93
  raise UnsupportedCapabilityError("Custom messages require convert_to_llm")
87
94
  validate_history(messages)
@@ -212,6 +219,8 @@ async def execute_batch(run: Run, outcomes: list[ToolOutcome]) -> None:
212
219
  agent_context=snapshot["context"],
213
220
  _on_reconciliation_required=lambda: run.require_reconciliation(),
214
221
  )
222
+ if run.application_batch:
223
+ contexts[id(outcome)]._set_origin(outcome.origin, outcome.source)
215
224
  return contexts[id(outcome)]
216
225
 
217
226
  def abort_before_start(outcome: ToolOutcome) -> bool:
@@ -300,6 +309,32 @@ async def execute_batch(run: Run, outcomes: list[ToolOutcome]) -> None:
300
309
  await run.commit_outcome(outcome)
301
310
 
302
311
 
312
+ def exceeds_tool_budget(run: Run, outcomes: list[ToolOutcome], used: int = 0) -> bool:
313
+ maximum = run.limits.max_tool_calls
314
+ if maximum is None or used + len(outcomes) <= maximum:
315
+ return False
316
+ for outcome in outcomes:
317
+ outcome.result = error_result("limit", "Entire tool batch exceeds remaining budget")
318
+ return True
319
+
320
+
321
+ async def run_application_batch(run: Run) -> tuple[str, str]:
322
+ run.checkpoint()
323
+ await run.emit("agent_start")
324
+ if exceeds_tool_budget(run, run.active_batch):
325
+ return "limit_reached", "max_tool_calls"
326
+ await execute_batch(run, run.active_batch)
327
+ if run.agent._unknown:
328
+ return "failed", "outcome_unknown"
329
+ if run.stuck():
330
+ run.fail("Tool did not stop after cancellation")
331
+ return "cancelled" if run.token.cancelled else "failed", "tool_not_stopped"
332
+ if run.token.cancelled:
333
+ status, reason, _ = run.cancelled_outcome()
334
+ return status, reason
335
+ return "completed", "stop"
336
+
337
+
303
338
  async def run_loop(run: Run, initial: list[Message]) -> tuple[str, str]:
304
339
  context = run.context
305
340
  assert context is not None
@@ -366,13 +401,8 @@ async def run_loop(run: Run, initial: list[Message]) -> tuple[str, str]:
366
401
  else:
367
402
  status, reason = "cancelled", run.token.reason or "aborted"
368
403
  elif outcomes:
369
- max_calls = run.limits.max_tool_calls
370
- if max_calls is not None and tool_count + len(outcomes) > max_calls:
404
+ if exceeds_tool_budget(run, outcomes, tool_count):
371
405
  status, reason = "limit_reached", "max_tool_calls"
372
- for outcome in outcomes:
373
- outcome.result = error_result(
374
- "limit", "Entire tool batch exceeds remaining budget"
375
- )
376
406
  elif message.stop_reason == "length":
377
407
  for outcome in outcomes:
378
408
  await run.emit(
pi_python/messages.py CHANGED
@@ -361,6 +361,63 @@ def message_from_dict(data: dict[str, Any]) -> Message:
361
361
  raise MessageValidationError(str(exc)) from exc
362
362
 
363
363
 
364
+ def convert_application_tool_batch(message: CustomMessage) -> UserMessage:
365
+ """Project built-in application feedback, never local details or raw results.
366
+
367
+ An allowlist also protects the default visibility boundary for imported records
368
+ with extra fields. Applications can override conversion with convert_to_llm.
369
+ """
370
+ data = message.data
371
+ if not isinstance(data, dict) or not isinstance(data.get("calls"), list):
372
+ raise MessageValidationError("Invalid application tool batch")
373
+ calls = []
374
+ for call in data["calls"]:
375
+ if not isinstance(call, dict):
376
+ raise MessageValidationError("Invalid application tool call record")
377
+ keys = (
378
+ "id",
379
+ "name",
380
+ "arguments",
381
+ "prepared_arguments",
382
+ "origin",
383
+ "source",
384
+ "execution_status",
385
+ "is_error",
386
+ "error_code",
387
+ "content",
388
+ )
389
+ try:
390
+ visible = {key: call[key] for key in keys}
391
+ except KeyError as exc:
392
+ raise MessageValidationError("Incomplete application tool call record") from exc
393
+ validate_json(visible)
394
+ if (
395
+ visible["origin"] != "application"
396
+ or not isinstance(visible["id"], str)
397
+ or not visible["id"]
398
+ or not isinstance(visible["name"], str)
399
+ or not visible["name"]
400
+ or not isinstance(visible["arguments"], dict)
401
+ or (
402
+ visible["prepared_arguments"] is not None
403
+ and not isinstance(visible["prepared_arguments"], dict)
404
+ )
405
+ or (visible["source"] is not None and not isinstance(visible["source"], str))
406
+ or type(visible["is_error"]) is not bool
407
+ or visible["execution_status"]
408
+ not in ("not_started", "running", "succeeded", "failed", "cancelled", "unknown")
409
+ or (visible["error_code"] is not None and not isinstance(visible["error_code"], str))
410
+ ):
411
+ raise MessageValidationError("Invalid application tool call record")
412
+ _blocks(visible["content"], images=True)
413
+ calls.append(visible)
414
+ return UserMessage(
415
+ "Application tool execution results (application-selected, not model tool calls):\n"
416
+ + json.dumps({"calls": calls}, ensure_ascii=False, allow_nan=False),
417
+ timestamp=message.timestamp,
418
+ )
419
+
420
+
364
421
  def validate_history(messages: list[Message]) -> None:
365
422
  pending: dict[str, str] = {}
366
423
  for message in messages:
pi_python/run.py CHANGED
@@ -15,7 +15,9 @@ from .errors import ConfigurationError
15
15
  from .hooks import AgentConfigUpdate, Hooks, RunContext, TurnUpdate
16
16
  from .messages import (
17
17
  AssistantMessage,
18
+ CustomMessage,
18
19
  Message,
20
+ ToolCall,
19
21
  ToolResultMessage,
20
22
  message_to_dict,
21
23
  validate_history,
@@ -23,7 +25,7 @@ from .messages import (
23
25
  from .errors import SubscriptionError
24
26
  from .tools import ToolOutcome, error_result, invoke
25
27
  from .transcript import declare_tool_changes
26
- from .loop import abort_error, failure_message, run_loop
28
+ from .loop import abort_error, failure_message, run_loop, run_application_batch
27
29
 
28
30
  if TYPE_CHECKING:
29
31
  from .agent import Agent
@@ -58,11 +60,24 @@ def merge(target: Any, update: AgentConfigUpdate) -> None:
58
60
  class Run:
59
61
  """State that lives for one run. The Agent keeps everything that outlives it."""
60
62
 
61
- def __init__(self, agent: Agent, skip_initial_steering: bool = False):
63
+ def __init__(
64
+ self,
65
+ agent: Agent,
66
+ skip_initial_steering: bool = False,
67
+ *,
68
+ application_calls: list[ToolCall] | None = None,
69
+ source: str | None = None,
70
+ ):
62
71
  self.agent = agent
63
72
  self.token = CancelToken()
64
73
  self.id = str(uuid4())
65
74
  self.turn = 0
75
+ self.application_batch = application_calls is not None
76
+ self.origin = "application" if self.application_batch else "model"
77
+ self.source = source
78
+ self.include_tool_origin = agent.include_tool_origin
79
+ self._limits = agent.limits
80
+ self._execution_mode = agent.execution_mode
66
81
  defaults = agent._defaults
67
82
  self.context = RunContext(
68
83
  deepcopy(agent._messages),
@@ -74,8 +89,16 @@ class Run:
74
89
  self.skip_initial_steering = skip_initial_steering
75
90
  self.partial: Any = None
76
91
  self.started = False # message_start was published for the current response
77
- self.outcomes: list[ToolOutcome] = []
78
- self.active_batch: list[ToolOutcome] = []
92
+ self.outcomes: list[ToolOutcome] = [
93
+ ToolOutcome(
94
+ deepcopy(call),
95
+ original_arguments=deepcopy(call.arguments),
96
+ )
97
+ for call in application_calls or []
98
+ ]
99
+ for outcome in self.outcomes:
100
+ outcome._set_origin("application", source)
101
+ self.active_batch: list[ToolOutcome] = list(self.outcomes)
79
102
  self._committed: set[int] = set()
80
103
  self._ended: set[int] = set()
81
104
  self._owned: set[asyncio.Future] = set()
@@ -95,11 +118,11 @@ class Run:
95
118
 
96
119
  @property
97
120
  def limits(self) -> RunLimits:
98
- return self.agent.limits
121
+ return self._limits if self.application_batch else self.agent.limits
99
122
 
100
123
  @property
101
124
  def execution_mode(self) -> str:
102
- return self.agent.execution_mode
125
+ return self._execution_mode if self.application_batch else self.agent.execution_mode
103
126
 
104
127
  @property
105
128
  def pending_calls(self) -> tuple[str, ...]:
@@ -199,7 +222,16 @@ class Run:
199
222
  await asyncio.gather(cancel_wait, return_exceptions=True)
200
223
 
201
224
  async def emit(self, kind: str, call_id: str | None = None, **data: Any) -> None:
202
- await self.agent._events.emit(kind, self.id, self.turn, data, call_id, self.await_owned)
225
+ if self.include_tool_origin and kind.startswith("tool_execution_"):
226
+ data.update(origin=self.origin, source=self.source)
227
+ await self.agent._events.emit(
228
+ kind,
229
+ self.id,
230
+ None if self.application_batch else self.turn,
231
+ data,
232
+ call_id,
233
+ self.await_owned,
234
+ )
203
235
 
204
236
  async def hook(self, name: str, *args: Any) -> Any:
205
237
  callback = getattr(self.hooks, name)
@@ -307,6 +339,8 @@ class Run:
307
339
  result = outcome.result
308
340
  outcome._settled = True
309
341
  self._committed.add(id(outcome))
342
+ if self.application_batch:
343
+ return # One custom batch record is appended after all accepted calls settle.
310
344
  message = ToolResultMessage(
311
345
  outcome.call.id,
312
346
  outcome.call.name,
@@ -330,6 +364,37 @@ class Run:
330
364
  execution_status=outcome.execution_status,
331
365
  )
332
366
 
367
+ async def append_application_batch(self) -> None:
368
+ calls = []
369
+ for outcome in self.outcomes:
370
+ result = outcome.result
371
+ assert result is not None # commit_outcome fills unresolved calls during cleanup.
372
+ calls.append(
373
+ {
374
+ "id": outcome.call.id,
375
+ "name": outcome.call.name,
376
+ "arguments": deepcopy(outcome.original_arguments),
377
+ "prepared_arguments": deepcopy(outcome.prepared_arguments),
378
+ "origin": outcome.origin,
379
+ "source": outcome.source,
380
+ "execution_status": outcome.execution_status,
381
+ "is_error": result.is_error,
382
+ "error_code": result.error_code,
383
+ "content": [asdict(block) for block in result.content],
384
+ }
385
+ )
386
+ await self.append(
387
+ CustomMessage(
388
+ "application_tool_batch",
389
+ {
390
+ "run_id": self.id,
391
+ "origin": self.origin,
392
+ "source": self.source,
393
+ "calls": calls,
394
+ },
395
+ )
396
+ )
397
+
333
398
  async def drive(self, pending: list[Message]) -> RunResult:
334
399
  agent = self.agent
335
400
  self.driver_started = True
@@ -339,7 +404,11 @@ class Run:
339
404
  # or aborted assistant message, followed by turn_end.
340
405
  failure: AssistantMessage | None = None
341
406
  try:
342
- status, reason = await run_loop(self, pending)
407
+ status, reason = (
408
+ await run_application_batch(self)
409
+ if self.application_batch
410
+ else await run_loop(self, pending)
411
+ )
343
412
  except asyncio.CancelledError:
344
413
  status, reason, failure = self.cancelled_outcome()
345
414
  except Exception as exc:
@@ -366,6 +435,16 @@ class Run:
366
435
  except Exception as exc:
367
436
  status = "failed"
368
437
  errors.append(f"{type(exc).__name__}: {exc}")
438
+ if self.application_batch:
439
+ failure = None # No model turn occurred, including on abort or failure.
440
+ try:
441
+ await self.append_application_batch()
442
+ except asyncio.CancelledError:
443
+ status = "cancelled" if status != "limit_reached" else status
444
+ errors.append("Batch message delivery cancelled")
445
+ except Exception as exc:
446
+ status = "failed"
447
+ errors.append(f"{type(exc).__name__}: {exc}")
369
448
  tail = agent._messages[-1] if len(agent._messages) > self.start else None
370
449
  if failure is not None and not (
371
450
  isinstance(tail, AssistantMessage) and tail.stop_reason in {"error", "aborted"}
@@ -420,9 +499,10 @@ class Run:
420
499
  self.fail(errors[-1])
421
500
  await self.cleanup()
422
501
  # Explicit config updates persist even if no further model request occurred.
423
- for update in agent._updates:
424
- merge(agent._defaults, update)
425
- agent._updates.clear()
502
+ if not self.application_batch:
503
+ for update in agent._updates:
504
+ merge(agent._defaults, update)
505
+ agent._updates.clear()
426
506
  usage: dict[str, Any] = {}
427
507
  for m in agent._messages[self.start :]:
428
508
  if isinstance(m, AssistantMessage):
pi_python/tools.py CHANGED
@@ -348,8 +348,45 @@ def apply_result_update(result: ToolResult, update: ToolResultUpdate) -> None:
348
348
  result.terminate = update.terminate
349
349
 
350
350
 
351
+ @dataclass(frozen=True)
352
+ class ToolInvocation:
353
+ """An application-requested call; the Agent assigns its ID."""
354
+
355
+ name: str
356
+ arguments: dict[str, Any]
357
+
358
+ def __post_init__(self) -> None:
359
+ if not isinstance(self.name, str) or not self.name:
360
+ raise ConfigurationError("ToolInvocation requires a nonempty tool name")
361
+ validate_json(self.arguments)
362
+ if not isinstance(self.arguments, dict):
363
+ raise ConfigurationError("ToolInvocation arguments must be an object")
364
+
365
+
366
+ class _ToolOrigin:
367
+ """Read-only provenance without changing legacy dataclass fields/asdict output.
368
+
369
+ Model calls need no per-instance storage. Application calls attach provenance
370
+ explicitly; deepcopy preserves it even though dataclasses.asdict excludes it.
371
+ """
372
+
373
+ _origin = "model"
374
+ _source: str | None = None
375
+
376
+ @property
377
+ def origin(self) -> str:
378
+ return self._origin
379
+
380
+ @property
381
+ def source(self) -> str | None:
382
+ return self._source
383
+
384
+ def _set_origin(self, origin: str, source: str | None) -> None:
385
+ self._origin, self._source = origin, source
386
+
387
+
351
388
  @dataclass
352
- class ToolContext:
389
+ class ToolContext(_ToolOrigin):
353
390
  run_id: str
354
391
  call_id: str
355
392
  cancel: CancelToken
@@ -449,7 +486,7 @@ class Tool:
449
486
 
450
487
 
451
488
  @dataclass
452
- class ToolOutcome:
489
+ class ToolOutcome(_ToolOrigin):
453
490
  call: ToolCall
454
491
  execution_status: str = "not_started"
455
492
  original_arguments: dict[str, Any] = field(default_factory=dict)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pi-python-core
3
- Version: 0.10.1
3
+ Version: 0.10.2
4
4
  Summary: An embeddable asyncio agent core with opt-in Claude and OpenAI providers
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,34 +1,34 @@
1
- pi_python/__init__.py,sha256=S_XoKUqwBbptuM9TSffhcy0NSedNhq0oCJ_huFcDvR0,3916
1
+ pi_python/__init__.py,sha256=-32DW8gNgcvPYNQJZUago0hrkgx_v4VZnOsr4BmG46M,3958
2
2
  pi_python/_mcp_host.py,sha256=CXio-tiEeUCUfdl65tatwSrzIOYdgecdS00sO6tNy5k,8630
3
3
  pi_python/_mcp_interaction.py,sha256=jPL-C8wLjBDV9tOdvGVFfXOl8-1FQIDJLq-MojlzXaM,43851
4
4
  pi_python/_mcp_process.py,sha256=4szi5gMEP_0GUGOcV-oLPUuVQAUSb0ToAaiM979zHME,6324
5
5
  pi_python/_mcp_sampling.py,sha256=BcWCrPgjsRC-GNx65fIB-JPq2WzWnoBK2w9iTnyPUww,12148
6
- pi_python/_version.py,sha256=v7Gyp89umFzDtY45tTjCdXqZnQ2RN01AibdYNxEvxYo,23
7
- pi_python/agent.py,sha256=I4P4waCdkcURDd022H4XgBbHNhpa7LrI5Iwb5ncxhGU,16021
6
+ pi_python/_version.py,sha256=A_AARqtxTOj_AQTpjpgOxNx-UOBio5wYFfZ2mrdMKfs,23
7
+ pi_python/agent.py,sha256=86_mWt2oysz0Llv0LkQRnGKjYsloVElkQFnrkE5ZXv0,18206
8
8
  pi_python/cancellation.py,sha256=DoQVdX7DcQdVLkl4OvhcpMhKZJEW0Kscl2-xxF6i6Y0,608
9
9
  pi_python/errors.py,sha256=65lT0nYfzqxRfUlFG-Y9vgYxfDHEuFiBfgqmFnX9_O8,675
10
10
  pi_python/estimate.py,sha256=dQQJ9UEGp57F3wO_CcU2oubgPe841ttgpUseK3Fu_0w,5048
11
- pi_python/events.py,sha256=t05VE-km2ZcsdIXCviVzNFQCob4xBghDfNeBRWpLrCA,4594
12
- pi_python/features.py,sha256=3jYcbDnQloPQaQn_qro952tehZYqXqbgsejsdGaiOqY,1642
11
+ pi_python/events.py,sha256=HFYaPB9-yjOvmuM2Ly7SgfWDMJJfX9e1Q7ethqOHSB8,4626
12
+ pi_python/features.py,sha256=V9Yhh1APuN8KaLMrAx8s00xeDLAF0ydh5jI5FCT9ZQg,1681
13
13
  pi_python/function_tools.py,sha256=DO8fZD-n5i62w22zLIjblCOTwnG8N-HIoSUo4yOpRM4,18862
14
14
  pi_python/hooks.py,sha256=jm6ep78NjdHxIPZvcOXTZIPxkfBMkffVTNzLvnHS9mM,1321
15
15
  pi_python/limits.py,sha256=K1aP1cFH4fVhtWIXqpWMmpkB9AI0wK-bz3Xd2hxnOs4,1157
16
- pi_python/loop.py,sha256=b0X_hNYXPQZcAjsyw3RIGdYLoZQHhKxm3nBtiznejak,17839
16
+ pi_python/loop.py,sha256=nr3P6fXnrG21DFXPCGMGTKssE30jJmMoWi4bKIWxOu0,18904
17
17
  pi_python/lowlevel.py,sha256=56wtwqD0Ur1AGAXGa7zclb1IpmRPFpSEss_nspkAdrI,5514
18
18
  pi_python/mcp.py,sha256=f7VOiOeBwJ48SrowJWmqtH3d-iavrv2GZo2KH45qeTA,16713
19
- pi_python/messages.py,sha256=w5e-lc9DLW_4vYZ73Wj32RXfNYzDHdp4HCYsF5qkfHY,15665
19
+ pi_python/messages.py,sha256=jUoQxR-UZbIQYWu257dGiCx8K5z_RV89fJpHgv4t-Ww,18068
20
20
  pi_python/models.py,sha256=IejtaWvbURM9vv_2kkoDzfto2TR0OSLjuQeOh3IJZ1k,6094
21
21
  pi_python/provider.py,sha256=k_VfJu5WOQla_LkglTDAq5s-77ejCj4YRTyhJ8onZBo,4259
22
22
  pi_python/proxy.py,sha256=kpEUoE1oDkJW7VVaV72slN3uJUFgYR4Q6jtYOEe8LmQ,12159
23
23
  pi_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  pi_python/queues.py,sha256=nQ5xRSA3MMOHMs_ZJnG9sT3-d_yKn72oSnxXqiKPrcI,3032
25
25
  pi_python/recovery.py,sha256=z5ht-6FoKtC5BPEaSgXiRE2KVf94mZDiKMSxV0mkZ-4,8138
26
- pi_python/run.py,sha256=J53DLttQNcIUwvGryvEjVuoyCBxdmAsqetQvxmliej8,18640
26
+ pi_python/run.py,sha256=S-mJ5xHVzVXdt8aBicRr4jU1-VZW1-QDi4Cru2hPgmE,21831
27
27
  pi_python/stream.py,sha256=R3v-qjCHFQJhHQk83qATz7lC3yr8paJguaHwk8RoHow,10729
28
28
  pi_python/sync.py,sha256=uTTX07hxe6TOCgwtfBxSJh2oJFDa2UklNmlCJ9g5-ss,5261
29
29
  pi_python/tasks.py,sha256=UNPhb_HSTAaALcstV4X9T4q8JZTyMf90LjRR5bFw26M,4738
30
30
  pi_python/testing.py,sha256=XfTSOkEv0CenqrBILI_T205jOhyitW53YQZ9yiMXir4,1046
31
- pi_python/tools.py,sha256=zBrYVoPOgrVHqvrL5sIhMXCf501QjyuDNdmR-tV6Ads,23274
31
+ pi_python/tools.py,sha256=507d9JXMdn1fsoS1Y1TZIauEf4d570vDeRMfavb9itY,24417
32
32
  pi_python/transcript.py,sha256=g6Tg8eiyMCAK43iLpcBv220MHBq8kO7Ty-6OYLCzw2A,6685
33
33
  pi_python/data/models.json,sha256=oa_LGv7vaRrS-l7_Z7iu8f7WTyoJJLPhUQyptUz7OmE,77549
34
34
  pi_python/plugins/__init__.py,sha256=FeYHYxgaOjKuq3QBBv1ezOyfORi5Fl2SdnImN_O0IrE,56156
@@ -43,8 +43,8 @@ pi_python/providers/completions.py,sha256=vcqoLfJ-9quMREEEAJcHYs3Oq-A0Sz8Q1NV1QL
43
43
  pi_python/providers/oauth.py,sha256=ZY8j7P-I-R6hZ8sInkGao957KWNGsqfvMF0F-YzlSgU,22786
44
44
  pi_python/providers/openai.py,sha256=p0WHDnojAYb7mUX8EbComwEU_XskcYoMhMIR07QlSfc,30196
45
45
  pi_python/providers/transport.py,sha256=B1mGKlVEqYGVK1MlcA9opwRhnuUYGuaYuTslNC3O8xs,26772
46
- pi_python_core-0.10.1.dist-info/METADATA,sha256=AncCL6Sz-4xE1Kx9_iHB2TmHcOtq8jJjBFHxAGExpxs,8939
47
- pi_python_core-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
- pi_python_core-0.10.1.dist-info/licenses/LICENSE,sha256=BFf1vOw7OyEWBd-10aSQQv1jjzaGpBD-CZwkolrxPEg,1069
49
- pi_python_core-0.10.1.dist-info/licenses/NOTICE,sha256=fJna16fm1ECHN7zVZhtyNmjLw8KGxGp8mL9blISF1K0,633
50
- pi_python_core-0.10.1.dist-info/RECORD,,
46
+ pi_python_core-0.10.2.dist-info/METADATA,sha256=SGNj48Q5U-Q-Z0AVANvz5CQoJVcLqYkMjEkXZN3AFA4,8939
47
+ pi_python_core-0.10.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
+ pi_python_core-0.10.2.dist-info/licenses/LICENSE,sha256=BFf1vOw7OyEWBd-10aSQQv1jjzaGpBD-CZwkolrxPEg,1069
49
+ pi_python_core-0.10.2.dist-info/licenses/NOTICE,sha256=fJna16fm1ECHN7zVZhtyNmjLw8KGxGp8mL9blISF1K0,633
50
+ pi_python_core-0.10.2.dist-info/RECORD,,