Skip to main content

Overview

RCLI provides callback functions for real-time event handling:
  • Transcript: Real-time STT updates (streaming partial and final results)
  • State: Pipeline state transitions (idle → listening → processing → speaking)
  • Action: Action execution results (success/failure)
  • Tool Trace: LLM tool call observability (detected → result)
  • Event: Generic events (file processing, benchmarks, timings)
Callbacks fire on worker threads. If you need to update UI, marshal the callback to your main/UI thread.

Callback Types

RCLITranscriptCallback

Real-time transcript updates from streaming STT.
typedef void (*RCLITranscriptCallback)(
    const char* text,
    int is_final,
    void* user_data
);
text
const char*
Transcript text. Partial results update continuously; final results are stable.
is_final
int
  • 1: Final transcript (speech endpoint detected)
  • 0: Partial transcript (still speaking)
user_data
void*
User data pointer passed to rcli_set_transcript_callback()

Example

void on_transcript(const char* text, int is_final, void* user_data) {
    if (is_final) {
        printf("\nFinal: %s\n", text);
    } else {
        printf("\rPartial: %-50s", text);  // Live updates
        fflush(stdout);
    }
}

rcli_set_transcript_callback(handle, on_transcript, NULL);
rcli_start_listening(handle);

RCLIStateCallback

Pipeline state transitions.
typedef void (*RCLIStateCallback)(
    int old_state,
    int new_state,
    void* user_data
);
old_state
int
Previous state (0=IDLE, 1=LISTENING, 2=PROCESSING, 3=SPEAKING, 4=INTERRUPTED)
new_state
int
New state
user_data
void*
User data pointer

Example

void on_state(int old_state, int new_state, void* user_data) {
    const char* names[] = {"IDLE", "LISTENING", "PROCESSING", "SPEAKING", "INTERRUPTED"};
    printf("State: %s -> %s\n", names[old_state], names[new_state]);
    
    // Update UI indicator
    if (new_state == 1) {
        show_listening_indicator();
    } else if (new_state == 3) {
        show_speaking_indicator();
    }
}

rcli_set_state_callback(handle, on_state, NULL);

RCLIActionCallback

Action execution results (macOS actions like opening apps, creating reminders).
typedef void (*RCLIActionCallback)(
    const char* action_name,
    const char* result_json,
    int success,
    void* user_data
);
action_name
const char*
Name of the action that was executed (e.g., "open_app", "create_reminder")
result_json
const char*
JSON result from the action:
{"success": true, "output": "Opened Safari", "error": ""}
success
int
  • 1: Action succeeded
  • 0: Action failed
user_data
void*
User data pointer

Example

void on_action(const char* name, const char* result_json, int success, void* user_data) {
    if (success) {
        printf("[Action] %s completed successfully\n", name);
    } else {
        printf("[Action] %s failed: %s\n", name, result_json);
    }
}

rcli_set_action_callback(handle, on_action, NULL);

// Trigger an action
rcli_process_command(handle, "open Safari");
// Callback fires: on_action("open_app", "{\"success\":true,...}", 1, NULL)

RCLIToolTraceCallback

Tool call observability - tracks both detection and execution.
typedef void (*RCLIToolTraceCallback)(
    const char* event,
    const char* tool_name,
    const char* data,
    int success,
    void* user_data
);
event
const char*
  • "detected": LLM emitted a tool call
  • "result": Tool execution completed
tool_name
const char*
Name of the tool (e.g., "open_app", "get_current_time", "calculate")
data
const char*
  • For "detected": JSON arguments
  • For "result": JSON result
success
int
  • For "detected": Always 0
  • For "result": 1 if successful, 0 if failed
user_data
void*
User data pointer
This callback covers all tools: both built-in tools (get_current_time, calculate) and actions. RCLIActionCallback only fires for actions.

Example: Tool Call Debugger

void on_tool_trace(const char* event, const char* tool_name,
                   const char* data, int success, void* user_data) {
    if (strcmp(event, "detected") == 0) {
        printf("[LLM] Calling tool: %s(%s)\n", tool_name, data);
    } else if (strcmp(event, "result") == 0) {
        if (success) {
            printf("[Tool] %s -> %s\n", tool_name, data);
        } else {
            printf("[Tool] %s FAILED: %s\n", tool_name, data);
        }
    }
}

rcli_set_tool_trace_callback(handle, on_tool_trace, NULL);

rcli_process_command(handle, "What time is it?");
// Output:
// [LLM] Calling tool: get_current_time({})
// [Tool] get_current_time -> {"time": "2:30 PM"}

RCLIEventCallback

Generic event callback for file processing, benchmarks, and timings.
typedef void (*RCLIEventCallback)(
    const char* event,
    const char* data,
    void* user_data
);
event
const char*
Event type:
  • "state_change": Pipeline state transition
  • "timings": Performance timings (JSON)
  • "benchmark_progress": Benchmark iteration progress
  • "benchmark_run": Single benchmark run result
  • "benchmark_result": Aggregate benchmark results
data
const char*
Event data (format depends on event type)
user_data
void*
User data pointer

Example: File Processing with Timings

void on_event(const char* event, const char* data, void* user_data) {
    if (strcmp(event, "state_change") == 0) {
        printf("State: %s\n", data);
    } else if (strcmp(event, "timings") == 0) {
        printf("Timings: %s\n", data);
    }
}

rcli_process_wav(handle, "input.wav", "output.wav", on_event, NULL);
// Output:
// State: listening
// State: processing
// State: speaking
// Timings: {"stt_ms":234,"llm_ttft_ms":89,...}

Callback Registration

rcli_set_transcript_callback

void rcli_set_transcript_callback(
    RCLIHandle handle,
    RCLITranscriptCallback cb,
    void* user_data
);

rcli_set_state_callback

void rcli_set_state_callback(
    RCLIHandle handle,
    RCLIStateCallback cb,
    void* user_data
);

rcli_set_action_callback

void rcli_set_action_callback(
    RCLIHandle handle,
    RCLIActionCallback cb,
    void* user_data
);

rcli_set_tool_trace_callback

void rcli_set_tool_trace_callback(
    RCLIHandle handle,
    RCLIToolTraceCallback cb,
    void* user_data
);

Thread Safety

Callbacks fire on worker threads:
  • RCLITranscriptCallback: STT thread
  • RCLIStateCallback: State transition thread (varies)
  • RCLIActionCallback: LLM thread
  • RCLIToolTraceCallback: LLM thread (synchronous with rcli_process_command())
  • RCLIEventCallback: Worker thread (file processing, benchmarks)

Swift Example: Dispatch to Main Thread

func onTranscript(text: UnsafePointer<CChar>?, isFinal: Int32, userData: UnsafeMutableRawPointer?) {
    guard let text = text else { return }
    let transcript = String(cString: text)
    
    DispatchQueue.main.async {
        // Update UI on main thread
        self.transcriptLabel.text = transcript
    }
}

rcli_set_transcript_callback(handle, onTranscript, nil)

C Example: User Data

struct AppState {
    int message_count;
    FILE* log_file;
};

void on_transcript(const char* text, int is_final, void* user_data) {
    struct AppState* state = (struct AppState*)user_data;
    
    if (is_final) {
        state->message_count++;
        fprintf(state->log_file, "[%d] %s\n", state->message_count, text);
    }
}

struct AppState state = { .message_count = 0, .log_file = fopen("log.txt", "w") };
rcli_set_transcript_callback(handle, on_transcript, &state);

Complete Example: Event Monitor

#include "api/rcli_api.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void on_transcript(const char* text, int is_final, void* user_data) {
    printf("[STT] %s %s\n", is_final ? "FINAL:" : "partial:", text);
}

void on_state(int old_state, int new_state, void* user_data) {
    const char* names[] = {"IDLE", "LISTENING", "PROCESSING", "SPEAKING", "INTERRUPTED"};
    printf("[State] %s -> %s\n", names[old_state], names[new_state]);
}

void on_action(const char* name, const char* result, int success, void* user_data) {
    printf("[Action] %s: %s\n", name, success ? "OK" : "FAILED");
}

void on_tool_trace(const char* event, const char* tool, const char* data,
                   int success, void* user_data) {
    if (strcmp(event, "detected") == 0) {
        printf("[LLM] -> %s(%s)\n", tool, data);
    } else {
        printf("[Tool] %s <- %s\n", tool, success ? "success" : "error");
    }
}

int main() {
    RCLIHandle handle = rcli_create(NULL);
    rcli_init(handle, "/path/to/models", 99);

    // Register all callbacks
    rcli_set_transcript_callback(handle, on_transcript, NULL);
    rcli_set_state_callback(handle, on_state, NULL);
    rcli_set_action_callback(handle, on_action, NULL);
    rcli_set_tool_trace_callback(handle, on_tool_trace, NULL);

    // Start listening
    rcli_start_listening(handle);
    sleep(30);
    rcli_stop_listening(handle);

    rcli_destroy(handle);
    return 0;
}

See Also

Build docs developers (and LLMs) love