There doesn't appear to be any way to do this in Direct3D 9.
In theory the D3D11 shader reflection API looks as though it should work, since there's nothing in (at least this part of) it that actually requires Direct3D 11 (it doesn't even require a device to be created). In my own tests the D3DReflect call fails.
If you think about it this makes sense. Direct3D 9 is still very much built around the old fixed-function vertex inputs, and it's legal for a vertex declaration to provide input that the vertex shader doesn't consume, similarly legal for a vertex shader to consume input that a vertex declaration doesn't provide. The tighter coupling of input layouts with vertex shaders only came about in Direct3D 10:
The Input Assembler (IA) replaces the Direct3D 9 style Vertex Stream Declaration and it's description structure is very similar in form. The main difference that the IA brings is that the IA layout object created must directly map to a specific format of shader input signature.
There are also possible historical reasons for this. In a similar way to the evolution of Direct3D 11 from Direct3D 10, Direct3D 9 was evolved from Direct3D 8, and in Direct3D 8 the vertex format and vertex shader were actually the same object. Unfortunately the old online documentation is now gone, but references to it may be found in the guidance for converting from Direct3D 8 to 9 (the text actually states "call IDirect3DDevice9::SetVertexShader" but this is clearly a typo and I've silently corrected it):
For DirectX 8.x, vertex declarations are tied to vertex shaders ..... For vertex shaders, call IDirect3DDevice8::SetVertexShader with a handle to a previously create vertex shader. The shader includes a vertex declaration.
Ultimately the Direct3D 9 Effect interface is just a bunch of text parsing and wrappers around the actual real API (which is just a handful of Set*Shader and Set*ShaderConstant calls), so you could create the desired outcome yourself by doing the necessary text parsing yourself.