Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 95 additions & 11 deletions src/utils/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,98 @@
import { invoke } from '@tauri-apps/api/core';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';

// Stream event types
export interface StreamEventBase {
eventType: string;
message?: string;
runId: string;
}

export interface StreamDeltaEvent extends StreamEventBase {
eventType: 'delta';
delta: string;
}

export interface StreamThinkingDeltaEvent extends StreamEventBase {
eventType: 'thinking_delta';
delta: string;
}

export interface StreamThinkingSignatureEvent extends StreamEventBase {
eventType: 'thinking_signature';
delta: string;
}

export interface StreamContextCompactedEvent extends StreamEventBase {
eventType: 'context_compacted';
contextCompaction: {
summary: string;
compactedThroughMessageId?: string | null;
compactedThroughIndex: number;
sourceMessageCount: number;
updatedAt: number;
originalMessageCount: number;
compactedMessageCount: number;
removedCount: number;
};
}

export interface StreamTextEvent extends StreamEventBase {
eventType: 'text';
text: string;
}

export interface StreamToolStartEvent extends StreamEventBase {
eventType: 'tool_start';
toolCallId: string;
toolName: string;
toolArguments?: any;
}

export interface StreamToolDeltaEvent extends StreamEventBase {
eventType: 'tool_delta';
toolCallId: string;
delta: string;
}

export interface StreamToolDoneEvent extends StreamEventBase {
eventType: 'tool_done';
toolCallId: string;
}

export interface StreamToolEvent extends StreamEventBase {
eventType: 'tool_use' | 'tool_result';
toolName?: string;
toolInput?: any;
toolResult?: any;
}

export interface StreamDoneEvent extends StreamEventBase {
eventType: 'done';
}

export interface StreamErrorEvent extends StreamEventBase {
eventType: 'error';
message: string;
}

export type StreamEvent =
| StreamDeltaEvent
| StreamThinkingDeltaEvent
| StreamThinkingSignatureEvent
| StreamContextCompactedEvent
| StreamTextEvent
| StreamToolStartEvent
| StreamToolDeltaEvent
| StreamToolDoneEvent
| StreamToolEvent
| StreamDoneEvent
| StreamErrorEvent;

export interface StreamEventWrapper {
payload: StreamEvent;
}

export const isTauriHost = (): boolean => {
if (typeof window === 'undefined') return false;
// If in vitest/jest test environment, default to desktop/tauri mock invoke
Expand Down Expand Up @@ -81,14 +173,6 @@ export const clearMobileToken = (): void => {
}
};

// Auto-extract and save token if present in URL
if (typeof window !== 'undefined') {
const token = new URLSearchParams(window.location.search).get('token');
if (token) {
localStorage.setItem('mobile_token', token);
}
}

export async function appInvoke<T>(cmd: string, args?: any): Promise<T> {
if (isTauriHost()) {
return invoke<T>(cmd, args);
Expand Down Expand Up @@ -246,15 +330,15 @@ export async function appInvoke<T>(cmd: string, args?: any): Promise<T> {

export function listenStream(
runId: string,
onEvent: (event: { payload: any }) => void,
onEvent: (event: StreamEventWrapper) => void,
onError?: (err: any) => void,
onComplete?: () => void
): () => void {
if (isTauriHost()) {
let active = true;
let unlisten: UnlistenFn | null = null;

listen<any>('agent-chat-stream', (event) => {
listen<StreamEvent>('agent-chat-stream', (event) => {
if (active) {
onEvent({ payload: event.payload });
if (event.payload?.eventType === 'done') {
Expand Down Expand Up @@ -282,7 +366,7 @@ export function listenStream(

eventSource.onmessage = (event) => {
try {
const payload = JSON.parse(event.data);
const payload = JSON.parse(event.data) as StreamEvent;
onEvent({ payload });
if (payload.eventType === 'done') {
onComplete?.();
Expand Down