xworkmate-app/lib/runtime/codex_ffi_bindings.dart
2026-03-19 23:47:04 +08:00

340 lines
9.5 KiB
Dart

// FFI bindings for Codex CLI integration.
//
// These bindings provide direct access to the native Rust library.
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
// ============================================================================
// FFI Structures
// ============================================================================
/// FFI-compatible result type.
final class CodexResultFFI extends Struct {
@Bool()
external bool success;
@Int32()
external int errorCode;
external Pointer<Utf8> errorMessage;
}
/// FFI-compatible message type.
final class CodexMessageFFI extends Struct {
external Pointer<Utf8> messageType;
external Pointer<Utf8> content;
external Pointer<Utf8> threadId;
external Pointer<Utf8> turnId;
}
/// FFI-compatible event type.
final class CodexEventFFI extends Struct {
external Pointer<Utf8> eventType;
external Pointer<Utf8> threadId;
external Pointer<Utf8> turnId;
external Pointer<Utf8> data;
@Int64()
external int timestamp;
}
/// FFI-compatible configuration.
final class CodexConfigFFI extends Struct {
external Pointer<Utf8> codexPath;
external Pointer<Utf8> workingDirectory;
@Int32()
external int sandboxMode;
@Int32()
external int approvalPolicy;
external Pointer<Utf8> model;
external Pointer<Utf8> apiKey;
external Pointer<Utf8> gatewayUrl;
@Bool()
external bool debug;
}
/// Opaque thread handle.
final class ThreadHandleFFI extends Struct {
@Uint64()
external int id;
}
// ============================================================================
// Native Functions
// ============================================================================
typedef _CodexInitNative = Int32 Function();
typedef _CodexInitDart = int Function();
typedef _CodexRuntimeCreateNative =
Pointer<CodexRuntime> Function(Pointer<CodexConfigFFI> config);
typedef _CodexRuntimeCreateDart =
Pointer<CodexRuntime> Function(Pointer<CodexConfigFFI> config);
typedef _CodexRuntimeDestroyNative =
Void Function(Pointer<CodexRuntime> runtime);
typedef _CodexRuntimeDestroyDart = void Function(Pointer<CodexRuntime> runtime);
typedef _CodexStartThreadNative =
ThreadHandleFFI Function(Pointer<CodexRuntime> runtime, Pointer<Utf8> cwd);
typedef _CodexStartThreadDart =
ThreadHandleFFI Function(Pointer<CodexRuntime> runtime, Pointer<Utf8> cwd);
typedef _CodexSendMessageNative =
Int32 Function(
Pointer<CodexRuntime> runtime,
ThreadHandleFFI thread,
Pointer<Utf8> message,
);
typedef _CodexSendMessageDart =
int Function(
Pointer<CodexRuntime> runtime,
ThreadHandleFFI thread,
Pointer<Utf8> message,
);
typedef _CodexPollEventsNative =
UintPtr Function(
Pointer<CodexRuntime> runtime,
Pointer<CodexEventFFI> events,
UintPtr maxEvents,
);
typedef _CodexPollEventsDart =
int Function(
Pointer<CodexRuntime> runtime,
Pointer<CodexEventFFI> events,
int maxEvents,
);
typedef _CodexShutdownNative = Int32 Function(Pointer<CodexRuntime> runtime);
typedef _CodexShutdownDart = int Function(Pointer<CodexRuntime> runtime);
typedef _CodexLastErrorNative =
Pointer<Utf8> Function(Pointer<CodexRuntime> runtime);
typedef _CodexLastErrorDart =
Pointer<Utf8> Function(Pointer<CodexRuntime> runtime);
// Opaque runtime type
final class CodexRuntime extends Opaque {}
// ============================================================================
// Dart Wrapper Class
// ============================================================================
/// Dart wrapper for Codex FFI.
class CodexFFIBindings {
final DynamicLibrary _lib;
late final _CodexInitDart _init;
late final _CodexRuntimeCreateDart _runtimeCreate;
late final _CodexRuntimeDestroyDart _runtimeDestroy;
late final _CodexStartThreadDart _startThread;
late final _CodexSendMessageDart _sendMessage;
late final _CodexPollEventsDart _pollEvents;
late final _CodexShutdownDart _shutdown;
late final _CodexLastErrorDart _lastError;
Pointer<CodexRuntime>? _runtime;
CodexFFIBindings() : _lib = _loadLibrary() {
_init = _lib.lookupFunction<_CodexInitNative, _CodexInitDart>('codex_init');
_runtimeCreate = _lib
.lookupFunction<_CodexRuntimeCreateNative, _CodexRuntimeCreateDart>(
'codex_runtime_create',
);
_runtimeDestroy = _lib
.lookupFunction<_CodexRuntimeDestroyNative, _CodexRuntimeDestroyDart>(
'codex_runtime_destroy',
);
_startThread = _lib
.lookupFunction<_CodexStartThreadNative, _CodexStartThreadDart>(
'codex_start_thread',
);
_sendMessage = _lib
.lookupFunction<_CodexSendMessageNative, _CodexSendMessageDart>(
'codex_send_message',
);
_pollEvents = _lib
.lookupFunction<_CodexPollEventsNative, _CodexPollEventsDart>(
'codex_poll_events',
);
_shutdown = _lib.lookupFunction<_CodexShutdownNative, _CodexShutdownDart>(
'codex_shutdown',
);
_lastError = _lib
.lookupFunction<_CodexLastErrorNative, _CodexLastErrorDart>(
'codex_last_error',
);
}
static DynamicLibrary _loadLibrary() {
if (Platform.isMacOS) {
return DynamicLibrary.open('libcodex_ffi.dylib');
} else if (Platform.isLinux) {
return DynamicLibrary.open('libcodex_ffi.so');
} else if (Platform.isWindows) {
return DynamicLibrary.open('codex_ffi.dll');
}
throw UnsupportedError('Unsupported platform');
}
/// Initialize the library.
void initialize() {
final result = _init();
if (result != 0) {
throw StateError('Failed to initialize Codex FFI');
}
}
/// Create a runtime with configuration.
void createRuntime(CodexConfig config) {
if (_runtime != null) {
throw StateError('Runtime already created');
}
final configPtr = _createConfigFFI(config);
try {
_runtime = _runtimeCreate(configPtr);
if (_runtime == nullptr) {
throw StateError('Failed to create runtime');
}
} finally {
_freeConfigFFI(configPtr);
}
}
/// Destroy the runtime.
void destroyRuntime() {
if (_runtime != null) {
_runtimeDestroy(_runtime!);
_runtime = nullptr;
}
}
/// Start a new thread.
int startThread(String cwd) {
_ensureRuntime();
final cwdPtr = cwd.toNativeUtf8();
try {
final handle = _startThread(_runtime!, cwdPtr);
return handle.id;
} finally {
calloc.free(cwdPtr);
}
}
/// Send a message to the thread.
int sendMessage(int threadId, String message) {
_ensureRuntime();
final messagePtr = message.toNativeUtf8();
final handlePtr = calloc<ThreadHandleFFI>();
try {
handlePtr.ref.id = threadId;
return _sendMessage(_runtime!, handlePtr.ref, messagePtr);
} finally {
calloc.free(messagePtr);
calloc.free(handlePtr);
}
}
/// Poll for events.
List<Map<String, dynamic>> pollEvents(int maxEvents) {
_ensureRuntime();
final eventsPtr = calloc<CodexEventFFI>(maxEvents);
try {
final count = _pollEvents(_runtime!, eventsPtr, maxEvents);
final events = <Map<String, dynamic>>[];
for (var i = 0; i < count; i++) {
final event = eventsPtr[i];
events.add({
'eventType': event.eventType.toDartString(),
'threadId': event.threadId.toDartString(),
'turnId': event.turnId.toDartString(),
'data': event.data.toDartString(),
'timestamp': event.timestamp,
});
}
return events;
} finally {
calloc.free(eventsPtr);
}
}
/// Shutdown the runtime.
void shutdown() {
_ensureRuntime();
_shutdown(_runtime!);
}
/// Get last error message.
String? lastError() {
if (_runtime == null) return null;
final ptr = _lastError(_runtime!);
if (ptr == nullptr) return null;
return ptr.toDartString();
}
void _ensureRuntime() {
if (_runtime == null) {
throw StateError('Runtime not initialized');
}
}
Pointer<CodexConfigFFI> _createConfigFFI(CodexConfig config) {
final ptr = calloc<CodexConfigFFI>();
ptr.ref.codexPath = config.codexPath?.toNativeUtf8() ?? nullptr;
ptr.ref.workingDirectory =
config.workingDirectory?.toNativeUtf8() ?? nullptr;
ptr.ref.sandboxMode = config.sandboxMode;
ptr.ref.approvalPolicy = config.approvalPolicy;
ptr.ref.model = config.model?.toNativeUtf8() ?? nullptr;
ptr.ref.apiKey = config.apiKey?.toNativeUtf8() ?? nullptr;
ptr.ref.gatewayUrl = config.gatewayUrl?.toNativeUtf8() ?? nullptr;
ptr.ref.debug = config.debug;
return ptr;
}
void _freeConfigFFI(Pointer<CodexConfigFFI> ptr) {
if (ptr.ref.codexPath != nullptr) {
calloc.free(ptr.ref.codexPath);
}
if (ptr.ref.workingDirectory != nullptr) {
calloc.free(ptr.ref.workingDirectory);
}
if (ptr.ref.model != nullptr) {
calloc.free(ptr.ref.model);
}
if (ptr.ref.apiKey != nullptr) {
calloc.free(ptr.ref.apiKey);
}
if (ptr.ref.gatewayUrl != nullptr) {
calloc.free(ptr.ref.gatewayUrl);
}
calloc.free(ptr);
}
}
/// Configuration for Codex FFI.
class CodexConfig {
final String? codexPath;
final String? workingDirectory;
final int sandboxMode;
final int approvalPolicy;
final String? model;
final String? apiKey;
final String? gatewayUrl;
final bool debug;
const CodexConfig({
this.codexPath,
this.workingDirectory,
this.sandboxMode = 1, // workspace-write
this.approvalPolicy = 0, // suggest
this.model,
this.apiKey,
this.gatewayUrl,
this.debug = false,
});
}