codex-ipc 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.
@@ -0,0 +1,764 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from dataclasses import dataclass
5
+ from typing import AsyncIterator, Iterator
6
+
7
+ from .async_client import AsyncAppServerClient
8
+ from .client import AppServerClient, AppServerConfig
9
+ from .generated.v2_all import (
10
+ ApprovalsReviewer,
11
+ AskForApproval,
12
+ ModelListResponse,
13
+ Personality,
14
+ ReasoningEffort,
15
+ ReasoningSummary,
16
+ SandboxMode,
17
+ SandboxPolicy,
18
+ ServiceTier,
19
+ SortDirection,
20
+ ThreadArchiveResponse,
21
+ ThreadCompactStartResponse,
22
+ ThreadForkParams,
23
+ ThreadListCwdFilter,
24
+ ThreadListParams,
25
+ ThreadListResponse,
26
+ ThreadReadResponse,
27
+ ThreadResumeParams,
28
+ ThreadSetNameResponse,
29
+ ThreadSortKey,
30
+ ThreadSourceKind,
31
+ ThreadStartSource,
32
+ ThreadStartParams,
33
+ Turn as AppServerTurn,
34
+ TurnCompletedNotification,
35
+ TurnInterruptResponse,
36
+ TurnStartParams,
37
+ TurnSteerResponse,
38
+ )
39
+ from .models import InitializeResponse, JsonObject, Notification, ServerInfo
40
+ from ._inputs import (
41
+ ImageInput as ImageInput,
42
+ Input,
43
+ InputItem as InputItem,
44
+ LocalImageInput as LocalImageInput,
45
+ MentionInput as MentionInput,
46
+ RunInput,
47
+ SkillInput as SkillInput,
48
+ TextInput as TextInput,
49
+ _normalize_run_input,
50
+ _to_wire_input,
51
+ )
52
+ from ._run import (
53
+ RunResult,
54
+ _collect_async_run_result,
55
+ _collect_run_result,
56
+ )
57
+
58
+
59
+ def _split_user_agent(user_agent: str) -> tuple[str | None, str | None]:
60
+ raw = user_agent.strip()
61
+ if not raw:
62
+ return None, None
63
+ if "/" in raw:
64
+ name, version = raw.split("/", 1)
65
+ return (name or None), (version or None)
66
+ parts = raw.split(maxsplit=1)
67
+ if len(parts) == 2:
68
+ return parts[0], parts[1]
69
+ return raw, None
70
+
71
+
72
+ class Codex:
73
+ """Minimal typed SDK surface for app-server v2."""
74
+
75
+ def __init__(self, config: AppServerConfig | None = None) -> None:
76
+ self._client = AppServerClient(config=config)
77
+ try:
78
+ self._client.start()
79
+ self._init = self._validate_initialize(self._client.initialize())
80
+ except Exception:
81
+ self._client.close()
82
+ raise
83
+
84
+ def __enter__(self) -> "Codex":
85
+ return self
86
+
87
+ def __exit__(self, _exc_type, _exc, _tb) -> None:
88
+ self.close()
89
+
90
+ @staticmethod
91
+ def _validate_initialize(payload: InitializeResponse) -> InitializeResponse:
92
+ user_agent = (payload.userAgent or "").strip()
93
+ server = payload.serverInfo
94
+
95
+ server_name: str | None = None
96
+ server_version: str | None = None
97
+
98
+ if server is not None:
99
+ server_name = (server.name or "").strip() or None
100
+ server_version = (server.version or "").strip() or None
101
+
102
+ if (server_name is None or server_version is None) and user_agent:
103
+ parsed_name, parsed_version = _split_user_agent(user_agent)
104
+ if server_name is None:
105
+ server_name = parsed_name
106
+ if server_version is None:
107
+ server_version = parsed_version
108
+
109
+ normalized_server_name = (server_name or "").strip()
110
+ normalized_server_version = (server_version or "").strip()
111
+ if (
112
+ not user_agent
113
+ or not normalized_server_name
114
+ or not normalized_server_version
115
+ ):
116
+ raise RuntimeError(
117
+ "initialize response missing required metadata "
118
+ f"(user_agent={user_agent!r}, server_name={normalized_server_name!r}, server_version={normalized_server_version!r})"
119
+ )
120
+
121
+ if server is None:
122
+ payload.serverInfo = ServerInfo(
123
+ name=normalized_server_name,
124
+ version=normalized_server_version,
125
+ )
126
+ else:
127
+ server.name = normalized_server_name
128
+ server.version = normalized_server_version
129
+
130
+ return payload
131
+
132
+ @property
133
+ def metadata(self) -> InitializeResponse:
134
+ return self._init
135
+
136
+ def close(self) -> None:
137
+ self._client.close()
138
+
139
+ # BEGIN GENERATED: Codex.flat_methods
140
+ def thread_start(
141
+ self,
142
+ *,
143
+ approval_policy: AskForApproval | None = None,
144
+ approvals_reviewer: ApprovalsReviewer | None = None,
145
+ base_instructions: str | None = None,
146
+ config: JsonObject | None = None,
147
+ cwd: str | None = None,
148
+ developer_instructions: str | None = None,
149
+ ephemeral: bool | None = None,
150
+ model: str | None = None,
151
+ model_provider: str | None = None,
152
+ personality: Personality | None = None,
153
+ sandbox: SandboxMode | None = None,
154
+ service_name: str | None = None,
155
+ service_tier: ServiceTier | None = None,
156
+ session_start_source: ThreadStartSource | None = None,
157
+ ) -> Thread:
158
+ params = ThreadStartParams(
159
+ approval_policy=approval_policy,
160
+ approvals_reviewer=approvals_reviewer,
161
+ base_instructions=base_instructions,
162
+ config=config,
163
+ cwd=cwd,
164
+ developer_instructions=developer_instructions,
165
+ ephemeral=ephemeral,
166
+ model=model,
167
+ model_provider=model_provider,
168
+ personality=personality,
169
+ sandbox=sandbox,
170
+ service_name=service_name,
171
+ service_tier=service_tier,
172
+ session_start_source=session_start_source,
173
+ )
174
+ started = self._client.thread_start(params)
175
+ return Thread(self._client, started.thread.id)
176
+
177
+ def thread_list(
178
+ self,
179
+ *,
180
+ archived: bool | None = None,
181
+ cursor: str | None = None,
182
+ cwd: ThreadListCwdFilter | None = None,
183
+ limit: int | None = None,
184
+ model_providers: list[str] | None = None,
185
+ search_term: str | None = None,
186
+ sort_direction: SortDirection | None = None,
187
+ sort_key: ThreadSortKey | None = None,
188
+ source_kinds: list[ThreadSourceKind] | None = None,
189
+ use_state_db_only: bool | None = None,
190
+ ) -> ThreadListResponse:
191
+ params = ThreadListParams(
192
+ archived=archived,
193
+ cursor=cursor,
194
+ cwd=cwd,
195
+ limit=limit,
196
+ model_providers=model_providers,
197
+ search_term=search_term,
198
+ sort_direction=sort_direction,
199
+ sort_key=sort_key,
200
+ source_kinds=source_kinds,
201
+ use_state_db_only=use_state_db_only,
202
+ )
203
+ return self._client.thread_list(params)
204
+
205
+ def thread_resume(
206
+ self,
207
+ thread_id: str,
208
+ *,
209
+ approval_policy: AskForApproval | None = None,
210
+ approvals_reviewer: ApprovalsReviewer | None = None,
211
+ base_instructions: str | None = None,
212
+ config: JsonObject | None = None,
213
+ cwd: str | None = None,
214
+ developer_instructions: str | None = None,
215
+ model: str | None = None,
216
+ model_provider: str | None = None,
217
+ personality: Personality | None = None,
218
+ sandbox: SandboxMode | None = None,
219
+ service_tier: ServiceTier | None = None,
220
+ ) -> Thread:
221
+ params = ThreadResumeParams(
222
+ thread_id=thread_id,
223
+ approval_policy=approval_policy,
224
+ approvals_reviewer=approvals_reviewer,
225
+ base_instructions=base_instructions,
226
+ config=config,
227
+ cwd=cwd,
228
+ developer_instructions=developer_instructions,
229
+ model=model,
230
+ model_provider=model_provider,
231
+ personality=personality,
232
+ sandbox=sandbox,
233
+ service_tier=service_tier,
234
+ )
235
+ resumed = self._client.thread_resume(thread_id, params)
236
+ return Thread(self._client, resumed.thread.id)
237
+
238
+ def thread_fork(
239
+ self,
240
+ thread_id: str,
241
+ *,
242
+ approval_policy: AskForApproval | None = None,
243
+ approvals_reviewer: ApprovalsReviewer | None = None,
244
+ base_instructions: str | None = None,
245
+ config: JsonObject | None = None,
246
+ cwd: str | None = None,
247
+ developer_instructions: str | None = None,
248
+ ephemeral: bool | None = None,
249
+ model: str | None = None,
250
+ model_provider: str | None = None,
251
+ sandbox: SandboxMode | None = None,
252
+ service_tier: ServiceTier | None = None,
253
+ ) -> Thread:
254
+ params = ThreadForkParams(
255
+ thread_id=thread_id,
256
+ approval_policy=approval_policy,
257
+ approvals_reviewer=approvals_reviewer,
258
+ base_instructions=base_instructions,
259
+ config=config,
260
+ cwd=cwd,
261
+ developer_instructions=developer_instructions,
262
+ ephemeral=ephemeral,
263
+ model=model,
264
+ model_provider=model_provider,
265
+ sandbox=sandbox,
266
+ service_tier=service_tier,
267
+ )
268
+ forked = self._client.thread_fork(thread_id, params)
269
+ return Thread(self._client, forked.thread.id)
270
+
271
+ def thread_archive(self, thread_id: str) -> ThreadArchiveResponse:
272
+ return self._client.thread_archive(thread_id)
273
+
274
+ def thread_unarchive(self, thread_id: str) -> Thread:
275
+ unarchived = self._client.thread_unarchive(thread_id)
276
+ return Thread(self._client, unarchived.thread.id)
277
+
278
+ # END GENERATED: Codex.flat_methods
279
+
280
+ def models(self, *, include_hidden: bool = False) -> ModelListResponse:
281
+ return self._client.model_list(include_hidden=include_hidden)
282
+
283
+
284
+ class AsyncCodex:
285
+ """Async mirror of :class:`Codex`.
286
+
287
+ Prefer ``async with AsyncCodex()`` so initialization and shutdown are
288
+ explicit and paired. The async client initializes lazily on context entry
289
+ or first awaited API use.
290
+ """
291
+
292
+ def __init__(self, config: AppServerConfig | None = None) -> None:
293
+ self._client = AsyncAppServerClient(config=config)
294
+ self._init: InitializeResponse | None = None
295
+ self._initialized = False
296
+ self._init_lock = asyncio.Lock()
297
+
298
+ async def __aenter__(self) -> "AsyncCodex":
299
+ await self._ensure_initialized()
300
+ return self
301
+
302
+ async def __aexit__(self, _exc_type, _exc, _tb) -> None:
303
+ await self.close()
304
+
305
+ async def _ensure_initialized(self) -> None:
306
+ if self._initialized:
307
+ return
308
+ async with self._init_lock:
309
+ if self._initialized:
310
+ return
311
+ try:
312
+ await self._client.start()
313
+ payload = await self._client.initialize()
314
+ self._init = Codex._validate_initialize(payload)
315
+ self._initialized = True
316
+ except Exception:
317
+ await self._client.close()
318
+ self._init = None
319
+ self._initialized = False
320
+ raise
321
+
322
+ @property
323
+ def metadata(self) -> InitializeResponse:
324
+ if self._init is None:
325
+ raise RuntimeError(
326
+ "AsyncCodex is not initialized yet. Prefer `async with AsyncCodex()`; "
327
+ "initialization also happens on first awaited API use."
328
+ )
329
+ return self._init
330
+
331
+ async def close(self) -> None:
332
+ await self._client.close()
333
+ self._init = None
334
+ self._initialized = False
335
+
336
+ # BEGIN GENERATED: AsyncCodex.flat_methods
337
+ async def thread_start(
338
+ self,
339
+ *,
340
+ approval_policy: AskForApproval | None = None,
341
+ approvals_reviewer: ApprovalsReviewer | None = None,
342
+ base_instructions: str | None = None,
343
+ config: JsonObject | None = None,
344
+ cwd: str | None = None,
345
+ developer_instructions: str | None = None,
346
+ ephemeral: bool | None = None,
347
+ model: str | None = None,
348
+ model_provider: str | None = None,
349
+ personality: Personality | None = None,
350
+ sandbox: SandboxMode | None = None,
351
+ service_name: str | None = None,
352
+ service_tier: ServiceTier | None = None,
353
+ session_start_source: ThreadStartSource | None = None,
354
+ ) -> AsyncThread:
355
+ await self._ensure_initialized()
356
+ params = ThreadStartParams(
357
+ approval_policy=approval_policy,
358
+ approvals_reviewer=approvals_reviewer,
359
+ base_instructions=base_instructions,
360
+ config=config,
361
+ cwd=cwd,
362
+ developer_instructions=developer_instructions,
363
+ ephemeral=ephemeral,
364
+ model=model,
365
+ model_provider=model_provider,
366
+ personality=personality,
367
+ sandbox=sandbox,
368
+ service_name=service_name,
369
+ service_tier=service_tier,
370
+ session_start_source=session_start_source,
371
+ )
372
+ started = await self._client.thread_start(params)
373
+ return AsyncThread(self, started.thread.id)
374
+
375
+ async def thread_list(
376
+ self,
377
+ *,
378
+ archived: bool | None = None,
379
+ cursor: str | None = None,
380
+ cwd: ThreadListCwdFilter | None = None,
381
+ limit: int | None = None,
382
+ model_providers: list[str] | None = None,
383
+ search_term: str | None = None,
384
+ sort_direction: SortDirection | None = None,
385
+ sort_key: ThreadSortKey | None = None,
386
+ source_kinds: list[ThreadSourceKind] | None = None,
387
+ use_state_db_only: bool | None = None,
388
+ ) -> ThreadListResponse:
389
+ await self._ensure_initialized()
390
+ params = ThreadListParams(
391
+ archived=archived,
392
+ cursor=cursor,
393
+ cwd=cwd,
394
+ limit=limit,
395
+ model_providers=model_providers,
396
+ search_term=search_term,
397
+ sort_direction=sort_direction,
398
+ sort_key=sort_key,
399
+ source_kinds=source_kinds,
400
+ use_state_db_only=use_state_db_only,
401
+ )
402
+ return await self._client.thread_list(params)
403
+
404
+ async def thread_resume(
405
+ self,
406
+ thread_id: str,
407
+ *,
408
+ approval_policy: AskForApproval | None = None,
409
+ approvals_reviewer: ApprovalsReviewer | None = None,
410
+ base_instructions: str | None = None,
411
+ config: JsonObject | None = None,
412
+ cwd: str | None = None,
413
+ developer_instructions: str | None = None,
414
+ model: str | None = None,
415
+ model_provider: str | None = None,
416
+ personality: Personality | None = None,
417
+ sandbox: SandboxMode | None = None,
418
+ service_tier: ServiceTier | None = None,
419
+ ) -> AsyncThread:
420
+ await self._ensure_initialized()
421
+ params = ThreadResumeParams(
422
+ thread_id=thread_id,
423
+ approval_policy=approval_policy,
424
+ approvals_reviewer=approvals_reviewer,
425
+ base_instructions=base_instructions,
426
+ config=config,
427
+ cwd=cwd,
428
+ developer_instructions=developer_instructions,
429
+ model=model,
430
+ model_provider=model_provider,
431
+ personality=personality,
432
+ sandbox=sandbox,
433
+ service_tier=service_tier,
434
+ )
435
+ resumed = await self._client.thread_resume(thread_id, params)
436
+ return AsyncThread(self, resumed.thread.id)
437
+
438
+ async def thread_fork(
439
+ self,
440
+ thread_id: str,
441
+ *,
442
+ approval_policy: AskForApproval | None = None,
443
+ approvals_reviewer: ApprovalsReviewer | None = None,
444
+ base_instructions: str | None = None,
445
+ config: JsonObject | None = None,
446
+ cwd: str | None = None,
447
+ developer_instructions: str | None = None,
448
+ ephemeral: bool | None = None,
449
+ model: str | None = None,
450
+ model_provider: str | None = None,
451
+ sandbox: SandboxMode | None = None,
452
+ service_tier: ServiceTier | None = None,
453
+ ) -> AsyncThread:
454
+ await self._ensure_initialized()
455
+ params = ThreadForkParams(
456
+ thread_id=thread_id,
457
+ approval_policy=approval_policy,
458
+ approvals_reviewer=approvals_reviewer,
459
+ base_instructions=base_instructions,
460
+ config=config,
461
+ cwd=cwd,
462
+ developer_instructions=developer_instructions,
463
+ ephemeral=ephemeral,
464
+ model=model,
465
+ model_provider=model_provider,
466
+ sandbox=sandbox,
467
+ service_tier=service_tier,
468
+ )
469
+ forked = await self._client.thread_fork(thread_id, params)
470
+ return AsyncThread(self, forked.thread.id)
471
+
472
+ async def thread_archive(self, thread_id: str) -> ThreadArchiveResponse:
473
+ await self._ensure_initialized()
474
+ return await self._client.thread_archive(thread_id)
475
+
476
+ async def thread_unarchive(self, thread_id: str) -> AsyncThread:
477
+ await self._ensure_initialized()
478
+ unarchived = await self._client.thread_unarchive(thread_id)
479
+ return AsyncThread(self, unarchived.thread.id)
480
+
481
+ # END GENERATED: AsyncCodex.flat_methods
482
+
483
+ async def models(self, *, include_hidden: bool = False) -> ModelListResponse:
484
+ await self._ensure_initialized()
485
+ return await self._client.model_list(include_hidden=include_hidden)
486
+
487
+
488
+ @dataclass(slots=True)
489
+ class Thread:
490
+ _client: AppServerClient
491
+ id: str
492
+
493
+ def run(
494
+ self,
495
+ input: RunInput,
496
+ *,
497
+ approval_policy: AskForApproval | None = None,
498
+ approvals_reviewer: ApprovalsReviewer | None = None,
499
+ cwd: str | None = None,
500
+ effort: ReasoningEffort | None = None,
501
+ model: str | None = None,
502
+ output_schema: JsonObject | None = None,
503
+ personality: Personality | None = None,
504
+ sandbox_policy: SandboxPolicy | None = None,
505
+ service_tier: ServiceTier | None = None,
506
+ summary: ReasoningSummary | None = None,
507
+ ) -> RunResult:
508
+ turn = self.turn(
509
+ _normalize_run_input(input),
510
+ approval_policy=approval_policy,
511
+ approvals_reviewer=approvals_reviewer,
512
+ cwd=cwd,
513
+ effort=effort,
514
+ model=model,
515
+ output_schema=output_schema,
516
+ personality=personality,
517
+ sandbox_policy=sandbox_policy,
518
+ service_tier=service_tier,
519
+ summary=summary,
520
+ )
521
+ stream = turn.stream()
522
+ try:
523
+ return _collect_run_result(stream, turn_id=turn.id)
524
+ finally:
525
+ stream.close()
526
+
527
+ # BEGIN GENERATED: Thread.flat_methods
528
+ def turn(
529
+ self,
530
+ input: Input,
531
+ *,
532
+ approval_policy: AskForApproval | None = None,
533
+ approvals_reviewer: ApprovalsReviewer | None = None,
534
+ cwd: str | None = None,
535
+ effort: ReasoningEffort | None = None,
536
+ model: str | None = None,
537
+ output_schema: JsonObject | None = None,
538
+ personality: Personality | None = None,
539
+ sandbox_policy: SandboxPolicy | None = None,
540
+ service_tier: ServiceTier | None = None,
541
+ summary: ReasoningSummary | None = None,
542
+ ) -> TurnHandle:
543
+ wire_input = _to_wire_input(input)
544
+ params = TurnStartParams(
545
+ thread_id=self.id,
546
+ input=wire_input,
547
+ approval_policy=approval_policy,
548
+ approvals_reviewer=approvals_reviewer,
549
+ cwd=cwd,
550
+ effort=effort,
551
+ model=model,
552
+ output_schema=output_schema,
553
+ personality=personality,
554
+ sandbox_policy=sandbox_policy,
555
+ service_tier=service_tier,
556
+ summary=summary,
557
+ )
558
+ turn = self._client.turn_start(self.id, wire_input, params=params)
559
+ return TurnHandle(self._client, self.id, turn.turn.id)
560
+
561
+ # END GENERATED: Thread.flat_methods
562
+
563
+ def read(self, *, include_turns: bool = False) -> ThreadReadResponse:
564
+ return self._client.thread_read(self.id, include_turns=include_turns)
565
+
566
+ def set_name(self, name: str) -> ThreadSetNameResponse:
567
+ return self._client.thread_set_name(self.id, name)
568
+
569
+ def compact(self) -> ThreadCompactStartResponse:
570
+ return self._client.thread_compact(self.id)
571
+
572
+
573
+ @dataclass(slots=True)
574
+ class AsyncThread:
575
+ _codex: AsyncCodex
576
+ id: str
577
+
578
+ async def run(
579
+ self,
580
+ input: RunInput,
581
+ *,
582
+ approval_policy: AskForApproval | None = None,
583
+ approvals_reviewer: ApprovalsReviewer | None = None,
584
+ cwd: str | None = None,
585
+ effort: ReasoningEffort | None = None,
586
+ model: str | None = None,
587
+ output_schema: JsonObject | None = None,
588
+ personality: Personality | None = None,
589
+ sandbox_policy: SandboxPolicy | None = None,
590
+ service_tier: ServiceTier | None = None,
591
+ summary: ReasoningSummary | None = None,
592
+ ) -> RunResult:
593
+ turn = await self.turn(
594
+ _normalize_run_input(input),
595
+ approval_policy=approval_policy,
596
+ approvals_reviewer=approvals_reviewer,
597
+ cwd=cwd,
598
+ effort=effort,
599
+ model=model,
600
+ output_schema=output_schema,
601
+ personality=personality,
602
+ sandbox_policy=sandbox_policy,
603
+ service_tier=service_tier,
604
+ summary=summary,
605
+ )
606
+ stream = turn.stream()
607
+ try:
608
+ return await _collect_async_run_result(stream, turn_id=turn.id)
609
+ finally:
610
+ await stream.aclose()
611
+
612
+ # BEGIN GENERATED: AsyncThread.flat_methods
613
+ async def turn(
614
+ self,
615
+ input: Input,
616
+ *,
617
+ approval_policy: AskForApproval | None = None,
618
+ approvals_reviewer: ApprovalsReviewer | None = None,
619
+ cwd: str | None = None,
620
+ effort: ReasoningEffort | None = None,
621
+ model: str | None = None,
622
+ output_schema: JsonObject | None = None,
623
+ personality: Personality | None = None,
624
+ sandbox_policy: SandboxPolicy | None = None,
625
+ service_tier: ServiceTier | None = None,
626
+ summary: ReasoningSummary | None = None,
627
+ ) -> AsyncTurnHandle:
628
+ await self._codex._ensure_initialized()
629
+ wire_input = _to_wire_input(input)
630
+ params = TurnStartParams(
631
+ thread_id=self.id,
632
+ input=wire_input,
633
+ approval_policy=approval_policy,
634
+ approvals_reviewer=approvals_reviewer,
635
+ cwd=cwd,
636
+ effort=effort,
637
+ model=model,
638
+ output_schema=output_schema,
639
+ personality=personality,
640
+ sandbox_policy=sandbox_policy,
641
+ service_tier=service_tier,
642
+ summary=summary,
643
+ )
644
+ turn = await self._codex._client.turn_start(
645
+ self.id,
646
+ wire_input,
647
+ params=params,
648
+ )
649
+ return AsyncTurnHandle(self._codex, self.id, turn.turn.id)
650
+
651
+ # END GENERATED: AsyncThread.flat_methods
652
+
653
+ async def read(self, *, include_turns: bool = False) -> ThreadReadResponse:
654
+ await self._codex._ensure_initialized()
655
+ return await self._codex._client.thread_read(
656
+ self.id, include_turns=include_turns
657
+ )
658
+
659
+ async def set_name(self, name: str) -> ThreadSetNameResponse:
660
+ await self._codex._ensure_initialized()
661
+ return await self._codex._client.thread_set_name(self.id, name)
662
+
663
+ async def compact(self) -> ThreadCompactStartResponse:
664
+ await self._codex._ensure_initialized()
665
+ return await self._codex._client.thread_compact(self.id)
666
+
667
+
668
+ @dataclass(slots=True)
669
+ class TurnHandle:
670
+ _client: AppServerClient
671
+ thread_id: str
672
+ id: str
673
+
674
+ def steer(self, input: Input) -> TurnSteerResponse:
675
+ return self._client.turn_steer(self.thread_id, self.id, _to_wire_input(input))
676
+
677
+ def interrupt(self) -> TurnInterruptResponse:
678
+ return self._client.turn_interrupt(self.thread_id, self.id)
679
+
680
+ def stream(self) -> Iterator[Notification]:
681
+ self._client.register_turn_notifications(self.id)
682
+ try:
683
+ while True:
684
+ event = self._client.next_turn_notification(self.id)
685
+ yield event
686
+ if (
687
+ event.method == "turn/completed"
688
+ and isinstance(event.payload, TurnCompletedNotification)
689
+ and event.payload.turn.id == self.id
690
+ ):
691
+ break
692
+ finally:
693
+ self._client.unregister_turn_notifications(self.id)
694
+
695
+ def run(self) -> AppServerTurn:
696
+ completed: TurnCompletedNotification | None = None
697
+ stream = self.stream()
698
+ try:
699
+ for event in stream:
700
+ payload = event.payload
701
+ if (
702
+ isinstance(payload, TurnCompletedNotification)
703
+ and payload.turn.id == self.id
704
+ ):
705
+ completed = payload
706
+ finally:
707
+ stream.close()
708
+
709
+ if completed is None:
710
+ raise RuntimeError("turn completed event not received")
711
+ return completed.turn
712
+
713
+
714
+ @dataclass(slots=True)
715
+ class AsyncTurnHandle:
716
+ _codex: AsyncCodex
717
+ thread_id: str
718
+ id: str
719
+
720
+ async def steer(self, input: Input) -> TurnSteerResponse:
721
+ await self._codex._ensure_initialized()
722
+ return await self._codex._client.turn_steer(
723
+ self.thread_id,
724
+ self.id,
725
+ _to_wire_input(input),
726
+ )
727
+
728
+ async def interrupt(self) -> TurnInterruptResponse:
729
+ await self._codex._ensure_initialized()
730
+ return await self._codex._client.turn_interrupt(self.thread_id, self.id)
731
+
732
+ async def stream(self) -> AsyncIterator[Notification]:
733
+ await self._codex._ensure_initialized()
734
+ self._codex._client.register_turn_notifications(self.id)
735
+ try:
736
+ while True:
737
+ event = await self._codex._client.next_turn_notification(self.id)
738
+ yield event
739
+ if (
740
+ event.method == "turn/completed"
741
+ and isinstance(event.payload, TurnCompletedNotification)
742
+ and event.payload.turn.id == self.id
743
+ ):
744
+ break
745
+ finally:
746
+ self._codex._client.unregister_turn_notifications(self.id)
747
+
748
+ async def run(self) -> AppServerTurn:
749
+ completed: TurnCompletedNotification | None = None
750
+ stream = self.stream()
751
+ try:
752
+ async for event in stream:
753
+ payload = event.payload
754
+ if (
755
+ isinstance(payload, TurnCompletedNotification)
756
+ and payload.turn.id == self.id
757
+ ):
758
+ completed = payload
759
+ finally:
760
+ await stream.aclose()
761
+
762
+ if completed is None:
763
+ raise RuntimeError("turn completed event not received")
764
+ return completed.turn