pi-agent-python-sdk 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.
pi_agent/types.py ADDED
@@ -0,0 +1,960 @@
1
+ """Pi 0.85.1 wire annotations and small Python conveniences.
2
+
3
+ Wire fields retain Pi's spelling and remain ordinary dictionaries. These
4
+ annotations describe known shapes, not a recursive runtime validator: extensions
5
+ and newer runtimes may add fields and discriminators. Provider compatibility
6
+ metadata is deliberately opaque JSON. See docs/discovery.md for pinned sources.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import math
12
+ from dataclasses import dataclass, field, fields
13
+ from typing import Any, Literal, NotRequired, TypeAlias, TypedDict
14
+
15
+ from .errors import PiProtocolError
16
+
17
+ JSONValue: TypeAlias = None | bool | int | float | str | list["JSONValue"] | dict[str, "JSONValue"]
18
+ JSONObject: TypeAlias = dict[str, JSONValue]
19
+ RawRecord: TypeAlias = dict[str, Any]
20
+ ThinkingLevel: TypeAlias = Literal["off", "minimal", "low", "medium", "high", "xhigh", "max"]
21
+ QueueMode: TypeAlias = Literal["all", "one-at-a-time"]
22
+ StreamingBehavior: TypeAlias = Literal["steer", "followUp"]
23
+ CompactionReason: TypeAlias = Literal["manual", "threshold", "overflow"]
24
+ StopReason: TypeAlias = Literal[
25
+ "pending", "stop", "length", "toolUse", "error", "aborted", "deferred"
26
+ ]
27
+
28
+
29
+ class TextContent(TypedDict):
30
+ type: Literal["text"]
31
+ text: str
32
+ textSignature: NotRequired[str]
33
+
34
+
35
+ class ThinkingContent(TypedDict):
36
+ type: Literal["thinking"]
37
+ thinking: str
38
+ thinkingSignature: NotRequired[str]
39
+ redacted: NotRequired[bool]
40
+
41
+
42
+ class ImageContent(TypedDict):
43
+ type: Literal["image"]
44
+ data: str
45
+ mimeType: str
46
+
47
+
48
+ class ToolCall(TypedDict):
49
+ type: Literal["toolCall"]
50
+ id: str
51
+ name: str
52
+ arguments: JSONObject
53
+ thoughtSignature: NotRequired[str]
54
+ namespace: NotRequired[str]
55
+
56
+
57
+ class UsageCost(TypedDict):
58
+ input: float
59
+ output: float
60
+ cacheRead: float
61
+ cacheWrite: float
62
+ total: float
63
+
64
+
65
+ class Usage(TypedDict):
66
+ input: float
67
+ output: float
68
+ cacheRead: float
69
+ cacheWrite: float
70
+ cacheWrite1h: NotRequired[float]
71
+ reasoning: NotRequired[float]
72
+ totalTokens: float
73
+ cost: UsageCost
74
+
75
+
76
+ class DeferredHandle(TypedDict):
77
+ provider: str
78
+ modelId: str
79
+ api: str
80
+ id: str
81
+ expiresAt: NotRequired[float]
82
+ pollAfterMs: NotRequired[float]
83
+ data: NotRequired[JSONValue]
84
+
85
+
86
+ class DiagnosticError(TypedDict):
87
+ message: str
88
+ name: NotRequired[str]
89
+ stack: NotRequired[str]
90
+ code: NotRequired[str | float]
91
+
92
+
93
+ class AssistantMessageDiagnostic(TypedDict):
94
+ type: str
95
+ timestamp: float
96
+ error: NotRequired[DiagnosticError]
97
+ details: NotRequired[JSONObject]
98
+
99
+
100
+ class UserMessage(TypedDict):
101
+ role: Literal["user"]
102
+ content: str | list[TextContent | ImageContent]
103
+ timestamp: float
104
+
105
+
106
+ class AssistantMessage(TypedDict):
107
+ role: Literal["assistant"]
108
+ content: list[TextContent | ThinkingContent | ToolCall]
109
+ api: str
110
+ provider: str
111
+ model: str
112
+ responseModel: NotRequired[str]
113
+ responseId: NotRequired[str]
114
+ providerThinkingLevel: NotRequired[str]
115
+ diagnostics: NotRequired[list[AssistantMessageDiagnostic]]
116
+ usage: Usage
117
+ stopReason: StopReason
118
+ deferred: NotRequired[DeferredHandle]
119
+ errorMessage: NotRequired[str]
120
+ rawStopReason: NotRequired[str]
121
+ endTurn: NotRequired[bool]
122
+ timestamp: float
123
+
124
+
125
+ class ToolResultMessage(TypedDict):
126
+ role: Literal["toolResult"]
127
+ toolCallId: str
128
+ toolName: str
129
+ content: list[TextContent | ImageContent]
130
+ details: NotRequired[JSONValue]
131
+ usage: NotRequired[Usage]
132
+ addedToolNames: NotRequired[list[str]]
133
+ isError: bool
134
+ timestamp: float
135
+
136
+
137
+ class BashExecutionMessage(TypedDict):
138
+ role: Literal["bashExecution"]
139
+ command: str
140
+ output: str
141
+ exitCode: NotRequired[float]
142
+ cancelled: bool
143
+ truncated: bool
144
+ fullOutputPath: NotRequired[str]
145
+ timestamp: float
146
+ excludeFromContext: NotRequired[bool]
147
+
148
+
149
+ class CustomMessage(TypedDict):
150
+ role: Literal["custom"]
151
+ customType: str
152
+ content: str | list[TextContent | ImageContent]
153
+ display: bool
154
+ details: NotRequired[JSONValue]
155
+ timestamp: float
156
+
157
+
158
+ class BranchSummaryMessage(TypedDict):
159
+ role: Literal["branchSummary"]
160
+ summary: str
161
+ fromId: str | None
162
+ timestamp: float
163
+
164
+
165
+ class CompactionSummaryMessage(TypedDict):
166
+ role: Literal["compactionSummary"]
167
+ summary: str
168
+ tokensBefore: float
169
+ timestamp: float
170
+
171
+
172
+ AgentMessage: TypeAlias = (
173
+ UserMessage
174
+ | AssistantMessage
175
+ | ToolResultMessage
176
+ | BashExecutionMessage
177
+ | CustomMessage
178
+ | BranchSummaryMessage
179
+ | CompactionSummaryMessage
180
+ )
181
+
182
+
183
+ class ModelCostRates(TypedDict):
184
+ input: float
185
+ output: float
186
+ cacheRead: float
187
+ cacheWrite: float
188
+
189
+
190
+ class ModelCostTier(ModelCostRates):
191
+ inputTokensAbove: float
192
+
193
+
194
+ class ModelCost(ModelCostRates):
195
+ tiers: NotRequired[list[ModelCostTier]]
196
+
197
+
198
+ class Model(TypedDict):
199
+ id: str
200
+ name: str
201
+ api: str
202
+ provider: str
203
+ baseUrl: str
204
+ reasoning: bool
205
+ thinkingLevelMap: NotRequired[dict[ThinkingLevel, str | None]]
206
+ input: list[Literal["text", "image"]]
207
+ cost: ModelCost
208
+ contextWindow: float
209
+ maxTokens: float
210
+ samplingParams: NotRequired[JSONObject]
211
+ headers: NotRequired[dict[str, str]]
212
+ compat: NotRequired[JSONObject]
213
+
214
+
215
+ class SessionState(TypedDict):
216
+ model: NotRequired[Model]
217
+ thinkingLevel: ThinkingLevel
218
+ isStreaming: bool
219
+ isCompacting: bool
220
+ steeringMode: QueueMode
221
+ followUpMode: QueueMode
222
+ sessionFile: NotRequired[str]
223
+ sessionId: str
224
+ sessionName: NotRequired[str]
225
+ autoCompactionEnabled: bool
226
+ messageCount: float
227
+ pendingMessageCount: float
228
+
229
+
230
+ class CompactionResult(TypedDict):
231
+ summary: str
232
+ firstKeptEntryId: str
233
+ tokensBefore: float
234
+ estimatedTokensAfter: NotRequired[float]
235
+ usage: NotRequired[Usage]
236
+ details: NotRequired[JSONValue]
237
+
238
+
239
+ class BashResult(TypedDict):
240
+ output: str
241
+ exitCode: NotRequired[float]
242
+ cancelled: bool
243
+ truncated: bool
244
+ fullOutputPath: NotRequired[str]
245
+
246
+
247
+ class ContextUsage(TypedDict):
248
+ tokens: float | None
249
+ contextWindow: float
250
+ percent: float | None
251
+
252
+
253
+ class SessionTokens(TypedDict):
254
+ input: float
255
+ output: float
256
+ cacheRead: float
257
+ cacheWrite: float
258
+ total: float
259
+
260
+
261
+ class SessionStats(TypedDict):
262
+ sessionFile: NotRequired[str]
263
+ sessionId: str
264
+ userMessages: float
265
+ assistantMessages: float
266
+ toolCalls: float
267
+ toolResults: float
268
+ totalMessages: float
269
+ tokens: SessionTokens
270
+ cost: float
271
+ contextUsage: NotRequired[ContextUsage]
272
+
273
+
274
+ class SourceInfo(TypedDict):
275
+ path: str
276
+ source: str
277
+ scope: Literal["user", "project", "temporary"]
278
+ origin: Literal["package", "top-level"]
279
+ baseDir: NotRequired[str]
280
+
281
+
282
+ class SlashCommand(TypedDict):
283
+ name: str
284
+ description: NotRequired[str]
285
+ source: Literal["extension", "prompt", "skill"]
286
+ sourceInfo: SourceInfo
287
+
288
+
289
+ class _EntryBase(TypedDict):
290
+ id: str
291
+ parentId: str | None
292
+ timestamp: str
293
+
294
+
295
+ class MessageEntry(_EntryBase):
296
+ type: Literal["message"]
297
+ message: AgentMessage
298
+
299
+
300
+ class ThinkingLevelChangeEntry(_EntryBase):
301
+ type: Literal["thinking_level_change"]
302
+ thinkingLevel: str
303
+
304
+
305
+ class ModelChangeEntry(_EntryBase):
306
+ type: Literal["model_change"]
307
+ provider: str
308
+ modelId: str
309
+
310
+
311
+ class CompactionEntry(_EntryBase):
312
+ type: Literal["compaction"]
313
+ summary: str
314
+ firstKeptEntryId: str
315
+ tokensBefore: float
316
+ details: NotRequired[JSONValue]
317
+ usage: NotRequired[Usage]
318
+ fromHook: NotRequired[bool]
319
+
320
+
321
+ class BranchSummaryEntry(_EntryBase):
322
+ type: Literal["branch_summary"]
323
+ fromId: str
324
+ summary: str
325
+ details: NotRequired[JSONValue]
326
+ usage: NotRequired[Usage]
327
+ fromHook: NotRequired[bool]
328
+
329
+
330
+ class CustomEntry(_EntryBase):
331
+ type: Literal["custom"]
332
+ customType: str
333
+ data: NotRequired[JSONValue]
334
+
335
+
336
+ class CustomMessageEntry(_EntryBase):
337
+ type: Literal["custom_message"]
338
+ customType: str
339
+ content: str | list[TextContent | ImageContent]
340
+ details: NotRequired[JSONValue]
341
+ display: bool
342
+
343
+
344
+ class LabelEntry(_EntryBase):
345
+ type: Literal["label"]
346
+ targetId: str
347
+ label: NotRequired[str]
348
+
349
+
350
+ class SessionInfoEntry(_EntryBase):
351
+ type: Literal["session_info"]
352
+ name: NotRequired[str]
353
+
354
+
355
+ SessionEntry: TypeAlias = (
356
+ MessageEntry
357
+ | ThinkingLevelChangeEntry
358
+ | ModelChangeEntry
359
+ | CompactionEntry
360
+ | BranchSummaryEntry
361
+ | CustomEntry
362
+ | CustomMessageEntry
363
+ | LabelEntry
364
+ | SessionInfoEntry
365
+ )
366
+
367
+
368
+ class SessionTreeNode(TypedDict):
369
+ entry: SessionEntry
370
+ children: list[SessionTreeNode]
371
+ label: NotRequired[str]
372
+ labelTimestamp: NotRequired[str]
373
+
374
+
375
+ class QueueState(TypedDict):
376
+ steering: list[str]
377
+ followUp: list[str]
378
+
379
+
380
+ class SessionChangeResult(TypedDict):
381
+ cancelled: bool
382
+
383
+
384
+ class ForkResult(SessionChangeResult):
385
+ # A veto omits text despite upstream's required-string declaration.
386
+ text: NotRequired[str]
387
+
388
+
389
+ class ModelCycleResult(TypedDict):
390
+ model: Model
391
+ thinkingLevel: ThinkingLevel
392
+ isScoped: bool
393
+
394
+
395
+ class ForkMessage(TypedDict):
396
+ entryId: str
397
+ text: str
398
+
399
+
400
+ class EntriesResult(TypedDict):
401
+ entries: list[SessionEntry]
402
+ leafId: str | None
403
+
404
+
405
+ class TreeResult(TypedDict):
406
+ tree: list[SessionTreeNode]
407
+ leafId: str | None
408
+
409
+
410
+ class _CommandBase(TypedDict):
411
+ id: NotRequired[str]
412
+
413
+
414
+ class PromptCommand(_CommandBase):
415
+ type: Literal["prompt"]
416
+ message: str
417
+ images: NotRequired[list[ImageContent]]
418
+ streamingBehavior: NotRequired[StreamingBehavior]
419
+
420
+
421
+ class QueueMessageCommand(_CommandBase):
422
+ type: Literal["steer", "follow_up"]
423
+ message: str
424
+ images: NotRequired[list[ImageContent]]
425
+
426
+
427
+ class EmptyCommand(_CommandBase):
428
+ """Commands with no arguments beyond the envelope."""
429
+
430
+ type: Literal[
431
+ "abort",
432
+ "clear_queue",
433
+ "get_state",
434
+ "cycle_model",
435
+ "get_available_models",
436
+ "cycle_thinking_level",
437
+ "get_available_thinking_levels",
438
+ "abort_retry",
439
+ "abort_bash",
440
+ "get_session_stats",
441
+ "clone",
442
+ "get_fork_messages",
443
+ "get_tree",
444
+ "get_last_assistant_text",
445
+ "get_messages",
446
+ "get_commands",
447
+ ]
448
+
449
+
450
+ class NewSessionCommand(_CommandBase):
451
+ type: Literal["new_session"]
452
+ parentSession: NotRequired[str]
453
+
454
+
455
+ class SetModelCommand(_CommandBase):
456
+ type: Literal["set_model"]
457
+ provider: str
458
+ modelId: str
459
+
460
+
461
+ class SetThinkingLevelCommand(_CommandBase):
462
+ type: Literal["set_thinking_level"]
463
+ level: ThinkingLevel
464
+
465
+
466
+ class SetQueueModeCommand(_CommandBase):
467
+ type: Literal["set_steering_mode", "set_follow_up_mode"]
468
+ mode: QueueMode
469
+
470
+
471
+ class CompactCommand(_CommandBase):
472
+ type: Literal["compact"]
473
+ customInstructions: NotRequired[str]
474
+
475
+
476
+ class SetAutomaticCommand(_CommandBase):
477
+ type: Literal["set_auto_compaction", "set_auto_retry"]
478
+ enabled: bool
479
+
480
+
481
+ class BashCommand(_CommandBase):
482
+ type: Literal["bash"]
483
+ command: str
484
+ excludeFromContext: NotRequired[bool]
485
+
486
+
487
+ class ExportHtmlCommand(_CommandBase):
488
+ type: Literal["export_html"]
489
+ outputPath: NotRequired[str]
490
+
491
+
492
+ class SwitchSessionCommand(_CommandBase):
493
+ type: Literal["switch_session"]
494
+ sessionPath: str
495
+
496
+
497
+ class ForkCommand(_CommandBase):
498
+ type: Literal["fork"]
499
+ entryId: str
500
+
501
+
502
+ class GetEntriesCommand(_CommandBase):
503
+ type: Literal["get_entries"]
504
+ since: NotRequired[str]
505
+
506
+
507
+ class SetSessionNameCommand(_CommandBase):
508
+ type: Literal["set_session_name"]
509
+ name: str
510
+
511
+
512
+ RpcCommand: TypeAlias = (
513
+ PromptCommand
514
+ | QueueMessageCommand
515
+ | EmptyCommand
516
+ | NewSessionCommand
517
+ | SetModelCommand
518
+ | SetThinkingLevelCommand
519
+ | SetQueueModeCommand
520
+ | CompactCommand
521
+ | SetAutomaticCommand
522
+ | BashCommand
523
+ | ExportHtmlCommand
524
+ | SwitchSessionCommand
525
+ | ForkCommand
526
+ | GetEntriesCommand
527
+ | SetSessionNameCommand
528
+ )
529
+
530
+
531
+ class AcceptanceReceipt(TypedDict):
532
+ """Successful prompt acknowledgement; it says nothing about run disposition."""
533
+
534
+ type: Literal["response"]
535
+ id: str
536
+ command: Literal["prompt"]
537
+ success: Literal[True]
538
+
539
+
540
+ class RpcSuccessResponse(TypedDict):
541
+ type: Literal["response"]
542
+ id: NotRequired[str]
543
+ command: str
544
+ success: Literal[True]
545
+ data: NotRequired[JSONValue]
546
+
547
+
548
+ class RpcErrorResponse(TypedDict):
549
+ type: Literal["response"]
550
+ id: NotRequired[str]
551
+ command: str
552
+ success: Literal[False]
553
+ error: str
554
+
555
+
556
+ RpcResponse: TypeAlias = RpcSuccessResponse | RpcErrorResponse
557
+
558
+
559
+ class AssistantStartEvent(TypedDict):
560
+ type: Literal["start"]
561
+
562
+
563
+ class ContentStartEvent(TypedDict):
564
+ type: Literal["text_start", "thinking_start"]
565
+ contentIndex: float
566
+
567
+
568
+ class ContentDeltaEvent(TypedDict):
569
+ type: Literal["text_delta", "thinking_delta", "toolcall_delta"]
570
+ contentIndex: float
571
+ delta: str
572
+
573
+
574
+ class ContentEndEvent(TypedDict):
575
+ type: Literal["text_end", "thinking_end"]
576
+ contentIndex: float
577
+ content: str
578
+
579
+
580
+ class ToolCallStartEvent(TypedDict):
581
+ type: Literal["toolcall_start"]
582
+ contentIndex: float
583
+ id: str
584
+ toolName: str
585
+
586
+
587
+ class ToolCallEndEvent(TypedDict):
588
+ type: Literal["toolcall_end"]
589
+ contentIndex: float
590
+ toolCall: ToolCall
591
+
592
+
593
+ class AssistantDoneEvent(TypedDict):
594
+ type: Literal["done"]
595
+ reason: Literal["stop", "length", "toolUse", "deferred"]
596
+ message: AssistantMessage
597
+
598
+
599
+ class AssistantErrorEvent(TypedDict):
600
+ type: Literal["error"]
601
+ reason: Literal["aborted", "error"]
602
+ error: AssistantMessage
603
+
604
+
605
+ AssistantMessageEvent: TypeAlias = (
606
+ AssistantStartEvent
607
+ | ContentStartEvent
608
+ | ContentDeltaEvent
609
+ | ContentEndEvent
610
+ | ToolCallStartEvent
611
+ | ToolCallEndEvent
612
+ | AssistantDoneEvent
613
+ | AssistantErrorEvent
614
+ )
615
+
616
+
617
+ class EmptySessionEvent(TypedDict):
618
+ type: Literal["agent_start", "agent_settled", "turn_start", "summarization_retry_finished"]
619
+
620
+
621
+ class AgentEndEvent(TypedDict):
622
+ type: Literal["agent_end"]
623
+ messages: list[AgentMessage]
624
+ willRetry: bool
625
+
626
+
627
+ class TurnEndEvent(TypedDict):
628
+ type: Literal["turn_end"]
629
+ message: AgentMessage
630
+ toolResults: list[ToolResultMessage]
631
+
632
+
633
+ class MessageBoundaryEvent(TypedDict):
634
+ type: Literal["message_start", "message_end"]
635
+ message: AgentMessage
636
+
637
+
638
+ class MessageUpdateEvent(TypedDict):
639
+ """Serialized shape: Pi removes cumulative message/partial snapshots."""
640
+
641
+ type: Literal["message_update"]
642
+ usage: Usage
643
+ assistantMessageEvent: AssistantMessageEvent
644
+
645
+
646
+ class ToolExecutionStartEvent(TypedDict):
647
+ type: Literal["tool_execution_start"]
648
+ toolCallId: str
649
+ toolName: str
650
+ args: JSONValue
651
+
652
+
653
+ class ToolExecutionUpdateEvent(TypedDict):
654
+ type: Literal["tool_execution_update"]
655
+ toolCallId: str
656
+ toolName: str
657
+ args: JSONValue
658
+ partialResult: JSONValue
659
+
660
+
661
+ class ToolExecutionEndEvent(TypedDict):
662
+ type: Literal["tool_execution_end"]
663
+ toolCallId: str
664
+ toolName: str
665
+ result: JSONValue
666
+ isError: bool
667
+
668
+
669
+ class QueueUpdateEvent(QueueState):
670
+ type: Literal["queue_update"]
671
+
672
+
673
+ class CompactionStartEvent(TypedDict):
674
+ type: Literal["compaction_start"]
675
+ reason: CompactionReason
676
+
677
+
678
+ class CompactionEndEvent(TypedDict):
679
+ type: Literal["compaction_end"]
680
+ reason: CompactionReason
681
+ result: NotRequired[CompactionResult]
682
+ aborted: bool
683
+ willRetry: bool
684
+ errorMessage: NotRequired[str]
685
+
686
+
687
+ class EntryAppendedEvent(TypedDict):
688
+ type: Literal["entry_appended"]
689
+ entry: SessionEntry
690
+
691
+
692
+ class SessionInfoChangedEvent(TypedDict):
693
+ type: Literal["session_info_changed"]
694
+ name: NotRequired[str]
695
+
696
+
697
+ class ThinkingLevelChangedEvent(TypedDict):
698
+ type: Literal["thinking_level_changed"]
699
+ level: ThinkingLevel
700
+
701
+
702
+ class RetryStartEvent(TypedDict):
703
+ type: Literal["auto_retry_start", "summarization_retry_scheduled"]
704
+ attempt: float
705
+ maxAttempts: float
706
+ delayMs: float
707
+ errorMessage: str
708
+
709
+
710
+ class AutoRetryEndEvent(TypedDict):
711
+ type: Literal["auto_retry_end"]
712
+ success: bool
713
+ attempt: float
714
+ finalError: NotRequired[str]
715
+
716
+
717
+ class CompactionRetryAttemptEvent(TypedDict):
718
+ type: Literal["summarization_retry_attempt_start"]
719
+ source: Literal["compaction"]
720
+ reason: CompactionReason
721
+
722
+
723
+ class BranchRetryAttemptEvent(TypedDict):
724
+ type: Literal["summarization_retry_attempt_start"]
725
+ source: Literal["branchSummary"]
726
+
727
+
728
+ class BashExecutionUpdateEvent(TypedDict):
729
+ type: Literal["bash_execution_update"]
730
+ id: NotRequired[str]
731
+ delta: str
732
+
733
+
734
+ SessionEvent: TypeAlias = (
735
+ EmptySessionEvent
736
+ | AgentEndEvent
737
+ | TurnEndEvent
738
+ | MessageBoundaryEvent
739
+ | MessageUpdateEvent
740
+ | ToolExecutionStartEvent
741
+ | ToolExecutionUpdateEvent
742
+ | ToolExecutionEndEvent
743
+ | QueueUpdateEvent
744
+ | CompactionStartEvent
745
+ | CompactionEndEvent
746
+ | EntryAppendedEvent
747
+ | SessionInfoChangedEvent
748
+ | ThinkingLevelChangedEvent
749
+ | RetryStartEvent
750
+ | AutoRetryEndEvent
751
+ | CompactionRetryAttemptEvent
752
+ | BranchRetryAttemptEvent
753
+ | BashExecutionUpdateEvent
754
+ )
755
+
756
+
757
+ class ExtensionErrorEvent(TypedDict):
758
+ type: Literal["extension_error"]
759
+ extensionPath: str
760
+ event: str
761
+ error: str
762
+
763
+
764
+ class _UIRequestBase(TypedDict):
765
+ type: Literal["extension_ui_request"]
766
+ id: str
767
+
768
+
769
+ class UISelectRequest(_UIRequestBase):
770
+ method: Literal["select"]
771
+ title: str
772
+ options: list[str]
773
+ timeout: NotRequired[float]
774
+
775
+
776
+ class UIConfirmRequest(_UIRequestBase):
777
+ method: Literal["confirm"]
778
+ title: str
779
+ message: str
780
+ timeout: NotRequired[float]
781
+
782
+
783
+ class UIInputRequest(_UIRequestBase):
784
+ method: Literal["input"]
785
+ title: str
786
+ placeholder: NotRequired[str]
787
+ timeout: NotRequired[float]
788
+
789
+
790
+ class UIEditorRequest(_UIRequestBase):
791
+ method: Literal["editor"]
792
+ title: str
793
+ prefill: NotRequired[str]
794
+
795
+
796
+ class UINotifyRequest(_UIRequestBase):
797
+ method: Literal["notify"]
798
+ message: str
799
+ notifyType: NotRequired[Literal["info", "warning", "error"]]
800
+
801
+
802
+ class UISetStatusRequest(_UIRequestBase):
803
+ method: Literal["setStatus"]
804
+ statusKey: str
805
+ statusText: NotRequired[str]
806
+
807
+
808
+ class UISetWidgetRequest(_UIRequestBase):
809
+ method: Literal["setWidget"]
810
+ widgetKey: str
811
+ widgetLines: NotRequired[list[str]]
812
+ widgetPlacement: NotRequired[Literal["aboveEditor", "belowEditor"]]
813
+
814
+
815
+ class UISetTitleRequest(_UIRequestBase):
816
+ method: Literal["setTitle"]
817
+ title: str
818
+
819
+
820
+ class UISetEditorTextRequest(_UIRequestBase):
821
+ method: Literal["set_editor_text"]
822
+ text: str
823
+
824
+
825
+ ExtensionUIRequest: TypeAlias = (
826
+ UISelectRequest
827
+ | UIConfirmRequest
828
+ | UIInputRequest
829
+ | UIEditorRequest
830
+ | UINotifyRequest
831
+ | UISetStatusRequest
832
+ | UISetWidgetRequest
833
+ | UISetTitleRequest
834
+ | UISetEditorTextRequest
835
+ )
836
+
837
+
838
+ class _UIResponseBase(TypedDict):
839
+ type: Literal["extension_ui_response"]
840
+ id: str
841
+
842
+
843
+ class UIValueResponse(_UIResponseBase):
844
+ value: str
845
+
846
+
847
+ class UIConfirmResponse(_UIResponseBase):
848
+ confirmed: bool
849
+
850
+
851
+ class UICancelResponse(_UIResponseBase):
852
+ cancelled: Literal[True]
853
+
854
+
855
+ ExtensionUIResponse: TypeAlias = UIValueResponse | UIConfirmResponse | UICancelResponse
856
+ RpcEvent: TypeAlias = SessionEvent | ExtensionErrorEvent | ExtensionUIRequest
857
+
858
+
859
+ @dataclass(frozen=True)
860
+ class Event:
861
+ """An unconverted event; raw preserves unknown fields and is omitted from repr."""
862
+
863
+ raw: RawRecord = field(repr=False)
864
+
865
+ @property
866
+ def type(self) -> str:
867
+ value = self.raw.get("type")
868
+ return value if isinstance(value, str) else ""
869
+
870
+ @property
871
+ def text_delta(self) -> str | None:
872
+ if self.type != "message_update":
873
+ return None
874
+ nested = self.raw.get("assistantMessageEvent")
875
+ if isinstance(nested, dict) and nested.get("type") == "text_delta":
876
+ delta = nested.get("delta")
877
+ if not isinstance(delta, str):
878
+ raise PiProtocolError("text_delta requires a string delta")
879
+ return delta
880
+ return None
881
+
882
+
883
+ @dataclass(frozen=True)
884
+ class SessionInfo:
885
+ """Session identity snapshot; paths and names are omitted from repr."""
886
+
887
+ session_id: str | None = None
888
+ session_file: str | None = field(default=None, repr=False)
889
+ session_name: str | None = field(default=None, repr=False)
890
+
891
+
892
+ @dataclass(frozen=True)
893
+ class UsageSummary:
894
+ """Observed assistant usage, not total billing; None means unknown.
895
+
896
+ Reasoning is a subset of output_tokens. It must not be added again.
897
+ A field remains unknown if any observed assistant omitted that measurement.
898
+ """
899
+
900
+ input_tokens: int | None = None
901
+ output_tokens: int | None = None
902
+ cache_read_tokens: int | None = None
903
+ cache_write_tokens: int | None = None
904
+ cache_write_1h_tokens: int | None = None
905
+ reasoning_tokens: int | None = None
906
+ total_tokens: int | None = None
907
+ cost: float | None = None
908
+ assistant_messages: int = 0
909
+
910
+
911
+ @dataclass(frozen=True)
912
+ class RunResult:
913
+ """Finalized messages from one settled run, including partial failed work.
914
+
915
+ Session is the identity current at completion, not an attribution of each
916
+ event. Text and messages can contain sensitive content and are not in repr.
917
+ """
918
+
919
+ text: str = field(repr=False)
920
+ messages: list[RawRecord] = field(repr=False)
921
+ stop_reason: str | None
922
+ session: SessionInfo
923
+ elapsed_seconds: float
924
+ usage: UsageSummary | None = None
925
+
926
+
927
+ @dataclass(frozen=True)
928
+ class Limits:
929
+ """Client deadlines (seconds) and bounded transport/subscription storage."""
930
+
931
+ startup_timeout: float = 30.0
932
+ command_timeout: float = 30.0
933
+ run_start_timeout: float = 30.0
934
+ cleanup_timeout: float = 5.0
935
+ max_record_bytes: int = 16 * 1024 * 1024
936
+ event_queue_size: int = 256
937
+ event_queue_bytes: int = 16 * 1024 * 1024
938
+ stderr_tail_bytes: int = 0
939
+ result_message_count: int = 4096
940
+ result_message_bytes: int = 64 * 1024 * 1024
941
+
942
+ def __post_init__(self) -> None:
943
+ for descriptor in fields(self):
944
+ name = descriptor.name
945
+ value = getattr(self, name)
946
+ if name.endswith("timeout"):
947
+ if (
948
+ isinstance(value, bool)
949
+ or not isinstance(value, (int, float))
950
+ or not math.isfinite(value)
951
+ or value <= 0
952
+ ):
953
+ raise ValueError(f"{name} must be a positive finite number")
954
+ elif (
955
+ isinstance(value, bool)
956
+ or not isinstance(value, int)
957
+ or value < (0 if name == "stderr_tail_bytes" else 1)
958
+ ):
959
+ minimum = "nonnegative" if name == "stderr_tail_bytes" else "positive"
960
+ raise ValueError(f"{name} must be a {minimum} integer")