All these streaming functions were really easy to implement, actually!
for example, here is the capture builtin code (simplified slightly)
do_builtin_capture:
; Push the old stream callback and its data onto the stack.
push word [print_callback]
push word [print_data]
; Set our callback.
mov word [print_callback],capture_string
; ... create empty string object ...
; (omitted)
; Store string as our callback data.
mov [print_data],dx
; ... evaluate next argument ...
; (omitted)
; Restore previous stream callback.
pop word [print_data]
pop word [print_callback]
ret
capture_string:
; The callback!
; Read each character..
lodsb
or al,al
jz .done
; And append it to the string.
mov bx,[print_data]
push si
call string_append_character
pop si
mov [print_data],bx
jmp capture_string
.done: ret
all that you have to do to receive data, is save the old callback, update the callback to your callback, and then restore the previous one once your builtin finishes