11from __future__ import annotations
22
33import asyncio
4+ import base64
45import os
56import subprocess
67from pathlib import Path
910
1011from codex import Codex , CodexOptions , ThreadStartOptions , TurnOptions
1112from codex .app_server import AsyncAppServerClient
12- from codex .app_server .options import (
13- AppServerProcessOptions ,
14- AppServerThreadStartOptions ,
15- AppServerTurnOptions ,
16- )
13+ from codex .app_server .options import AppServerProcessOptions
1714from codex .protocol import types as protocol
1815
19- _STREAM_TIMEOUT_SECONDS = 90.0
2016_COMMAND_UNDER_TEST = "git diff --no-color HEAD~1...HEAD"
2117
2218
@@ -120,8 +116,8 @@ def test_run_with_real_codex_binary_and_api_key(tmp_path: Path) -> None:
120116 assert result == {"answer" : "OK" }
121117
122118
123- def test_streamed_git_command_events_with_real_codex_binary (tmp_path : Path ) -> None :
124- binary , api_key , child_env = _integration_binary_and_env (tmp_path )
119+ def test_streamed_command_exec_events_with_real_codex_binary (tmp_path : Path ) -> None :
120+ binary , _api_key , child_env = _integration_binary_and_env (tmp_path )
125121 repo = _create_git_repo (tmp_path / "repo" )
126122
127123 async def scenario () -> None :
@@ -132,65 +128,49 @@ async def scenario() -> None:
132128 )
133129 )
134130 try :
135- await client .account .login_api_key (api_key = api_key )
136- thread = await client .start_thread (
137- AppServerThreadStartOptions (
138- model = "gpt-5-mini" ,
131+ process_id = "codex-python-integration-git-diff"
132+ subscription = client .events .subscribe ({"command/exec/outputDelta" })
133+ command_task = asyncio .create_task (
134+ client .command .execute (
135+ command = ["/bin/sh" , "-lc" , _COMMAND_UNDER_TEST ],
139136 cwd = str (repo ),
140- approval_policy = protocol .AskForApproval ("never" ),
141- sandbox = protocol .SandboxMode ("workspace-write" ),
142- config = {
143- "skip_git_repo_check" : True ,
144- "web_search" : "disabled" ,
145- },
137+ process_id = process_id ,
138+ stream_stdout_stderr = True ,
139+ timeout_ms = 5000 ,
146140 )
147141 )
148- stream = await thread .run (
149- (
150- f'Use the shell tool to run `/bin/sh -lc "{ _COMMAND_UNDER_TEST } "` exactly '
151- "once. After the command completes, reply with the single word OK."
152- ),
153- AppServerTurnOptions (effort = protocol .ReasoningEffort ("low" )),
154- )
155142
156- saw_command_start = False
157- saw_command_completion = False
143+ stdout_chunks : list [str ] = []
158144 observed_events : list [str ] = []
159145
160146 while True :
161147 try :
162- event = await asyncio .wait_for (
163- stream . __anext__ (), timeout = _STREAM_TIMEOUT_SECONDS
164- )
165- except StopAsyncIteration :
166- break
148+ event = await asyncio .wait_for (subscription . next (), timeout = 0.2 )
149+ except TimeoutError :
150+ if command_task . done ():
151+ break
152+ continue
167153
168154 method = getattr (getattr (event , "method" , None ), "root" , type (event ).__name__ )
169- if isinstance (
170- event ,
171- protocol .ItemStartedNotificationModel | protocol .ItemCompletedNotificationModel ,
172- ):
173- item = event .params .item .root
174- observed_events .append (
175- f"{ method } : { type (item ).__name__ } : "
176- f"command={ getattr (item , 'command' , None )!r} "
177- )
178- if not isinstance (item , protocol .CommandExecutionThreadItem ):
179- continue
180- if _COMMAND_UNDER_TEST not in item .command :
181- continue
182- if isinstance (event , protocol .ItemStartedNotificationModel ):
183- saw_command_start = True
184- if isinstance (event , protocol .ItemCompletedNotificationModel ):
185- saw_command_completion = True
186- else :
187- observed_events .append (f"{ method } : { type (event ).__name__ } " )
188-
155+ observed_events .append (f"{ method } : { type (event ).__name__ } " )
156+ if not isinstance (event , protocol .CommandExecOutputDeltaNotificationModel ):
157+ continue
158+ if event .params .processId != process_id :
159+ continue
160+ if event .params .stream .root != "stdout" :
161+ continue
162+ stdout_chunks .append (base64 .b64decode (event .params .deltaBase64 ).decode ())
163+
164+ result = await command_task
165+ await subscription .close ()
189166 event_summary = "\n " .join (observed_events )
190- assert saw_command_start is True , event_summary
191- assert saw_command_completion is True , event_summary
192- assert stream .final_turn is not None
193- assert stream .final_turn .status .root == "completed"
167+ streamed_stdout = "" .join (stdout_chunks )
168+ assert result .exit_code == 0
169+ assert result .stderr == ""
170+ assert result .stdout == ""
171+ assert "-one" in streamed_stdout , event_summary
172+ assert "+two" in streamed_stdout , event_summary
173+ assert "+three" in streamed_stdout , event_summary
194174 finally :
195175 await client .close ()
196176
0 commit comments